本节咱们将介绍新浪微博宫格验证码的识别。微博宫格验证码是一种新型交互式验证码,每一个宫格之间会有一条指示连线,指示了应该的滑动轨迹。咱们要按照滑动轨迹依次从起始宫格滑动到终止宫格,才能够完成验证,以下图所示。
css
鼠标滑动后的轨迹会以黄色的连线来标识,以下图所示。html
访问新浪微博移动版登陆页面,就能够看到如上验证码,连接为https://passport.weibo.cn/signin/login。不是每次登陆都会出现验证码,当频繁登陆或者帐号存在安全风险的时候,验证码才会出现。
git
咱们的目标是用程序来识别并经过微博宫格验证码的验证。
github
本次咱们使用的Python库是Selenium,使用的浏览器为Chrome,请确保已经正确安装好Selenium库、Chrome浏览器,并配置好ChromeDriver。
web
识别从探寻规律入手。规律就是,此验证码的四个宫格必定是有连线通过的,每一条连线上都会相应的指示箭头,连线的形状多样,包括C型、Z型、X型等,以下图所示。
算法
咱们发现,同一类型的连线轨迹是相同的,惟一不一样的就是连线的方向,以下图所示。浏览器
这两种验证码的连线轨迹是相同的。可是因为连线上面的指示箭头不一样,致使滑动的宫格顺序有所不一样。安全
若是要彻底识别滑动宫格顺序,就须要具体识别出箭头的朝向。而整个验证码箭头朝向一共有8种,并且会出如今不一样的位置。若是要写一个箭头方向识别算法,须要考虑不一样箭头所在的位置,找出各个位置箭头的像素点坐标,计算像素点变化规律,这个工做量就会变得比较大。bash
这时咱们能够考虑用模板匹配的方法,就是将一些识别目标提早保存并作好标记,这称做模板。这里将验证码图片作好拖动顺序的标记当作模板。对比要新识别的目标和每个模板,若是找到匹配的模板,则就成功识别出要新识别的目标。在图像识别中,模板匹配也是经常使用的方法,实现简单且易用性好。微信
咱们必需要收集到足够多的模板,模板匹配方法的效果才会好。而对于微博宫格验证码来讲,宫格只有4个,验证码的样式最多4×3×2×1=24种,则咱们能够将全部模板都收集下来。
接下来咱们须要考虑的就是,用何种模板来进行匹配,只匹配箭头仍是匹配整个验证码全图呢?咱们权衡一下这两种方式的匹配精度和工做量。
首先是精度问题。若是是匹配箭头,比对的目标只有几个像素点范围的箭头,咱们须要精确知道各个箭头所在的像素点,一旦像素点有误差,那么会直接错位,致使匹配结果大打折扣。若是是匹配全图,咱们无需关心箭头所在位置,同时还有连线帮助辅助匹配。显然,全图匹配的精度更高。
其次是工做量的问题。若是是匹配箭头,咱们须要保存全部不一样朝向的箭头模板,而相同位置箭头的朝向可能不一,相同朝向的箭头位置可能不一,那么咱们须要算出每一个箭头的位置并将其逐个截出保存成模板,依次探寻验证码对应位置是否有匹配模板。若是是匹配全图,咱们不须要关心每一个箭头的位置和朝向,只须要将验证码全图保存下来便可,在匹配的时候也不须要计算箭头的位置。显然,匹配全图的工做量更少。
综上考虑,咱们选用全图匹配的方式来进行识别。找到匹配的模板以后,咱们就能够获得事先为模板定义的拖动顺序,而后模拟拖动便可。
咱们须要作一下准备工做。先保存24张验证码全图。由于验证码是随机的,一共有24种。咱们能够写一段程序来批量保存验证码图片,而后从中筛选出须要的图片,代码以下所示:
import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
USERNAME = ''
PASSWORD = ''
class CrackWeiboSlide():
def __init__(self):
self.url = 'https://passport.weibo.cn/signin/login'
self.browser = webdriver.Chrome()
self.wait = WebDriverWait(self.browser, 20)
self.username = USERNAME
self.password = PASSWORD
def __del__(self):
self.browser.close()
def open(self):
""" 打开网页输入用户名密码并点击 :return: None """
self.browser.get(self.url)
username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
username.send_keys(self.username)
password.send_keys(self.password)
submit.click()
def get_position(self):
""" 获取验证码位置 :return: 验证码位置元组 """
try:
img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
except TimeoutException:
print('未出现验证码')
self.open()
time.sleep(2)
location = img.location
size = img.size
top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
return (top, bottom, left, right)
def get_screenshot(self):
""" 获取网页截图 :return: 截图对象 """
screenshot = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
return screenshot
def get_image(self, name='captcha.png'):
""" 获取验证码图片 :return: 图片对象 """
top, bottom, left, right = self.get_position()
print('验证码位置', top, bottom, left, right)
screenshot = self.get_screenshot()
captcha = screenshot.crop((left, top, right, bottom))
captcha.save(name)
return captcha
def main(self):
""" 批量获取验证码 :return: 图片对象 """
count = 0
while True:
self.open()
self.get_image(str(count) + '.png')
count += 1
if __name__ == '__main__':
crack = CrackWeiboSlide()
crack.main()复制代码
这里须要将USERNAME
和PASSWORD
修改成本身微博的用户名和密码。运行一段时间后,本地多了不少以数字命名的验证码,以下图所示。
这里咱们只须要挑选出不一样的24张验证码图片并命名保存。名称能够直接取做宫格的滑动的顺序,以下图所示。
咱们将图片命名为4132.png,表明滑动顺序为4-1-3-2。按照这样的规则,咱们将验证码整理为以下24张图,以下图所示。
如上24张图就是咱们的模板。接下来,识别过程只须要遍历模板进行匹配便可。
调用get_image()
方法,获得验证码图片对象。而后,对验证码图片对象进行模板匹配,定义以下所示的方法:
from os import listdir
def detect_image(self, image):
""" 匹配图片 :param image: 图片 :return: 拖动顺序 """
for template_name in listdir(TEMPLATES_FOLDER):
print('正在匹配', template_name)
template = Image.open(TEMPLATES_FOLDER + template_name)
if self.same_image(image, template):
# 返回顺序
numbers = [int(number) for number in list(template_name.split('.')[0])]
print('拖动顺序', numbers)
return numbers复制代码
TEMPLATES_FOLDER
就是模板所在的文件夹。这里经过listdir()
方法获取全部模板的文件名称,而后对其进行遍历,经过same_image()
方法对验证码和模板进行比对。若是匹配成功,那么就将匹配到的模板文件名转换为列表。如模板文件3124.png匹配到了,则返回结果为[3, 1, 2, 4]。
比对的方法实现以下所示:
def is_pixel_equal(self, image1, image2, x, y):
""" 判断两个像素是否相同 :param image1: 图片1 :param image2: 图片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """
# 取两个图片的像素点
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 20
if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
pixel1[2] - pixel2[2]) < threshold:
return True
else:
return False
def same_image(self, image, template):
""" 识别类似验证码 :param image: 待识别验证码 :param template: 模板 :return: """
# 类似度阈值
threshold = 0.99
count = 0
for x in range(image.width):
for y in range(image.height):
# 判断像素是否相同
if self.is_pixel_equal(image, template, x, y):
count += 1
result = float(count) / (image.width * image.height)
if result > threshold:
print('成功匹配')
return True
return False复制代码
在这里比对图片也利用了遍历像素的方法。same_image()
方法接收两个参数,image
为待检测的验证码图片对象,template
是模板对象。因为两者大小是彻底一致的,因此在这里咱们遍历了图片的全部像素点。比对两者同一位置的像素点,若是像素点相同,计数就加1。最后计算相同的像素点占总像素的比例。若是该比例超过必定阈值,那就断定图片彻底相同,则匹配成功。这里阈值设定为0.99,即若是两者有0.99以上的类似比,则表明匹配成功。
经过上面的方法,依次匹配24个模板。若是验证码图片正常,咱们总能找到一个匹配的模板,这样就能够获得宫格的滑动顺序了。
接下来,根据滑动顺序拖动鼠标,链接各个宫格,方法实现以下所示:
def move(self, numbers):
""" 根据顺序拖动 :param numbers: :return: """
# 得到四个按点
circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
dx = dy = 0
for index in range(4):
circle = circles[numbers[index] - 1]
# 若是是第一次循环
if index == 0:
# 点击第一个按点
ActionChains(self.browser) \
.move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) \
.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']复制代码
这里方法接收的参数就是宫格的点按顺序,如[3,1,2,4]。首先咱们利用find_elements_by_css_selector()
方法获取到4个宫格元素,它是一个列表形式,每一个元素表明一个宫格。接下来遍历宫格的点按顺序,作一系列对应操做。
其中若是当前遍历的是第一个宫格,那就直接鼠标点击并保持动做,不然移动到下一个宫格。若是当前遍历的是最后一个宫格,那就松开鼠标,若是不是最后一个宫格,则计算移动到下一个宫格的偏移量。
经过4次循环,咱们即可以成功操做浏览器完成宫格验证码的拖拽填充,松开鼠标以后便可识别成功。运行效果以下图所示。
鼠标会慢慢从起始位置移动到终止位置。最后一个宫格松开以后,验证码的识别便完成了。
至此,微博宫格验证码的识别就所有完成。验证码窗口会自动关闭。直接点击登陆按钮便可登陆微博。
本节代码地址为:https://github.com/Python3WebSpider/CrackWeiboSlide。
本节介绍了一种经常使用的模板匹配识别图片的方式,模拟了鼠标拖拽动做来实现验证码的识别。若是遇到相似的验证码,咱们能够采用一样的思路进行识别。
本资源首发于崔庆才的我的博客静觅: Python3网络爬虫开发实战教程 | 静觅
如想了解更多爬虫资讯,请关注个人我的微信公众号:进击的Coder
weixin.qq.com/r/5zsjOyvEZ… (二维码自动识别)