极验最初的滑块验证码是两张图,首先出现的是原图,点一下出现凹槽,然后拖动滑块进去,注意拖拽速度就可以破解成功。
原理: 分别遍历扫描原图和有凹槽的图片像素,进行对比,像素不一致的位置就是凹槽,拖动滑块到凹槽就可以破解。
- 模拟点击验证按钮
- 识别滑块缺口位置
- 拖动滑块到缺口位置
现在极验登录升级了验证码,首先出来的就是凹槽,这样无法获取原图,就没有办法进行对比。登录地址为:https://auth.geetest.com/login/
极验现在登录不仅有这种滑块,还有图案顺序验证的方式
我们简化看一个博客园的例子,它采用的也是极验的验证码,登录地址为:
https://account.cnblogs.com/signin
第一个点在于获取原图,如果获取到原图,就可以用上面的原理进行解决。在element调试分析如图一:
示例图二
对应的执行如下代码:
browser.execute_script(
"var x=document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0];"
"x.style.display='block';"
"x.style.opacity=1;"
)
图一与图二如果直接进行像素扫描,会由于小滑块的干扰,无法找到缺口,这也是一个点。示例图三:
执行如下代码去掉小滑块:
browser.execute_script(
"document.getElementsByClassName('geetest_canvas_slice geetest_absolute')[0].remove();"
)
然后就可以用利用上面原理的常规方法来解决了。
需要注意的是,在使用selenium的方法解决验证码图片的时候,截取的是window图,不能直接获取到验证码图片,要进行处理,获取到尺寸从屏幕截图当中抠出验证码图片。
如下:
def get_captcha_pic(name="captcha.png"):
"""
获取验证码图片
:param name:
:return: captcha图片对象
"""
browser.save_screenshot(name) # 截屏幕图
im = Image.open(name)
aa = (572, 193, 980, 452) # 获取验证码图片在屏幕图当中的位置,测量不知道比例,可以再一边测好了,直接使用,否则返回的验证码图片不正确
captcha = im.crop(aa) # todo 识别的时候存在问题
captcha.save(name) # 保存验证码图片
return captcha
两张图片进行对比扫描找缺口的时候,有一些小技巧,比如图三中的第一个箭头是阴影小滑块,要把干扰去掉
def pixel_is_equal(image1, image2, x, y):
"""
判断两张图片的像素是否相等,不相等即为缺口位置
:param image1:
:param image2:
:param x:
:param y:
:return:
"""
# 取两个图片的像素点
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 60 # 像素色差
if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
pixel1[2] - pixel2[2]) < threshold:
return True # 像素色差小于60,默认为没区别
else:
return False
过程中还有一些其它问题,可以留言讨论,完整代码如下:
# -*- coding: utf-8 -*-
import time
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BlogYuan(object):
def __init__(self):
self.browser = webdriver.Chrome()
self.wait = WebDriverWait(self.browser, 20)
def open(self):
self.browser.get('https://account.cnblogs.com/signin')
self.browser.implicitly_wait(3)
input_username = self.browser.find_element_by_id('LoginName')
input_username.send_keys('dawfawfaefag')
input_password = self.browser.find_element_by_id('Password')
input_password.send_keys('cawfafaf')
submitBtn = self.browser.find_element_by_id('submitBtn')
time.sleep(1)
submitBtn.click()
time.sleep(2) # 等待验证码加载
def get_captcha_pic(self, name="captcha.png"):
"""
获取验证码图片
:param name:
:return: captcha图片对象
"""
self.browser.save_screenshot(name) # 截屏幕图
im = Image.open(name)
aa = (572, 193, 980, 452) # 获取验证码图片在屏幕图当中的位置,测量不知道比例,可以再一边测好了
captcha = im.crop(aa) # todo 识别的时候存在问题
captcha.save(name) # 保存验证码图片
return captcha
def get_slider(self):
"""
获取滑块
:return:
"""
slide = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
return slide
def pixel_is_equal(self, image1, image2, x, y):
"""
判断两张图片的像素是否相等,不相等即为缺口位置
:param image1:
:param image2:
:param x:
:param y:
:return:
"""
# 取两个图片的像素点
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 60 # 像素色差
if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
pixel1[2] - pixel2[2]) < threshold:
return True # 像素色差小于60,默认为没区别
else:
return False
def get_gap(self, image1, image2):
"""
获取缺口位置
:param image1:完整图片
:param image2: 带缺口的图片
:return:
"""
left = 60 # 设置一个起始量,因为验证码一般不可能在左边,加快识别速度
for i in range(left, image1.size[0]):
for j in range(image1.size[1]):
if not self.pixel_is_equal(image1, image2, i, j):
left = i
return left
return left
def slide_path(self, gap):
"""
滑动路径
:param gap:
:return: 滑动路径
"""
# 移动轨迹
track = []
# 当前位移
current = 0
# 减速阈值
mid = gap * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < gap:
if current < mid:
# 加速度为正2
a = 2
else:
# 加速度为负3
a = -3
# 初速度v0
v0 = v
# 当前速度v = v0 + at
v = v0 + a * t
# 移动距离x = v0t + 1/2 * a * t^2
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
def move_to_gap(self, slider, track):
"""
拖动滑块到缺口处
:param slider: 滑块
:param track: 轨迹
:return:
"""
ActionChains(self.browser).click_and_hold(slider).perform()
for x in track:
ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5)
ActionChains(self.browser).release().perform()
def check_gap(self, gap):
"""
校准gap,可以自己调节,越精细,效果越好
:param gap:
:return: gap
"""
aa = round(gap / 12.5)
bb = {4: 38, 5: 41, 6: 42, 7: 43, 8: 46, 9: 52, 10: 54, 11: 59, 12: 62, 13: 65, 14: 68, 15: 71, 16: 74, 17: 79,
18: 84, 19: 86, 20: 87, 21: 92, 22: 93, 23: 95, 24: 98, 25: 101}
return gap - bb.get(int(aa))
def run(self):
self.open()
# 移除滑块,否则滑块会对色差造成影响,无法获取gap
self.browser.execute_script(
"document.getElementsByClassName('geetest_canvas_slice geetest_absolute')[0].remove();"
)
image1 = self.get_captcha_pic("image1.png") # 获取有缺口验证码图片,
# 显示无缺口图片
self.browser.execute_script(
"var x=document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0];"
"x.style.display='block';"
"x.style.opacity=1;"
)
image2 = self.get_captcha_pic("image2.png") # 获取无缺口验证码图片
# 获取缺口的位置
gap = self.get_gap(image1, image2)
# 减去缺口位移
gap -= 6
# 获取滑动路径
track = self.slide_path(self.check_gap(gap))
# 拖动滑块
slide = self.get_slider()
self.move_to_gap(slide, track)
time.sleep(1)
self.browser.close()
# 如果验证通过,执行,,,
if __name__ == "__main__":
gt = BlogYuan()
gt.run()