极验最初的滑块验证码是两张图,首先出现的是原图,点一下出现凹槽,然后拖动滑块进去,注意拖拽速度就可以破解成功。
原理: 分别遍历扫描原图和有凹槽的图片像素,进行对比,像素不一致的位置就是凹槽,拖动滑块到凹槽就可以破解。
- 模拟点击验证按钮
- 识别滑块缺口位置
- 拖动滑块到缺口位置
现在极验登录升级了验证码,首先出来的就是凹槽,这样无法获取原图,就没有办法进行对比。登录地址为: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 ECclass 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 captchadef get_slider(self):"""获取滑块:return:"""slide = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))return slidedef 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 Falsedef 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 = ireturn leftreturn leftdef slide_path(self, gap):"""滑动路径:param gap::return: 滑动路径"""# 移动轨迹track = []# 当前位移current = 0# 减速阈值mid = gap * 4 / 5# 计算间隔t = 0.2# 初速度v = 0while current < gap:if current < mid:# 加速度为正2a = 2else:# 加速度为负3a = -3# 初速度v0v0 = v# 当前速度v = v0 + atv = v0 + a * t# 移动距离x = v0t + 1/2 * a * t^2move = v0 * t + 1 / 2 * a * t * t# 当前位移current += move# 加入轨迹track.append(round(move))return trackdef 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()# 移除滑块,否则滑块会对色差造成影响,无法获取gapself.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()