@(博客)[selenium, python, 爬虫, 破解极验]
国家企业信用信息公式系统(上海)(http://www.sgs.gov.cn/notice)
破解滑动验证主要需要解决两个问题:1. 滑块移动距离;2. 模拟人操作
滑动距离其实就是比较两种图片的在同一个位置点的像素差,我们可以设置一个阈值,当坐标点相同的地方,像素差超过了预设的阈值,我们就认为这里存在缺口,也就是滑块需要移动的距离。
但可以看到,上图右边,中间有个滑块图案,它的存在会影响我们比较像素点的结果,所以需要去掉。在前端,样式中设置display:none;会使得滑块图案消失。
如何获取完整与有缺块的图片呢?
其实很简单,首先通过selenium提供的截屏接口(browser.get_screenshot_as_png()
),再用BytesIO封装,然后Image.open()
打开。
之所以要绕一圈,这是因为Image.open()
返回来的对象提供crop()
方法用于裁切图片,方便我们取出图片。
def crop_img(pos):
"""截图"""
screentshot = BytesIO(browser.get_screenshot_as_png())
img = Image.open(screentshot).crop(pos)
return img
注意:crop()方法接收一个元组,顺序依次为:left, upper, right, lower
由于crop()需要传入位置信息,所以我们还需要获取截取图片的指定位置
需要通过selenium提供的接口,找到包含整个图片的节点,然后利用location获取其坐标,返回来的值是这个节点的左上角坐标;通过利用size属性得到该节点的长宽,之后就可以计算出图片的位置信息
def get_img_posistion(node):
"""获取图片的位置"""
location = node.location
# print(location)
size = node.size
# 构建图片的左,上,右,下
left, top, right, buttom = ( location["x"], location["y"],
location["x"] + size["width"],
location["y"] + size["height"] )
print((left, top, right, buttom))
return (left, top, right, buttom)
确定横向坐标,垂直比较像素点,一旦像素点的差值超过阈值,那么此时的横向坐标值就是滑块需要移动的距离
def get_move_distance(imgwhole, imgpart):
"""比较两张图片
返回移动距离"""
for i in range(imgwhole.size[0]):
for j in range(imgwhole.size[1]):
flag = is_pixel_equal(imgwhole, imgpart, i, j)
if not flag:
return i
def is_pixel_equal(imgwhole, imgpart, x, y):
"""比较像素点是否相同
允许范围内返回true,反之false"""
# 获得像素值
pixel = imgwhole.load()[x, y]
pixe2 = imgpart.load()[x, y]
# 允许偏差 (阈值)
allowOffset = 60
if abs(pixel[0]-pixe2[0]) < allowOffset \
and abs(pixel[1]-pixe2[1]) < allowOffset \
and abs(pixel[2]-pixe2[2]) < allowOffset:
return True
else:
return False
注意:
imgwhole.load()[x, y]
返回(x,y)处的像素点那么get_move_distance()
函数返回的i值,就是我们需要的滑块移动距离了
尽管现在我们有了移动距离,但倘若直接利用Action创建一个动作链来移动滑块,实际上是会被极验后台认出是机器在操作,也就是说尽管重合了缺块部分,依然会验证失败
这个时候就需要模拟人的操作。一般来说,人在移动滑块的时候,是先加速后减速,然后停顿一下(一般这个时候是在查看是否重合),最后松开鼠标
def move_spider(moves):
"""移动滑块"""
slider = get_a_node(".gt_slider_knob.gt_show", "click")
# 摁住滑块不放
ActionChains(browser).click_and_hold(slider).perform()
# 移动滑块
for x in moves:
# print("i={0}".format(x))
ActionChains(browser).move_by_offset(x, randint(-1, 1)).perform()
# 停顿一下,松开鼠标
time.sleep(1)
ActionChains(browser).release().perform()
得出先加速后减速移动片段的代码如下:
def get_move_track(offset):
"""获得移动轨迹"""
current = 0
# 减速点
point = (3/4)*offset
v = 0
t = 0.2
moves = []
while current < offset:
if current < point:
a = 3
else:
a = -5
v0 = v
# 高中物理:利用加速度a与初速度v0计算出某一个时间点的速度v
v = v0 + a*t
# 高中物理:利用加速度a与初速度v0计算出移动距离
x = v0*t + (1/2)*a*t**2
current += x
moves.append(round(x))
return moves
这个时候get_move_track()
函数返回的moves其实就是一个距离片,把总的移动距离,细分成一段一段小距离。打印出来的效果如下:
因为经过round()
四舍五入,所以总和moves与实际需要移动的距离存在小差异,但只要在极验验证允许的差异内都是OK的
注意:ActionChains(browser).move_by_offset(x, randint(-1, 1)).perform()
这段代码,人手控制鼠标滑动的时候,是做不到完全水平线上移动的,上下抖动肯定必然。所以我用了randint(-1, 1)
来模拟人手上下抖动的过程
注意:整个过程中我发现包含图片的节点的class值是会改变的,所以此时需要做一些处理
try:
browser.execute_script(
"var node = document.getElementsByClassName('gt_slice gt_show');"
"node[0].style.display='none'")
except WebDriverException:
browser.execute_script(
"var node = document.getElementsByClassName('gt_bg gt_show');"
"node[0].style.display='block'")
总的来说,极验验证后台一直在收集大量的机器移动数据,所以模拟人操作的这段代码可能需要大量的实际测试才会得出理想效果。就我感觉来说,国家企业信用信息公式系统(上海)这个网址的极验验证似乎更好“哄骗”,不清楚是不是版本的问题,反正拉钩登陆时出现的极验验证会有些麻烦,我多次调试后也可以做到登陆,此刻代码有些混乱,等整理后会上传GitHub。
这一次的完整代码已上传GitHub