天天看點

Python+scrapy爬蟲:手繪驗證碼識别

Python+scrapy爬蟲:手繪驗證碼識别

一、介紹

今天主要介紹的是微部落格戶端在登入時出現的四宮格手繪驗證碼,不多說直接看看驗證碼長成什麼樣。

Python+scrapy爬蟲:手繪驗證碼識别

二、思路

1、由于微網誌上的手繪驗證碼隻有四個宮格,且每個宮格之間都有有向線段連接配接,是以我們可以判斷四個宮格不同方向的驗證碼一共有24種,

我們将四個宮格進行标号,得到的結果如下:

Python+scrapy爬蟲:手繪驗證碼識别

則我們可以排列出24種不同的手繪方向的驗證碼,分别為一下24種

2、我們通過擷取到微部落格戶端的24種手繪驗證碼後需要進行模闆比對,這樣通過全圖比對的方式進行滑動。

三、代碼實作

1、首先是要通過微網誌移動端(https://passport.weibo.cn/signin/login) 批量擷取手繪驗證碼,但是這個驗證碼不一定出現,

隻有在賬号存在風險或者頻繁登入的時候才會出現。擷取手繪驗證碼的代碼如下:

注意:需要将模拟浏覽器是以元素(使用者名框,密碼框)加載完了才能發送使用者名和密碼,否則報錯

# -*- coding:utf-8 -*-              import time              from io import BytesIO              from PIL import Image              from selenium import webdriver              from selenium.webdriver.common.by import By              from selenium.common.exceptions import TimeoutException              from selenium.webdriver.support.ui import WebDriverWait              from selenium.webdriver.support import expected_conditions as EC                      classCrackWeiboSlide():                 def__init__(self):                     self.url = "https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/"                     self.browser = webdriver.Chrome(r"D:\chromedriver.exe")                     self.browser.maximize_window()                     self.wait = WebDriverWait(self.browser,5)                         def__del__(self):                     self.browser.close()                     defopen(self):                     # 打開模拟浏覽器                     self.browser.get(self.url)                     # 擷取使用者名元素                     username = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginName"]')))                     # 擷取密碼框元素                     password = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginPassword"]')))                     # 擷取登入按鈕元素                     submit = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="loginAction"]')))                     # 送出資料并登入                     username.send_keys("15612345678")                     password.send_keys("xxxxxxxxxxxx")                     submit.click()                         defget_image(self,name = "captcha.png"):                     try:                         # 擷取驗證碼圖檔元素                         img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,"patt-shadow")))                         time.sleep(1)                         # 擷取驗證碼圖檔所在的位置                         location = img.location                         # 擷取驗證碼圖檔的大小                         size = img.size                         top = location["y"]  # 上                         bottom = location["y"] + size["height"]  # 下                         left = location["x"]  # 左                         right = location["x"] + size["width"]  # 右                         print("驗證碼的位置:", left, top, right, bottom)                         # 将目前視窗進行截屏                         screenshot = self.browser.get_screenshot_as_png()                         # 讀取截圖                         screenshot = Image.open(BytesIO(screenshot))                         # 剪切九宮格圖檔驗證碼                         captcha = screenshot.crop((left, top, right, bottom))                         # 将剪切的九宮格驗證碼儲存到指定位置                         captcha.save(name)                         print("微網誌登入驗證碼儲存完成!!!")                         return captcha                     except TimeoutException:                         print("沒有出現驗證碼!!")                         # 回調打開模拟浏覽器函數                         self.open()                         defmain(self):                     count = 1                     while True:                         # 調用打開模拟浏覽器函數                         self.open()                         # 調用擷取驗證碼圖檔函數                         self.get_image(str(count) + ".png")                         count += 1                      if __name__ == '__main__':                 crack = CrackWeiboSlide()                 crack.main()           

得到的24種手繪驗證碼,同時需要對這些手繪驗證碼根據上邊的編号進行命名

上圖就是我們需要的模闆,接下來我們進行周遊模闆比對即可

2、模闆比對

通過周遊手繪驗證碼模闆進行比對

import os              import time              from io import BytesIO              from PIL import Image              from selenium import webdriver              from selenium.webdriver import ActionChains              from selenium.webdriver.common.by import By              from selenium.common.exceptions import TimeoutException              from selenium.webdriver.support.ui import WebDriverWait              from selenium.webdriver.support import expected_conditions as EC                  classCrackWeiboSlide():                 def__init__(self):                     self.url = "https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/"                     self.browser = webdriver.Chrome(r"D:\chromedriver.exe")                     self.browser.maximize_window()                     self.wait = WebDriverWait(self.browser,5)                         def__del__(self):                     self.browser.close()                     defopen(self):                     # 打開模拟浏覽器                     self.browser.get(self.url)                     # 擷取使用者名元素                     username = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginName"]')))                     # 擷取密碼框元素                     password = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginPassword"]')))                     # 擷取登入按鈕元素                     submit = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="loginAction"]')))                     # 送出資料并登入                     username.send_keys("15612345678")                     password.send_keys("xxxxxxxxxxxx")                     submit.click()                         defget_image(self,name = "captcha.png"):                     try:                         # 擷取驗證碼圖檔元素                         img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,"patt-shadow")))                         time.sleep(1)                             # 擷取驗證碼圖檔所在的位置                         location = img.location                             # 擷取驗證碼圖檔的大小                         size = img.size                         top = location["y"]                      # 上                         bottom = location["y"] + size["height"]  # 下                         left = location["x"]                     # 左                         right = location["x"] + size["width"]    # 右                         print("驗證碼的位置:", left, top, right, bottom)                             # 将目前視窗進行截屏                         screenshot = self.browser.get_screenshot_as_png()                             # 讀取截圖                         screenshot = Image.open(BytesIO(screenshot))                             # 剪切九宮格圖檔驗證碼                         captcha = screenshot.crop((left, top, right, bottom))                             # 将剪切的九宮格驗證碼儲存到指定位置                         captcha.save(name)                         print("微網誌登入驗證碼儲存完成!!!")                             # 傳回微網誌移動端的驗證碼圖檔                         return captcha                     except TimeoutException:                         print("沒有出現驗證碼!!")                             # 回調打開模拟浏覽器函數                         self.open()                     defis_pixel_equal(self,image,template,i,j):                         # 取出兩張圖檔的像素點                     pixel1 = image.load()[i,j]              # 移動用戶端擷取的驗證碼                     pixel2 = template.load()[i,j]           # 模闆檔案裡的驗證碼                     threshold = 20                          # 門檻值                     pix_r = abs(pixel1[0] - pixel2[0])      # R                     pix_g = abs(pixel1[1] - pixel2[1])      # G                     pix_b = abs(pixel1[2] - pixel2[2])      # B                     if (pix_r< threshold) and (pix_g< threshold ) and (pix_b< threshold) :                         return True                     else:                         return False                     defsame_image(self,image,template):                     """                     :param image: 微網誌移動端擷取的驗證碼圖檔                     :param template: 通過模闆檔案擷取的驗證碼圖檔                     """                     threshold = 0.99            # 相似度門檻值                     count = 0                     # 周遊微網誌移動端擷取的驗證碼圖檔的寬度和高度                     for i in range(image.width):                         for j in range(image.height):                                 # 判斷兩張圖檔的像素是否相等                             if self.is_pixel_equal(image,template,i,j):                                 count += 1                     result = float(count)/(image.width*image.height)                     if result >threshold:                         print("比對成功!!!")                         return True                     else:                         return False                         defdetect_image(self,image):                     # 周遊手繪驗證碼模闆檔案内的所有驗證碼圖檔                     for template_name in os.listdir(r"D:\photo\templates"):                         print("正在比對",template_name)                             # 打開驗證碼圖檔                         template = Image.open(r"D:\photo\templates\{}".format(template_name))                             if self.same_image(image,template):                             # 傳回這張圖檔的順序,如4—>3—>1—>2                             numbers = [int(number) for number in list(template_name.split(".")[0])]                             print("按照順序進行拖動",numbers)                             return numbers                     defmove(self,numbers):                     # 獲得四個按點                     circles = self.browser.find_element_by_css_selector('.patt-wrap .patt-circ')                     dx = dy = 0                     # 由于是四個宮格,是以需要循環四次                     for index in range(4):                         circle = circles[numbers[index] - 1]                         # 如果是第一次循環                         if index == 0:                             # 點選第一個點                             action = ActionChains(self.browser).move_to_element_with_offset(circle,circle.size["width"]/2,circle.size['height']/2)                             action.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']                         defmain(self):                     # 調用打開模拟浏覽器函數                     self.open()                     image = self.get_image("captcha.png")   # 微網誌移動端的驗證碼圖檔                     numbers = self.detect_image(image)                     self.move(numbers)                     time.sleep(10)                     print('識别結束')                      if __name__ == '__main__':                 crack = CrackWeiboSlide()                 crack.main()           

四、識别結果