1. 程式人生 > >Python爬蟲學習筆記之微信宮格驗證碼的識別(存在問題)

Python爬蟲學習筆記之微信宮格驗證碼的識別(存在問題)

依次 返回結果 ptc 接下來 clas 軌跡 self top http

本節我們將介紹新浪微博宮格驗證碼的識別。微博宮格驗證碼是一種新型交互式驗證碼,每個宮格之間會有一條

指示連線,指示了應該的滑動軌跡。我們要按照滑動軌跡依次從起始宮格滑動到終止宮格,才可以完成驗證,如

下圖所示。

技術分享圖片

鼠標滑動後的軌跡會以黃色的連線來標識,如下圖所示。

技術分享圖片

訪問新浪微博移動版登錄頁面,就可以看到如上驗證碼,鏈接為 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()

這裏需要將USERNAMEPASSWORD修改為自己微博的用戶名和密碼。運行一段時間後,本地多了很多以數字命名的驗證碼,如下圖所示。

技術分享圖片

我們將圖片命名為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爬蟲學習筆記之微信宮格驗證碼的識別(存在問題)