python selenium 爬蟲文字登入驗證碼處理
前言:
我不喜歡說爬蟲這個詞, 講"自動化測試"不是帥多了嗎(笑
雖然爬蟲案子錢都不多, 但不用動甚麼腦子, 做起來很愉也很快, 是個不錯的選擇
正文:
首先, 目標網頁是https://csms.mohw.gov.tw/lcms/
我們用到的package有 :
- selenium
- opencv-python
- numpy
- ddddocr
import ddddocr
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
TARGET_URL = r"https://csms.mohw.gov.tw/lcms/" # 目標URL
ocr = ddddocr.DdddOcr() # 初始化 ddddocr
# 這裡用RemoteDriver是因為剛好有 請選擇用自己有的driver 例如 webdriver.chrome(...)
driver = webdriver.Remote(command_executor="http://127.0.0.1:4444",
desired_capabilities=DesiredCapabilities.CHROME)
接著打開目標頁面看看長怎樣
嗚阿 有個pop up modal
想辦法把他關掉
於是我們先載入網頁
from selenium.webdriver.support.ui import WebDriverWait
driver.get(TARGET_URL) # 載入網頁
wait = WebDriverWait(driver, 10) # 設定implicit wait 為10秒
接下來
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 有個礙事的modal 直接用javascript幹掉他
modal: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "myModal")))
driver.execute_script("arguments[0].remove();", modal)
WebDriverWait.until(method: any)方法可以等待直到method回傳True為止
如果沒有找到該元素(超出implicit wait的時間) 則會拋出
selenium expected_conditions 有提供一些class 方便使用
像是 visibility_of_element_located(locator: any) 可以檢查該元素是否渲染完成並可見
更多的class可以在selenium的文件中找到
然後locator通常是tuple (By: str, param: str)
用 By 的方式來定位元素 param, By可以有這些值 :
class By(object):
"""
Set of supported locator strategies.
"""
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
找到討人厭的modal之後 我們用javascript把元素直接刪除
要在webdriver執行javascript可以使用
driver.execute_script(script: any, *args: any)
script 是要執行的javascript 如果你有已經定位的元素想要在javascript中使用
將他放到args參數中 就可以在javascript中用 arguments[i] 參照
例如
driver.execute_script("arguments[0]; arguments[1];", input1, input2)
arguments[0] 會對應到 input1, arguments[1] 會對應到 input2 依此類推
然後執行完就會變這樣
耶~ modal不見了
可以看到的我們的目標驗證碼了
接著我們要辨識驗證碼 大概會有以下的步驟
- 找到圖片元素並取得圖像
- 圖像處理
- ocr辨識圖像 回傳結果
我們寫個function 方便重新辨識
from PIL import Image
import cv2
import time
import numpy as np
import io
# 因為驗證碼固定長度為4個字元 為了提高準確度
# 辨識結果的字串長度不為4時 重新取的新的驗證
# 碼 這邊寫成function方便呼叫
def captchaCrack(driver: webdriver) -> str:
wait = WebDriverWait(driver, 10)
# 找驗證碼圖片
captchaDiv: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "captchaDiv")))
captchaImg: WebElement = captchaDiv.find_element_by_tag_name("img")
# 截圖存成PIL的Image
img = Image.open(io.BytesIO(captchaImg.screenshot_as_png))
# 灰階處理
gray = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
# 雜訊處理
noise = np.random.normal(0, 15, threshold.shape)
noise = np.clip(threshold +noise, 0, 255).astype('uint8')
guassian = cv2.blur(noise, (3, 3))
# 最後存回 PIL Image
img = Image.fromarray(guassian)
# 回傳辨識結果
return ocr.classification(img)
res = captchaCrack(driver)
# 如果長度不為4 點擊重新整理再辨識一次
while(len(res) != 4):
refresh: WebElement = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button[text()='重新產生']")))
refresh.click()
time.sleep(1)
res = captchaCrack(driver)
圖像處理的方法很多我就不多說明了 Google 資料很多 OUOb
這裡把圖像轉成灰階 & 高斯模糊 降低雜訊
再用ddddocr 的classification(img) -> str 函數取得辨識的結果
img 可以是 PIL Image | bytes | base64 string | pathlib.PurePath
再把辨識結果輸入到input裡面就完成了
# 接著找驗證碼的input
captcha_input: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "captcha")))
# 將結果輸入到input
captcha_input.send_keys(res)
執行結果
最後附上完整程式碼
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from PIL import Image
import cv2
import ddddocr
import time
import numpy as np
import io
TARGET_URL = r"https://csms.mohw.gov.tw/lcms/" # 目標URL
ocr = ddddocr.DdddOcr() # 初始化 ddddocr
# 這裡用RemoteDriver是因為剛好有 請選擇用自己有的driver 例如 webdriver.chrome(...)
driver = webdriver.Remote(command_executor="http://127.0.0.1:4444",
desired_capabilities=DesiredCapabilities.CHROME)
driver.get(TARGET_URL) # 載入網頁
wait = WebDriverWait(driver, 10) # 設定implicit wait 為10秒
# 有個礙事的modal 直接用javascript幹掉他
modal: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "myModal")))
driver.execute_script("arguments[0].remove();", modal)
# 因為驗證碼固定長度為4個字元 為了提高準確度
# 辨識結果的字串長度不為4時 重新取的新的驗證
# 碼 這邊寫成function方便呼叫
def captchaCrack(driver: webdriver) -> str:
wait = WebDriverWait(driver, 10)
# 找驗證碼圖片
captchaDiv: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "captchaDiv")))
captchaImg: WebElement = captchaDiv.find_element_by_tag_name("img")
# 截圖存成PIL的Image
img = Image.open(io.BytesIO(captchaImg.screenshot_as_png))
# 灰階處理
gray = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
# 雜訊處理
noise = np.random.normal(0, 15, threshold.shape)
noise = np.clip(threshold +noise, 0, 255).astype('uint8')
guassian = cv2.blur(noise, (3, 3))
# 最後存回 PIL Image
img = Image.fromarray(guassian)
# 回傳辨識結果
return ocr.classification(img)
res = captchaCrack(driver)
# 如果長度不為4 點擊重新整理再辨識一次
while(len(res) != 4):
refresh: WebElement = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button[text()='重新產生']")))
refresh.click()
time.sleep(1)
res = captchaCrack(driver)
# 接著找驗證碼的input
captcha_input: WebElement = wait.until(
EC.visibility_of_element_located((By.ID, "captcha")))
# 將結果輸入到input
captcha_input.send_keys(res)
留言
張貼留言