滑动验证码 转载:https://mp.weixin.qq.com/s/4cvr3mqKD0jkZNVDky4y7w
今天的主角是滑动验证码,现在有很多网站使用了极验验证码来智能反爬虫,其中有一种是滑动验证码,具体来说就是拖动滑块来拼合图像,若图像完全拼合,则验证成功。下图是B站的登录验证码,便是采用了极验的滑动验证码,一起来看看如何破解吧!
先打开B站的登录页面,https://passport.bilibili.com/login,输入账号密码之后点击登录便会弹出上述的滑动验证码。
将任务拆分有助于我们解决问题,解决这个滑动验证码我们可以分为这么两个步骤:
1)识别图片缺口
2)模拟拖动滑块
那么就一步一步来吧~
图片缺口识别
可以看到的是缺口图的颜色与周围有显著不同,我们只需要拿到不含缺口的原图进行对比就能够找到这个缺口的坐标。
打开开发者工具,看源代码上是否包含两张图片的url链接,这样我们可以直接下载下来分析对比。不巧的是,我们没有发现它的踪迹。
但当我们将上图源代码中的类别为"geetest_canvas_fullbg geetest_fade geetest_absolute"的style设置为空,即可显示没有缺口的原图。
知道如何获得这两张图片之后,我们可以通过get_geetest_image函数来获取滑动验证码的图片,具体是用了 Selenium 工具选取图片元素,然后得到其所在位置以及大小,随后获取整个网页的截图,再将这个滑动验证码从截图中裁切出来。
def get_geetest_image(self,name,flag):
"""
获得验证码图片
"""
bottom,top,left,right=self.get_position(flag)
print("验证图片位置",bottom,top,left,right)
screenshot=self.get_screenshot()
captcha=screenshot.crop((left,bottom,right,top))
captcha.save(name)
return captcha
def get_position(self, flag):
"""
获取验证码图片位置
"""
img=self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "canvas.geetest_canvas_slice")))
time.sleep(2)
if flag:
# 执行js获取不带缺口的原图
self.browser.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].setAttribute("style", "")')
else:
# 执行js,把缺口复原
self.browser.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].setAttribute("style", "opacity: 1; display: none;")')
location=img.location
print("图片坐标为:{}".format(location))
size=img.size
print("图片大小为:{}".format(size))
bottom,top,left,right=location["y"],location["y"]+size["height"],location["x"],location["x"]+size["width"]
return (bottom,top,left,right)
我们通过selenium执行js代码,获取不带缺口的原图和我们最先见到的有缺口的图。
图片获取之后,来对比图片各个像素通道的差异来获取缺口的位置就行。我们宽泛的认为,像素相差在一定范围内视为相同,像素相差大于阈值视为发现缺口,便由此得到了缺口的坐标信息。
def get_gap(self, image1, image2):
"""
获取缺口位置,通过比较像素值
"""
for i in range(LEFT, image1.size[0]):
for j in range(image1.size[1]):
if not self.is_pixel_equal(image1,image2,i,j):
return i
return LEFT
def is_pixel_equal(self,image1,image2,x,y):
"""
像素值比较,若三个通道均为出现超过阈值的变化,返回True
"""
pixel1=image1.load()[x, y]
pixel2=image2.load()[x, y]
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
加上LEFT这个偏移量,是因为带缺口的图还附带了滑块,我们需要将滑块的长度范围舍弃,即在滑块的右侧开始像素的比较,这样我们就可以得到缺口的位置了。
模拟拖动滑块
要拖动滑块我们需要先得到滑块,通过简单的selenium操作即可。
def get_slider(self):
"""
获取滑块
"""
return self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "geetest_slider_button")))
这里需要用到简单的高中物理知识,为了让selenium模拟人的操作,我们需要将滑块先加速运动,再减速运动,这样会比较符合人的操作。我们采用匀加速和匀减速的方法,方便套用物理公式。模拟前4/5路程为匀加速路程,后1/5路程是匀减速路程,t是计算的时间间隔。
def get_track(self,distance):
"""
获取滑块移动轨迹的列表,distance是缺口的左侧横坐标值
"""
track=[]
current=0
mid=distance*0.8
t=0.2
v=0
while current<distance:
if current<mid:
a=2.5
else:
a=-3.5
v0=v
v=v0+a*t
move=v0*t+ 0.5*a*t*t
current+=move
track.append(round(move))
return track
然后,我们只需要按照计算得到的运动轨迹操控小滑块运动即可。下面是破解过程展示。