超级强大的破解极验滑动验证码--讲解非常详细
废话不多说,好久不写博客了,难得用心写了一下,怎么也给自己留个念想。。。虽然工作上暂时用不到。。。
其实网上已经好多了,我写的时候遇到的问题也参考了不少其他人的代码,嗯。。,那我这个就叫小白解读帖吧,讲解还算详细
网站说:
少于150字的随笔不允许发布到网站首页
尼玛,我下面的代码数量何止150个字啊,哎,开始凑字数,你们懂的,作为毫无营养的废话,我决定用颜色把他。。。靠,我说到哪了,嗯,反正也是废话,凑字而已。日啊
自己都看不见的文字,让我想起了m。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
我们开发的说,能写一行代码的绝对不写两行,但凡能携程这样的都是可以优化的,好比lamabada变大时,我觉得我打错了。好吧,好吧,反正我也看不见。。凑够字了吧。。
# -*- coding: utf-8 -*- """ @author:你们的龙哥 @2018-06-21 规则只匹配极验的官网,若使用破解验证码的规则,仍需要根据实际项目情况做调整 建议:本程序提供的是思路,无需修改本程序代码 写代码过程中遇到的问题: 1、chrome driver要下载,并配置好,过程忘记了,请百度。driver的版本一定要和chrome的版本对应。。。 2、chrome浏览器不在环境变量的,要把第32行(大概位置)注释解开,并填入自己电脑chrome的位置。要不就会报错,二进制文件找不到之类的。 3、reload(sys)要加,不然不能识别中文 4、location定位的时候,经常出白图,是因为save_screenshot()截屏的时候,保存的图片像素为实际像素的2倍,这个百度了一下,有人说是高分屏的原因,因为我用的mac? 5、第一次识别失败后,经常会报错,好像是时间超时之类的,我try掉了。想踩雷的,可以去掉所有的try自己玩一下。 6、本代码经过本机的极限测试,连续尝试40多次后依然可以正常运行,然后我就手动结束了测试。 """ from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.chrome.options import Options import logging import time import sys reload(sys) sys.setdefaultencoding('utf8') # 本段为指定chrome浏览器的位置,如果chrome安装的时候放到了环境变量中,应该用不到这样写,注释掉就可以 # 果然,把chrome移动到应用程序里面后,自动就配置到环境变量了(mac本) chrome_options = Options() # chrome_options.binary_location = r"/Users/qmp/Downloads/Google Chrome.app/Contents/MacOS/Google Chrome" class CrackGeetest(): """ 实现了正常登陆、错误重试、多次重试后刷新图片的功能。。。除非规则和页面结构变更,要不基本走不到多次刷新图片重试这一步 没有对crack()和fail_again()这两个类方法进行精简,是希望程序的运行步骤清晰明了。。。 """ def __init__(self,url,email,password,threshold=60,left=57,deviation=6): self.url = url self.driver = webdriver.Chrome(chrome_options=chrome_options) self.wait = WebDriverWait(self.driver,10) self.email = email self.password = password self.threshold = threshold #验证码图片对比中RGB的差值,可调 self.left = left #验证码图片的对比中的起始坐标,即拖动模块的右边线位置 self.deviation = deviation # 偏移量,这个值是多次测试得出的经验值 self.count = 1 def close_win(self): self.driver.close() def get_geetest_button(self): """ 点击按钮,弹出没有缺口的图片 :return: 返回按钮对象 """ button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_radar_tip'))) return button def get_slider(self): """ 获取滑块 :return: 滑块对象 """ slider = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'geetest_slider_button'))) return slider def get_snap(self): """ 对整个网页截图,保存图片,然后用PIL.Image拿到图片对象 :return: 图片对象 """ self.driver.save_screenshot('极验验证过程中产生的图片.png') page_snap_obj = Image.open('极验验证过程中产生的图片.png') return page_snap_obj def get_image(self,name='captcha.png'): """ 从网页的网站截图中,截取验证码图片 :return: 验证码图片对象 """ img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) # 保证图片刷新出来 location = img.location size = img.size top = location['y'] bottom = location['y'] + size['height'] left = location['x'] right = location['x'] + size['width'] page_snap_obj = self.get_snap() #这里强调一下:大概由于高分屏的原因,网页的截图是实际像素的2倍,所以验证码定位也要相应*2 # crop_imag_obj = page_snap_obj.crop((left,top,right,bottom)) crop_imag_obj = page_snap_obj.crop((2 * left, 2 * top, 2 * right, 2 * bottom)) size = 258, 159 crop_imag_obj.thumbnail(size) #实际生产环境下就不需要保存这张图片了 # crop_imag_obj.save(name) return crop_imag_obj def open(self): """ 打开网页输入用户名和密码 :return: """ self.driver.get(self.url) email = self.wait.until(EC.presence_of_element_located((By.ID, 'email'))) password = self.wait.until(EC.presence_of_element_located((By.ID, 'password'))) email.send_keys(self.email) password.send_keys(self.password) def get_distance(self,image1, image2): """ 拿到滑动验证码需要移动的距离 :param image1: 没有缺口的图片对象 :param image2: 带缺口的图片对象 :return: 需要移动的距离 """ i = 0 for i in range(self.left, image1.size[0]): for j in range(image1.size[1]): rgb1 = image1.load()[i, j] rgb2 = image2.load()[i, j] res1 = abs(rgb1[0] - rgb2[0]) res2 = abs(rgb1[1] - rgb2[1]) res3 = abs(rgb1[2] - rgb2[2]) if not (res1 < self.threshold and res2 < self.threshold and res3 < self.threshold): return i - self.deviation # 误差矫正 logging.debug('未识别出验证码中的不同位置,或图片定位出现异常') return i # 如果没有识别出不同位置,则象征性的滑动,以刷新下一张验证码 def get_tracks(self,distance): """ 拿到移动轨迹,模仿人的滑动行为,先匀加速后均减速 匀变速运动基本公式: ①:v=v0+at ②:s=v0t+½at² ③:v²-v0²=2as :param distance:需要移动的距离 :return:存放每0.3秒移动的距离 """ distance += 20 # 先滑过一点,最后再反着滑动回来 # 初速度 v = 0 # 单位时间为0.3s来统计轨迹,轨迹即0.3s内的位移 t = 0.3 # 位移/轨迹列表,列表内的一个元素代表0.3s的位移 forward_tracks = [] # 当前位移 current = 0 # 到达mid值开始减速 mid = distance * 4 / 5 while current < distance: if current < mid: # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细 a = 2 else: a = -3 # 初速度 v0 = v # 0.3秒时间内的位移 s = v0 * t + 0.5 * a * (t ** 2) # 当前的位置 current += s # 添加到轨迹列表,round()为保留一位小数且该小数要进行四舍五入 forward_tracks.append(round(s)) # 速度已经达到v,该速度作为下次的初速度 v = v0 + a * t # 反着滑动到准确位置 back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1] # 总共等于-20 return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks} def login(self): """ 登录 :return: None """ submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn'))) submit.click() print('登录成功') logging.debug('登陆成功') def crack(self): """ 程序运行流程。。。 :return: """ #步骤一:输入同户名密码 self.open() #步骤二:点击按钮,弹出没有缺口的图片 button = self.get_geetest_button() button.click() time.sleep(0.5) # 步骤三:拿到没有缺口的图片 image1 = self.get_image('captcha1.png') # 步骤四:点击托送按钮,弹出有缺口的图片 slider = self.get_slider() slider.click() #步骤五:拿到有缺口的图片 image2 = self.get_image('captcha2.png') #步骤六:对比两张图片的所有RBG像素点,得到不一样像素点间的差值,即要移动的距离 distance = self.get_distance(image1, image2) # 步骤七:模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹 tracks = self.get_tracks(distance) # 步骤八:按照轨迹拖动,完成验证 button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button'))) ActionChains(self.driver).click_and_hold(slider).perform() # 正常人类总是自信满满地开始正向滑动,自信地表现是疯狂加速 for track in tracks['forward_tracks']: ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform() # 结果傻逼了,正常的人类停顿了一下,回过神来发现,卧槽,滑过了,然后开始反向滑动 time.sleep(0.6) for back_track in tracks['back_tracks']: ActionChains(self.driver).move_by_offset(xoffset=back_track, yoffset=0).perform() # 小范围震荡一下,进一步迷惑极验后台,这一步可以极大地提高成功率 time.sleep(0.3) ActionChains(self.driver).move_by_offset(xoffset=3, yoffset=0).perform() # 先移动去一点 time.sleep(0.4) ActionChains(self.driver).move_by_offset(xoffset=-3, yoffset=0).perform() # 再退回来,模仿人的行为习惯 time.sleep(0.6) # 0.6秒后释放鼠标 ActionChains(self.driver).release().perform() try: success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip'), '验证成功')) self.login() time.sleep(5) self.close_win() except: # 位置没定位好,或者时间太长了,所以本次失败,进入下一轮 self.fail_again() def fail_again(self): """ 失败重试功能,加了好几个try,防止报错,使程序运行下去。在故意改变误差值(偏移量)的情况下,目前只尝试到40多次,遇到的报错都位于步骤三,程序暂无问题 :return: """ # 步骤二:点击刷新按钮,弹出没有缺口的图片,其实貌似也许大概,无需手动点刷新,失败后自动就刷新了,但我就这么写了。。。 try: button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_refresh_1'))) button.click() except Exception as e: print e print '尝试次数过多,刷新一次图片-步骤二' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步骤三:拿到没有缺口的图片 try: time.sleep(0.5) image1 = self.get_image('captcha1.png') except Exception as e: print e print '尝试次数过多,刷新一次图片-步骤三' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步骤四:点击托送按钮,弹出有缺口的图片 try: slider = self.get_slider() slider.click() except Exception as e: print e print '尝试次数过多,刷新一次图片-步骤四' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步骤五:拿到有缺口的图片 try: image2 = self.get_image('captcha2.png') except Exception as e: print e print '尝试次数过多,刷新一次图片-步骤五' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步骤六:对比两张图片的所有RBG像素点,得到不一样像素点间的差值,即要移动的距离 distance = self.get_distance(image1, image2) # 步骤七:模拟人的行为习惯(先军加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹 tracks = self.get_tracks(distance) # 步骤八:按照轨迹拖动,完全验证 button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button'))) ActionChains(self.driver).click_and_hold(slider).perform() # 正常人类总是自信满满地开始正向滑动,自信地表现是疯狂加速 for track in tracks['forward_tracks']: ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform() # 结果傻逼了,正常的人类停顿了一下,回过神来发现,卧槽,滑过了,然后开始反向滑动 time.sleep(0.6) for back_track in tracks['back_tracks']: ActionChains(self.driver).move_by_offset(xoffset=back_track, yoffset=0).perform() # 小范围震荡一下,进一步迷惑极验后台,这一步可以极大地提高成功率 time.sleep(0.3) ActionChains(self.driver).move_by_offset(xoffset=-4, yoffset=0).perform() # 先移动去一点 time.sleep(0.4) ActionChains(self.driver).move_by_offset(xoffset=4, yoffset=0).perform() # 再退回来,模仿人的行为习惯 time.sleep(0.6) # 0.5秒后释放鼠标 ActionChains(self.driver).release().perform() print self.count try: success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip'), '验证成功')) self.login() time.sleep(5) self.close_win() except: self.count+=1 if self.count<100: self.fail_again() else: self.close_win() if __name__ == '__main__': url = 'https://account.geetest.com/login' email = '马赛克了吗' password = '赛克了' threshold = 60 #RGB色差 left = 57 #起始位置 deviation = 7 #偏移量,误差 crack = CrackGeetest(url, email, password, threshold, left, deviation) crack.crack()
我去,赶紧马赛克账号密码,差点把自己暴露了