Python爬蟲學習筆記之微信宮格驗證碼的識別(存在問題)
本節我們將介紹新浪微博宮格驗證碼的識別。微博宮格驗證碼是一種新型交互式驗證碼,每個宮格之間會有一條
指示連線,指示了應該的滑動軌跡。我們要按照滑動軌跡依次從起始宮格滑動到終止宮格,才可以完成驗證,如
下圖所示。
鼠標滑動後的軌跡會以黃色的連線來標識,如下圖所示。
訪問新浪微博移動版登錄頁面,就可以看到如上驗證碼,鏈接為 https://passport.weibo.cn/signin/login
一、本節目標
我們的目標是用程序來識別並通過微博宮格驗證碼的驗證。
二、準備工作
本次我們使用的Python庫是Selenium,使用的瀏覽器為Chrome,請確保已經正確安裝好Selenium庫、Chrome瀏覽器,並配置好ChromeDriver。
三、識別思路
識別從探尋規律入手。規律就是,此驗證碼的四個宮格一定是有連線經過的,每一條連線上都會相應的指示箭頭,連線的形狀多樣,包括C型、Z型、X型等,如下圖所示。
我們發現,同一類型的連線軌跡是相同的,唯一不同的就是連線的方向,如下圖所示。這兩種驗證碼的連線軌跡是相同的。但是由於連線上面的指示箭頭不同,導致滑動的宮格順序有所不同。
如果要完全識別滑動宮格順序,就需要具體識別出箭頭的朝向。而整個驗證碼箭頭朝向一共有8種,而且會出現在不同的位置。如果要寫一個箭頭方向識別算法,需要考慮不同箭頭所在的位置,找
出各個位置箭頭的像素點坐標,計算像素點變化規律,這個工作量就會變得比較大。這時我們可以考慮用模板匹配的方法,就是將一些識別目標提前保存並做好標記,這稱作模板。這裏將驗證碼圖
片做好拖動順序的標記當做模板。對比要新識別的目標和每一個模板,如果找到匹配的模板,則就成功識別出要新識別的目標。在圖像識別中,模板匹配也是常用的方法,實現簡單且易用性好。
我們必須要收集到足夠多的模板,模板匹配方法的效果才會好。而對於微博宮格驗證碼來說,宮格只有4個,驗證碼的樣式最多4×3×2×1=24種,則我們可以將所有模板都收集下來。
接下來我們需要考慮的就是,用何種模板來進行匹配,只匹配箭頭還是匹配整個驗證碼全圖呢?我們權衡一下這兩種方式的匹配精度和工作量。
首先是精度問題。如果是匹配箭頭,比對的目標只有幾個像素點範圍的箭頭,我們需要精確知道各個箭頭所在的像素點,一旦像素點有偏差,那麽會直接錯位,導致匹配結果大打折扣。如果
是匹配全圖,我們無需關心箭頭所在位置,同時還有連線幫助輔助匹配。顯然,全圖匹配的精度更高。
其次是工作量的問題。如果是匹配箭頭,我們需要保存所有不同朝向的箭頭模板,而相同位置箭頭的朝向可能不一,相同朝向的箭頭位置可能不一,那麽我們需要算出每個箭頭的位置並將其
逐個截出保存成模板,依次探尋驗證碼對應位置是否有匹配模板。如果是匹配全圖,我們不需要關心每個箭頭的位置和朝向,只需要將驗證碼全圖保存下來即可,在匹配的時候也不需要計算箭頭的
位置。顯然,匹配全圖的工作量更少。
綜上考慮,我們選用全圖匹配的方式來進行識別。找到匹配的模板之後,我們就可以得到事先為模板定義的拖動順序,然後模擬拖動即可。
獲取模板:
1 import os 2 import time 3 from io import BytesIO 4 from PIL import Image 5 from selenium import webdriver 6 from selenium.common.exceptions import TimeoutException 7 from selenium.webdriver import ActionChains 8 from selenium.webdriver.common.by import By 9 from selenium.webdriver.support.ui import WebDriverWait 10 from selenium.webdriver.support import expected_conditions as EC 11 from os import listdir 12 13 USERNAME = ‘‘ 14 PASSWORD = ‘‘ 15 16 TEMPLATES_FOLDER = ‘templates/‘ 17 18 class CrackWeiboSlide(): 19 def __init__(self): 20 self.url = ‘https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/‘ 21 self.browser = webdriver.Chrome() 22 self.wait = WebDriverWait(self.browser, 20) 23 self.username = USERNAME 24 self.password = PASSWORD 25 26 def __del__(self): 27 self.browser.close() 28 29 def open(self): 30 """ 31 打開網頁輸入用戶名密碼登陸 32 :return: None 33 """ 34 self.browser.get(self.url) 35 username = self.wait.until(EC.presence_of_element_located((By.ID, ‘loginName‘))) 36 password = self.wait.until(EC.presence_of_element_located((By.ID, ‘loginPassword‘))) 37 submit = self.wait.until(EC.element_to_be_clickable((By.ID, ‘loginAction‘))) 38 username.send_keys(self.username) 39 password.send_keys(self.password) 40 submit.click() 41 42 def get_position(self): 43 """ 44 獲取驗證碼位置 45 :return: 驗證碼位置元組 46 """ 47 try: 48 img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, ‘patt-shadow‘))) 49 except TimeoutException: 50 print(‘未出現驗證碼‘) 51 self.opem() 52 time.sleep(2) 53 location = img.location 54 size = img.size 55 top, bottom, left, right = location[‘y‘], location[‘y‘] + size[‘height‘], location[‘x‘], location[‘x‘] + size[‘width‘] 56 return (top, bottom, left, right) 57 58 def get_screenshot(self): 59 """ 60 獲取網頁截圖 61 :return: 截圖對象 62 """ 63 screenshot = self.browser.get_screenshot_as_png() 64 screenshot = Image.open(BytesIO(screenshot)) 65 return screenshot 66 67 def get_image(self, name=‘captcha.png‘): 68 """ 69 獲取驗證碼圖片 70 :return:圖片對象 71 """ 72 top, bottom, left, right = self.get_position() 73 print(‘驗證碼位置‘, top, bottom, left, right) 74 screenshot = self.get_screenshot() 75 captcha = screenshot.crop((left, top, right, bottom)) 76 captcha.save(name) 77 return captcha 78 79 def main(self): 80 """ 81 批量獲取驗證碼 82 :return: 圖片對象 83 """ 84 count = 0 85 while True: 86 self.open() 87 self.get_image(str(count) + ‘.png‘) 88 count += 1 89 90 if __name__ == ‘__main__‘: 91 crack = CrackWeiboSlide() 92 crack.main()
這裏需要將USERNAME
和PASSWORD
修改為自己微博的用戶名和密碼。運行一段時間後,本地多了很多以數字命名的驗證碼,如下圖所示。
我們將圖片命名為4132.png,代表滑動順序為4-1-3-2。按照這樣的規則,我們將驗證碼整理為如下24張圖,如下圖所示。
好了,獲取模板就到此結束了,接下來該模板匹配了
方法解釋:
(1)調用get_image()
方法,得到驗證碼圖片對象。然後,對驗證碼圖片對象進行模板匹配
(2)TEMPLATES_FOLDER
就是模板所在的文件夾。這裏通過listdir()
方法獲取所有模板的文件名稱,然後對其進行遍歷,通過same_image()
方法對驗證碼和模板進行比對。如果匹配成功,那麽就將匹配到的模板文件名轉換為列表。如模板文件3124.png匹配到了,則返回結果
為[3, 1, 2, 4]。
(3)same_image()
方法接收兩個參數,image
為待檢測的驗證碼圖片對象,template
是模板對象。由於二者大小是完全一致的,所以在這裏我
們遍歷了圖片的所有像素點。比對二者同一位置的像素點,如果像素點相同,計數就加1。最後計算相同的像素點占總像素的比例。如果
該比例超過一定閾值,那就判定圖片完全相同,則匹配成功。這裏閾值設定為0.99,即如果二者有0.99以上的相似比,則代表匹配成功。
(4)通過上面的方法,依次匹配24個模板。如果驗證碼圖片正常,我們總能找到一個匹配的模板,這樣就可以得到宮格的滑動順序了。
(5)接下來,根據滑動順序拖動鼠標,連接各個宮格
這裏方法接收的參數就是宮格的點按順序,如[3,1,2,4]。首先我們利用find_elements_by_css_selector()
方法獲取到4個宮格元素,它
是一個列表形式,每個元素代表一個宮格。接下來遍歷宮格的點按順序,做一系列對應操作。其中如果當前遍歷的是第一個宮格,那就直
接鼠標點擊並保持動作,否則移動到下一個宮格。如果當前遍歷的是最後一個宮格,那就松開鼠標,如果不是最後一個宮格,則計算移動
到下一個宮格的偏移量。通過4次循環,我們便可以成功操作瀏覽器完成宮格驗證碼的拖拽填充,松開鼠標之後即可識別成功.
(6)鼠標會慢慢從起始位置移動到終止位置。最後一個宮格松開之後,驗證碼的識別便完成了。至此,微博宮格驗證碼的識別就全部完成。驗
證碼窗口會自動關閉。直接點擊登錄按鈕即可登錄微博。
方法代碼(為什麽驗證不成功?請大家幫我看一下):
1 import os 2 import time 3 from io import BytesIO 4 from PIL import Image 5 from selenium import webdriver 6 from selenium.common.exceptions import TimeoutException 7 from selenium.webdriver import ActionChains 8 from selenium.webdriver.common.by import By 9 from selenium.webdriver.support.ui import WebDriverWait 10 from selenium.webdriver.support import expected_conditions as EC 11 from os import listdir 12 13 USERNAME = ‘‘ 14 PASSWORD = ‘‘ 15 16 TEMPLATES_FOLDER = ‘templates/‘ 17 18 19 class CrackWeiboSlide(): 20 def __init__(self): 21 self.url = ‘https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/‘ 22 self.browser = webdriver.Chrome() 23 self.wait = WebDriverWait(self.browser, 20) 24 self.username = USERNAME 25 self.password = PASSWORD 26 27 def __del__(self): 28 self.browser.close() 29 30 def open(self): 31 """ 32 打開網頁輸入用戶名密碼並點擊 33 :return: None 34 """ 35 self.browser.get(self.url) 36 username = self.wait.until(EC.presence_of_element_located((By.ID, ‘loginName‘))) 37 password = self.wait.until(EC.presence_of_element_located((By.ID, ‘loginPassword‘))) 38 submit = self.wait.until(EC.element_to_be_clickable((By.ID, ‘loginAction‘))) 39 username.send_keys(self.username) 40 password.send_keys(self.password) 41 submit.click() 42 43 def get_position(self): 44 """ 45 獲取驗證碼位置 46 :return: 驗證碼位置元組 47 """ 48 try: 49 img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, ‘patt-shadow‘))) 50 except TimeoutException: 51 print(‘未出現驗證碼‘) 52 self.open() 53 time.sleep(2) 54 location = img.location 55 size = img.size 56 top, bottom, left, right = location[‘y‘], location[‘y‘] + size[‘height‘], location[‘x‘], location[‘x‘] + size[ 57 ‘width‘] 58 return (top, bottom, left, right) 59 60 def get_screenshot(self): 61 """ 62 獲取網頁截圖 63 :return: 截圖對象 64 """ 65 screenshot = self.browser.get_screenshot_as_png() 66 screenshot = Image.open(BytesIO(screenshot)) 67 return screenshot 68 69 def get_image(self, name=‘captcha.png‘): 70 """ 71 獲取驗證碼圖片 72 :return: 圖片對象 73 """ 74 top, bottom, left, right = self.get_position() 75 print(‘驗證碼位置‘, top, bottom, left, right) 76 screenshot = self.get_screenshot() 77 captcha = screenshot.crop((left, top, right, bottom)) 78 captcha.save(name) 79 return captcha 80 81 def is_pixel_equal(self, image1, image2, x, y): 82 """ 83 判斷兩個像素是否相同 84 :param image1: 圖片1 85 :param image2: 圖片2 86 :param x: 位置x 87 :param y: 位置y 88 :return: 像素是否相同 89 """ 90 # 取兩個圖片的像素點 91 pixel1 = image1.load()[x, y] 92 pixel2 = image2.load()[x, y] 93 threshold = 20 94 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( 95 pixel1[2] - pixel2[2]) < threshold: 96 return True 97 else: 98 return False 99 100 def same_image(self, image, template): 101 """ 102 識別相似驗證碼 103 :param image: 待識別驗證碼 104 :param template: 模板 105 :return: 106 """ 107 # 相似度閾值 108 threshold = 0.99 109 count = 0 110 for x in range(image.width): 111 for y in range(image.height): 112 # 判斷像素是否相同 113 if self.is_pixel_equal(image, template, x, y): 114 count += 1 115 result = float(count) / (image.width * image.height) 116 if result > threshold: 117 print(‘成功匹配‘) 118 return True 119 return False 120 121 def detect_image(self, image): 122 """ 123 匹配圖片 124 :param image: 圖片 125 :return: 拖動順序 126 """ 127 for template_name in listdir(TEMPLATES_FOLDER): 128 print(‘正在匹配‘, template_name) 129 template = Image.open(TEMPLATES_FOLDER + template_name) 130 if self.same_image(image, template): 131 # 返回順序 132 numbers = [int(number) for number in list(template_name.split(‘.‘)[0])] 133 print(‘拖動順序‘, numbers) 134 return numbers 135 136 def move(self, numbers): 137 """ 138 根據順序拖動 139 :param numbers: 140 :return: 141 """ 142 # 獲得四個按點 143 circles = self.browser.find_elements_by_css_selector(‘.patt-wrap .patt-circ‘) 144 dx = dy = 0 145 for index in range(4): 146 circle = circles[numbers[index] - 1] 147 # 如果是第一次循環 148 if index == 0: 149 # 點擊第一個按點 150 ActionChains(self.browser).move_to_element_with_offset(circle, circle.size[‘width‘] / 2, circle.size[‘height‘] / 2).click_and_hold().perform() 151 else: 152 # 小幅移動次數 153 times = 30 154 # 拖動 155 for i in range(times): 156 ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform() 157 time.sleep(1 / times) 158 # 如果是最後一次循環 159 if index == 3: 160 # 松開鼠標 161 ActionChains(self.browser).release().perform() 162 else: 163 # 計算下一次偏移 164 dx = circles[numbers[index + 1] - 1].location[‘x‘] - circle.location[‘x‘] 165 dy = circles[numbers[index + 1] - 1].location[‘y‘] - circle.location[‘y‘] 166 167 def crack(self): 168 """ 169 破解入口 170 :return: 171 """ 172 self.open() 173 # 獲取驗證碼圖片 174 image = self.get_image(‘captcha.png‘) 175 numbers = self.detect_image(image) 176 self.move(numbers) 177 time.sleep(10) 178 print(‘識別結束‘) 179 180 181 if __name__ == ‘__main__‘: 182 crack = CrackWeiboSlide() 183 crack.crack()
錯誤提示:
最後,本節代碼來自:https://github.com/Python3WebSpider/CrackWeiboSlide
Python爬蟲學習筆記之微信宮格驗證碼的識別(存在問題)