Python+scrapy爬蟲識別驗證碼(四)手繪驗證碼識別

一、介紹

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

二、思路

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

我們將四個宮格進行標號,得到的結果如下:

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


2、我們通過獲取到微博客戶端的24種手繪驗證碼後需要進行模板匹配,這樣通過全圖匹配的方式進行滑動。

三、代碼實現

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

只有在賬號存在風險或者頻繁登錄的時候才會出現。獲取手繪驗證碼的代碼如下:

注意:需要將模擬瀏覽器所以元素(用戶名框,密碼框)加載完了才能發送用戶名和密碼,否則報錯

<code># -*- 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


class CrackWeiboSlide():
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()

def open(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()


def get_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()


def main(self):
count = 1
while True:
# 調用打開模擬瀏覽器函數
self.open()
# 調用獲取驗證碼圖片函數
self.get_image(str(count) + ".png")

count += 1


if __name__ == '__main__':
crack = CrackWeiboSlide()
crack.main()/<code>

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

上圖就是我們需要的模板,接下來我們進行遍歷模板匹配即可

2、模板匹配

通過遍歷手繪驗證碼模板進行匹配

<code>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

class CrackWeiboSlide():
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()

def open(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()


def get_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()

def is_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

def same_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


def detect_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

def move(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']


def main(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()/<code>

四、識別結果

通過循環四次後繪出四條方向,最終得到效果圖