本节咱们来介绍一下新浪微博宫格验证码的识别,此验证码是一种新型交互式验证码,每一个宫格之间会有一条指示连线,指示了咱们应该的滑动轨迹,咱们须要按照滑动轨迹依次从起始宫格一直滑动到终止宫格才能够完成验证,如图 8-24 所示:css
图 8-24 验证码示例web
鼠标滑动后的轨迹会以×××的连线来标识,如图 8-25 所示:算法
图 8-25 滑动过程浏览器
咱们能够访问新浪微博移动版登陆页面就能够看到如上验证码,连接为:https://passport.weibo.cn/signin/login,固然也不是每次都会出现验证码,通常当频繁登陆或者帐号存在安全风险的时候会出现。安全
接下来咱们就来试着识别一下此类验证码。学习过程当中有不懂的能够加入咱们的学习交流秋秋圈784中间758后面214,与你分享Python企业当下人才需求及怎么从零基础学习Python,和学习什么内容。相关学习视频资料、开发工具都有分享网络
本节咱们的目标是用程序来识别并经过微博宫格验证码的验证。ide
本次咱们使用的 Python 库是 Selenium,使用的浏览器为 Chrome,在此以前请确保已经正确安装好了 Selenium 库、Chrome浏览器并配置好了 ChromeDriver,相关流程能够参考第一章的说明。工具
要识别首先要从探寻规律入手,那么首先咱们找到的规律就是此验证码的四个宫格必定是有连线通过的,并且每一条连线上都会相应的指示箭头,连线的形状多样,如C型、Z型、X型等等,如图 8-2六、8-2七、8-28 所示:学习
图 8-26 C 型开发工具
图 8-27 Z 型
图 8-28 X 型
而同时咱们发现同一种类型它的连线轨迹是相同的,惟一不一样的就是连线的方向,如图 8-2九、8-30 所示:
图 8-29 反向连线
图 8-30 正向连线
这两种验证码的连线轨迹是相同的,可是因为连线上面的指示箭头不一样致使滑动的宫格顺序就有所不一样。
因此要彻底识别滑动宫格顺序的话就须要具体识别出箭头的朝向,而观察一下整个验证码箭头朝向一共可能有 8 种,并且会出如今不一样的位置,若是要写一个箭头方向识别算法的话须要都考虑到不一样箭头所在的位置,咱们须要找出各个位置的箭头的像素点坐标,同时识别算法还须要计算其像素点变化规律,这个工做量就变得比较大。
这时咱们能够考虑用模板匹配的方法,模板匹配的意思就是将一些识别目标提早保存下来并作好标记,称做模板,在这里咱们就能够获取验证码图片并作好拖动顺序的标记当作模板。在匹配的时候来对比要新识别的目标和每个模板哪一个是匹配的,若是找到匹配的模板,则被匹配到的模板就和新识别的目标是相同的,这样就成功识别出了要新识别的目标了。模板匹配在图像识别中也是很是经常使用的一种方法,实现简单并且易用性好。
模板匹配方法若是要效果好的话,咱们必需要收集到足够多的模板才能够,而对于微博宫格验证码来讲,宫格就 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 修改成本身微博的用户名密码,运行一段时间后即可以发如今本地多了不少以数字命名的验证码,如图 8-31 所示:
图 8-31 获取结果
在这里咱们只须要挑选出不一样的24张验证码图片并命名保存就行了,名称能够直接取做宫格的滑动的顺序,如某张验证码图片如图 8-32 所示:
图 8-32 验证码示例
咱们将其命名为 4132.png 便可,也就是表明滑动顺序为 4-1-3-2,按照这样的规则,咱们将验证码整理为以下 24 张图,如图 8-33 所示:
图 8-33 整理结果
如上的 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 Python资源分享qun 784758214 ,内有安装包,PDF,学习视频,这里是Python学习者的汇集地,零基础,进阶,都欢迎
在这里 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'] Python资源分享qun 784758214 ,内有安装包,PDF,学习视频,这里是Python学习者的汇集地,零基础,进阶,都欢迎
在这里方法接收的参数就是宫格的点按顺序,如 [3, 1, 2, 4]。首先咱们利用 find_elements_by_css_selector() 方法获取到四个宫格元素,是一个列表形式,每一个元素表明一个宫格,接下来咱们遍历了宫格的点按顺序,再作一系列对应操做。
其中若是是第一个宫格,那就直接鼠标点击并保持动做,不然移动到下一个宫格。若是是最后一个宫格,那就松开鼠标,不然计算移动到下一个宫格的偏移量。
经过四次循环,咱们即可以成功操做浏览器完成宫格验证码的拖拽填充,松开鼠标以后便可识别成功。
运行效果如图 8-34 所示:
图 8-34 运行效果
鼠标会慢慢的从起始位置移动到终止位置,最后一个宫格松开以后便完成了验证码的识别。
至此,微博宫格验证码的识别就所有完成了。
识别完成以后验证码窗口会自动关闭,接下来直接点击登陆按钮便可完成微博登陆。
本节咱们介绍了一种经常使用的模板匹配识别图片的方式来识别验证码,并模拟了鼠标拖拽动做来实现验证码的识别。若是遇到相似的验证码,能够采用一样的思路进行识别。