本文主要介绍如何利用 Python 实现对图形验证码和滑动验证码的识别
图形验证码的识别
图形验证码是出现最早,也是最容易识别的一类验证码,这类验证码很常见,学校教务系统登录界面就有类似的验证码
通过审查元素可以发现该验证码的链接为 http://219.231.36.52/CheckCode.aspx
,访问这个链接就能得到一个验证码(前提是先连上学校的 vpn ),先将验证码下载并保存起来以供测试识别之用
接下来新建一个项目,将验证码图片放到项目文件夹下,首先用 PIL 库对验证码图片进行二值化处理
1 | from PIL import Image |
处理后的验证码图片如下:
换一张背景复杂一点的图片处理效果可以看得更明显一点
这种做法的原理是首先利用 convert('L')
将图像转换成灰度图像,然后生成一个256位的列表( table )来对应灰色的256个色阶。列表在生成的时候对应着一个阈值 (threshold) ,列表内序号小于阈值的将被赋为0,大于等于阈值的赋为1。之后利用 point( table,'1')
对灰度图片进行映射,后面的 1 表示的是黑白模式。这样图片中灰度色阶小于阈值的像素点将被填充成黑色,其他的像素点将被填充成白色。通过这种操作可以将验证码的文字部分和背景区分开。
然后就可以直接调用 tesserocr 库对处理后的验证码图片进行识别。这种方法可以应对一般的图形验证码,如果要提升识别准确度还需要对验证码图片进行切割对比,最好的方法是运用深度学习自己训练一个数据集,这个以后慢慢学。
滑动验证码
快一个星期过去了,终于可以来补完剩下的滑动验证码部分了,来自菜鸡的悲愤 X999
先来看看效果
回到话题
滑动验证码是一种新式验证码,因为其操作简单人机辨识度高,在很短的时间内就流行起来,其中比较出名的就是极验
GeeTest
由于滑动验证码的加密参数太复杂,所以通过构造加密参数进行破解的难度太大,性价比太低,直接忽略。这时候就应该祭出神器 selenium 了。具体解决思路是点击验证按钮弹出滑动验证窗口然后通过验证码图片找出滑块和其对应的缺口位置,最通过 selenium 拖动滑块到达指定位置即可
在这里,选取魅族官网注册界面的滑动验证码进行尝试,其页面内容如下:
初始化webdriver
1 | from selenium import webdriver |
模拟点击验证按钮
初期方案是利用审查元素得到验证按钮的 css 选择器,然后显式等待直到按钮可以点击后使用 webdriver 的 click()
方法,但在点击之后只会出现点触验证码而不是期望的滑动验证码,重新分析了一下页面发现,验证按钮上面有一个圆圈会一直追寻鼠标的的方向旋转,据此推测验证按钮内附加了监视鼠标移动轨迹的方法,于是引入 ActionChains
,先将鼠标移动到按钮的上方再进行点击
1 | from selenium import webdriver |
使用此方法后点击验证按钮后一般就会直接显示验证成功,后面啥都没了,既没有点触验证码也没有我想要的滑动验证码,这验证也太草率了点吧。没办法,只好重复运行程序,总会有那么几次会出现滑动验证码的(我感觉我写这个程序的绝大部分时间都是在重启程序然后等待响应…)
当出现滑动验证码时就要开始主要操作也就是验证码图片滑块缺口识别以及滑块的操作了
滑块缺口识别
首先来一张滑动验证码的图片
这是一张 325 * 200 的图片,图片上的缺口是一个半透明的阴影蒙版,现在要做的工作就是将这个缺口的位置识别出来。看了看网上的方法,都是将原图片与出现缺口的图片进行比对,由于缺口位置的像素相较于原图片会有较大差异,从而可以确定出缺口位置,他们获取原图片的方法有两种,但都是老版验证码的。一种是将点击开始之前的正常验证码图片保存起来,另一种是将请求到的乱码图片根据网页元素的数据重新组合成正常图片。乱码图片就是这样的
但现在两种方法都没法用了,点击开始操作连带着原图片一起被取消了,拼合乱码图片网页数据也被放到了 js 文件里,我也找不到请求链接。网上前辈用过的方法全被封堵了,还真是前人砍树,后人中暑啊。没有办法只好不用原图片对着验证码图片硬杠了。
以下是我的解决方法:
首先将验证码图片转化为灰度模式然后保存,然后使用 numpy
打开,之所以使用灰度模式是因为普通 RGB 模式的图片使用 numpy
打开形成的是一个三维向量组分析起来很麻烦,而灰度图片打开后只是一个二维向量组,里面的每一个数字都对应着图片上响应像素点的灰阶,这样看起来更直观。
然后按列遍历图片上的像素,我是想通过这个找到滑块缺口最左边的那条直线。
通过观察可以发现,这条直线由于阴影的存在,其灰阶肯定比左侧部位高,又由于他是一条视觉上的直线所有其上下相邻的像素灰度值必然在一定范围之内。然后把图扔进 ps 里可以看到点的灰度,至于灰度和灰阶的换算这就要靠自己摸索了。
由于缺口不会出现在滑块内,所以遍历时直接从第 滑块的尾部大概60列遍历就行了,图片上的黑字会对查找边缘造成影响,最好在确定遍历范围的时候将上面那行大字去掉,小字也有干扰但不能去,因为滑块有时会出现在那里。当有连续的像素点符合这上述条件的时候用列表存下其横坐标,代码如下:
1 |
|
在上述条件下,普通图片一般会直接找到缺口左端边缘,干扰大的可能会有两到三个错误位置,不过问题不大。之后利用相似的手段可以得到滑块左端边缘的位置,二者相减后就能得到拖动距离。需要注意的是,由于有些缺口左侧并不是一条完整的直线还可能会有半圆之类的附加物,例如 ps 中的那张图,所以连续像素点的要求不能设得太高,否则可能会找不到边缘。
滑块拖动
由于极验对滑块的拖动轨迹进行了人工智能识别,所以拖动时不能直接拖而是要尽可能切合人类操作的特征。所以在此使用了匀变速运动的拖动轨迹,进行先加速后减速运动。利用方程 s=v0*t+vt^2/2
计算出每个单位时间 t 内应该运动的距离然后添加到列表内,通过遍历列表拖动距离来实现变速运动,这种操作也是很奇妙。
1 | def get_track(self,distance): |
关于上面代码中的参数 1.26 ,这是因为拖动按钮的位移和拼图滑块的位移并不是完全一样的,我也是被这个给坑了好久,最后是通过每拖动 1 像素截一次图然后放到 ps 内查看滑块移动距离的方法才找到了两者之间的关系,每次略有差异但大致都在 1.26 左右。巨坑!
保存页面内元素的操作如下:
1 | img = self.browser.find_element_by_css_selector('body > div.geetest_fullpage_click.geetest_float.geetest_wind.geetest_slide3 > div.geetest_fullpage_click_wrap > div.geetest_fullpage_click_box > div > div.geetest_wrap > div.geetest_widget > div > a > div.geetest_canvas_img.geetest_absolute > div > canvas.geetest_canvas_slice.geetest_absolute') |
截图会被保存在项目文件夹下
点触验证码目前没有很好的解决方法,主流做法就是将验证码图片发给打码平台,然后根据打码平台发回的坐标进行点击即可。
记录一下自己挖的坑
1.不要将 ActionChains 类初始化成 self.action = ActionChains(self.browser)
的形式然后其他地方直接拿去用,这样会造成一些稀奇古怪的 bug ,此外,ActionChains 类之后要加上 perform()
才能执行。
2.Firefox 拖动速度较慢,如果需要进行变速拖动需要给一个较大的加速度,否则它其实就是个匀速运动
3.在执行对列表内元素的操作时,请确保它不是个空列表
源码
1 | from selenium import webdriver |