Python+scrapy爬虫:手绘验证码识别
一、介绍
今天主要介绍的是微博客户端在登录时出现的四宫格手绘验证码,不多说直接看看验证码长成什么样。
二、思路
1、由于微博上的手绘验证码只有四个宫格,且每个宫格之间都有有向线段连接,所以我们可以判断四个宫格不同方向的验证码一共有24种,
我们将四个宫格进行标号,得到的结果如下:
则我们可以排列出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()
四、识别结果
通过循环四次后绘出四条方向,最终得到效果图
- END -
文源网络,仅供学习之用,如有侵权,联系删除。
◆
◆
◆