本節咱們來介紹一下新浪微博宮格驗證碼的識別,此驗證碼是一種新型交互式驗證碼,每一個宮格之間會有一條指示連線,指示了咱們應該的滑動軌跡,咱們須要按照滑動軌跡依次從起始宮格一直滑動到終止宮格才能夠完成驗證,如圖 8-24 所示:css
圖 8-24 驗證碼示例web
鼠標滑動後的軌跡會以×××的連線來標識,如圖 8-25 所示:算法
圖 8-25 滑動過程瀏覽器
咱們能夠訪問新浪微博移動版登陸頁面就能夠看到如上驗證碼,連接爲:https://passport.weibo.cn/signin/login,固然也不是每次都會出現驗證碼,通常當頻繁登陸或者帳號存在安全風險的時候會出現。安全
接下來咱們就來試着識別一下此類驗證碼。學習過程當中有不懂的能夠加入咱們的學習交流秋秋圈784中間758後面214,與你分享Python企業當下人才需求及怎麼從零基礎學習Python,和學習什麼內容。相關學習視頻資料、開發工具都有分享網絡
本節咱們的目標是用程序來識別並經過微博宮格驗證碼的驗證。ide
本次咱們使用的 Python 庫是 Selenium,使用的瀏覽器爲 Chrome,在此以前請確保已經正確安裝好了 Selenium 庫、Chrome瀏覽器並配置好了 ChromeDriver,相關流程能夠參考第一章的說明。工具
要識別首先要從探尋規律入手,那麼首先咱們找到的規律就是此驗證碼的四個宮格必定是有連線通過的,並且每一條連線上都會相應的指示箭頭,連線的形狀多樣,如C型、Z型、X型等等,如圖 8-2六、8-2七、8-28 所示:學習
圖 8-26 C 型開發工具
圖 8-27 Z 型
圖 8-28 X 型
而同時咱們發現同一種類型它的連線軌跡是相同的,惟一不一樣的就是連線的方向,如圖 8-2九、8-30 所示:
圖 8-29 反向連線
圖 8-30 正向連線
這兩種驗證碼的連線軌跡是相同的,可是因爲連線上面的指示箭頭不一樣致使滑動的宮格順序就有所不一樣。
因此要徹底識別滑動宮格順序的話就須要具體識別出箭頭的朝向,而觀察一下整個驗證碼箭頭朝向一共可能有 8 種,並且會出如今不一樣的位置,若是要寫一個箭頭方向識別算法的話須要都考慮到不一樣箭頭所在的位置,咱們須要找出各個位置的箭頭的像素點座標,同時識別算法還須要計算其像素點變化規律,這個工做量就變得比較大。
這時咱們能夠考慮用模板匹配的方法,模板匹配的意思就是將一些識別目標提早保存下來並作好標記,稱做模板,在這裏咱們就能夠獲取驗證碼圖片並作好拖動順序的標記當作模板。在匹配的時候來對比要新識別的目標和每個模板哪一個是匹配的,若是找到匹配的模板,則被匹配到的模板就和新識別的目標是相同的,這樣就成功識別出了要新識別的目標了。模板匹配在圖像識別中也是很是經常使用的一種方法,實現簡單並且易用性好。
模板匹配方法若是要效果好的話,咱們必需要收集到足夠多的模板才能夠,而對於微博宮格驗證碼來講,宮格就 4 個,驗證碼的樣式最多就是 4 3 2 * 1 = 24種,因此咱們能夠直接將全部模板都收集下來。
因此接下來咱們須要考慮的就是用何種模板來進行匹配,是隻匹配箭頭仍是匹配整個驗證碼全圖呢?咱們來權衡一下這兩種方式的匹配精度和工做量:
因此綜上考慮,咱們選用全圖匹配的方式來進行識別。
因此到此爲止,咱們就可使用全圖模板匹配的方法來識別這個宮格驗證碼了,找到匹配的模板以後,咱們就能夠獲得事先爲模板定義的拖動順序,而後模擬拖動便可。
在開始以前,咱們須要作一下準備工做,先將 24 張驗證碼全圖保存下來,保存工做難道須要手工來作嗎?固然不是的,由於驗證碼是隨機的,一共有 24 種,因此咱們能夠寫一段程序來批量保存一些驗證碼圖片,而後從中篩選出須要的圖片就行了,代碼以下:
import time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC USERNAME = '' PASSWORD = '' class CrackWeiboSlide(): def __init__(self): self.url = 'https://passport.weibo.cn/signin/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 20) self.username = USERNAME self.password = PASSWORD def __del__(self): self.browser.close() def open(self): """ 打開網頁輸入用戶名密碼並點擊 :return: None """ self.browser.get(self.url) username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName'))) password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword'))) submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction'))) username.send_keys(self.username) password.send_keys(self.password) submit.click() def get_position(self): """ 獲取驗證碼位置 :return: 驗證碼位置元組 """ try: img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow'))) except TimeoutException: print('未出現驗證碼') self.open() time.sleep(2) location = img.location size = img.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] return (top, bottom, left, right) def get_screenshot(self): """ 獲取網頁截圖 :return: 截圖對象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_image(self, name='captcha.png'): """ 獲取驗證碼圖片 :return: 圖片對象 """ top, bottom, left, right = self.get_position() print('驗證碼位置', top, bottom, left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def main(self): """ 批量獲取驗證碼 :return: 圖片對象 """ count = 0 while True: self.open() self.get_image(str(count) + '.png') count += 1 if __name__ == '__main__': crack = CrackWeiboSlide() crack.main()
其中這裏須要將 USERNAME 和 PASSWORD 修改成本身微博的用戶名密碼,運行一段時間後即可以發如今本地多了不少以數字命名的驗證碼,如圖 8-31 所示:
圖 8-31 獲取結果
在這裏咱們只須要挑選出不一樣的24張驗證碼圖片並命名保存就行了,名稱能夠直接取做宮格的滑動的順序,如某張驗證碼圖片如圖 8-32 所示:
圖 8-32 驗證碼示例
咱們將其命名爲 4132.png 便可,也就是表明滑動順序爲 4-1-3-2,按照這樣的規則,咱們將驗證碼整理爲以下 24 張圖,如圖 8-33 所示:
圖 8-33 整理結果
如上的 24 張圖就是咱們的模板,接下來咱們在識別的時候只須要遍歷模板進行匹配便可。
上面的代碼已經實現了將驗證碼保存下來的功能,經過調用 get_image() 方法咱們即可以獲得驗證碼圖片對象,獲得驗證碼對象以後咱們就須要對其進行模板匹配了,定義以下的方法進行匹配:
from os import listdir def detect_image(self, image): """ 匹配圖片 :param image: 圖片 :return: 拖動順序 """ for template_name in listdir(TEMPLATES_FOLDER): print('正在匹配', template_name) template = Image.open(TEMPLATES_FOLDER + template_name) if self.same_image(image, template): # 返回順序 numbers = [int(number) for number in list(template_name.split('.')[0])] print('拖動順序', numbers) return numbers Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
在這裏 TEMPLATES_FOLDER 就是模板所在的文件夾,在這裏咱們用 listdir() 方法將全部模板的文件名稱獲取出來,而後對其進行遍歷,經過 same_image() 方法對驗證碼和模板進行比對,若是成功匹配,那麼就將匹配到的模板文件名轉爲列表,如匹配到了 3124.png,則返回結果 [3, 1, 2, 4]。
比對的方法實現以下:
def is_pixel_equal(self, image1, image2, x, y): """ 判斷兩個像素是否相同 :param image1: 圖片1 :param image2: 圖片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 取兩個圖片的像素點 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 20 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def same_image(self, image, template): """ 識別類似驗證碼 :param image: 待識別驗證碼 :param template: 模板 :return: """ # 類似度閾值 threshold = 0.99 count = 0 for x in range(image.width): for y in range(image.height): # 判斷像素是否相同 if self.is_pixel_equal(image, template, x, y): count += 1 result = float(count) / (image.width * image.height) if result > threshold: print('成功匹配') return True return False
在這裏比對圖片也是利用了遍歷像素的方法,same_image() 方法接收兩個參數,image 爲待檢測的驗證碼圖片對象,template 是模板對象,因爲兩者大小是徹底一致的,因此在這裏咱們遍歷了圖片的全部像素點,比對兩者同一位置的像素點是否相同,若是相同就計數加 1,最後計算一下相同的像素點佔總像素的比例,若是該比例超過必定閾值那就斷定爲圖片徹底相同,匹配成功。在這裏設定閾值爲 0.99,即若是兩者有 0.99 以上的類似比則表明匹配成功。
這樣經過上面的方法,依次匹配 24 個模板,若是驗證碼圖片正常,總能找到一個匹配的模板,這樣最後就能夠獲得宮格的滑動順序了。
獲得了滑動順序以後,咱們接下來就是根據滑動順序來拖動鼠標鏈接各個宮格了,方法實現以下:
def move(self, numbers): """ 根據順序拖動 :param numbers: :return: """ # 得到四個按點 circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ') dx = dy = 0 for index in range(4): circle = circles[numbers[index] - 1] # 若是是第一次循環 if index == 0: # 點擊第一個按點 ActionChains(self.browser) .move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) .click_and_hold().perform() else: # 小幅移動次數 times = 30 # 拖動 for i in range(times): ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform() time.sleep(1 / times) # 若是是最後一次循環 if index == 3: # 鬆開鼠標 ActionChains(self.browser).release().perform() else: # 計算下一次偏移 dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x'] dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y'] Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
在這裏方法接收的參數就是宮格的點按順序,如 [3, 1, 2, 4]。首先咱們利用 find_elements_by_css_selector() 方法獲取到四個宮格元素,是一個列表形式,每一個元素表明一個宮格,接下來咱們遍歷了宮格的點按順序,再作一系列對應操做。
其中若是是第一個宮格,那就直接鼠標點擊並保持動做,不然移動到下一個宮格。若是是最後一個宮格,那就鬆開鼠標,不然計算移動到下一個宮格的偏移量。
經過四次循環,咱們即可以成功操做瀏覽器完成宮格驗證碼的拖拽填充,鬆開鼠標以後便可識別成功。
運行效果如圖 8-34 所示:
圖 8-34 運行效果
鼠標會慢慢的從起始位置移動到終止位置,最後一個宮格鬆開以後便完成了驗證碼的識別。
至此,微博宮格驗證碼的識別就所有完成了。
識別完成以後驗證碼窗口會自動關閉,接下來直接點擊登陸按鈕便可完成微博登陸。
本節咱們介紹了一種經常使用的模板匹配識別圖片的方式來識別驗證碼,並模擬了鼠標拖拽動做來實現驗證碼的識別。若是遇到相似的驗證碼,能夠採用一樣的思路進行識別。