Python笔记:滑动验证码的识别算法

概述

用程序识别极验滑动验证码的验证,包括分析识别思路、识别缺口位置、生成滑块拖动路径、模拟实现滑块拼合通过验证等步骤。

准备工作

  • 使用Python库是Selenium,浏览器为Chrome。请确保已安装Selenium库和ChromeDriver浏览器驱动。
  • 关于极验滑动验证码:
    • 极验滑动验证码官网为:http://www.geetest.com/
    • 验证方式为拖动滑块拼合图像,若图像完全拼合,则验证成功,否则需要重新验证。
  • 链接地址:https://account.geetest.com/login,打开极验的管理后台登录页面,完成自动化登录操作

实现步骤

1 ) 初始化

  • 初始化链接地址、创建模拟浏览器对象、设置登录账户和密码等信息。

    EMAIL = '登录账户'
    PASSWORD = '登录密码'
    
    class CrackGeetest():
        def __init__(self):
            self.url = 'https://account.geetest.com/login'
            self.browser = webdriver.Chrome()
            #设置显示等待时间
            self.wait = WebDriverWait(self.browser, 20)
            self.email = EMAIL
            self.password = PASSWORD
    
        def crack():
            pass
    
    # 程序主入口
    if __name__ == '__main__':
        crack = CrackGeetest()
        crack.crack()
    

2 ) 模拟登录填写,点开滑块验证

  • 在实例化CrackGeetest对象后调用crack()方法开始模拟登录验证…

  • 调用open()方法,打开登录界面,获取账户和密码输入框节点,完成账户和密码的输入。

  • 调用get_geetest_button()方法获取滑动验证按钮,并点击。

    class CrackGeetest():
    #...
    
    def get_geetest_button(self):
        ''' 获取初始验证按钮,return:按钮对象 '''
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button
    
    def open(self):
        ''' 打开网页输入用户名密码, return: None '''
        self.browser.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 crack(self):
        # 输入用户名密码
        self.open()
        # 点击验证按钮
        button = self.get_geetest_button()
        button.click()
        #...
    #...
    

3 ) 获取并储存有无缺口的两张图片

  • 首先获取无缺口的验证图片,并保存到本地
  • 获取滑块对象,并执行点击,让浏览器中显示有缺口图片
  • 获取有缺口的验证图片,并保存到本地
    def get_position(self):
        ''' 获取验证码位置, 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,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']
        return (top, bottom, left, right)
    
    def get_screenshot(self):
        ''' 获取网页截图, return: 截图对象 '''
        #浏览器截屏
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot
    
    def get_geetest_image(self, name='captcha.png'):
        ''' 获取验证码图片, return: 图片对象 '''
        top, bottom, left, right = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        #从网页截屏图片中裁剪处理验证图片
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha
    
    def get_slider(self):
        ''' 获取滑块, return: 滑块对象 '''
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider
    
    def crack(self):
        #...
    
        # 获取验证码图片
        image1 = self.get_geetest_image('captcha1.png')
        # 点按呼出缺口
        slider = self.get_slider()
        slider.click()
        # 获取带缺口的验证码图片
        image2 = self.get_geetest_image('captcha2.png')
    
        #...
    

4 ) 获取缺口位置

  • 对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离

    BORDER = 6
    INIT_LEFT = 60
    
    class CrackGeetest():  
        def get_gap(self, image1, image2):
            ''' 获取缺口偏移量, 参数:image1不带缺口图片、image2带缺口图片。返回偏移量 '''
            left = 65
            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):
                        left = i
                        return left
            return left
    
        def is_pixel_equal(self, image1, image2, x, y):
            '''
            判断两个像素是否相同
            :param image1: 图片1
            :param image2: 图片2
            :param x: 位置x
            :param y: 位置y
            :return: 像素是否相同
            '''
            # 取两个图片的像素点(R、G、B)
            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
            else:
                return False
    
        def crack(self):
            #...
    
            # 获取缺口位置
            gap = self.get_gap(image1, image2)
            print('缺口位置', gap)
            # 减去缺口位移
            gap -= BORDER
    

5 ) 获取移动轨迹

  • 模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹
    def get_track(self, distance):
      '''
      根据偏移量获取移动轨迹
      :param distance: 偏移量
      :return: 移动轨迹
      '''
      # 移动轨迹
      track = []
      # 当前位移
      current = 0
      # 减速阈值
      mid = distance * 4 / 5
      # 计算间隔
      t = 0.2
      # 初速度
      v = 0
    
      while current < distance:
          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 crack(self):
      #...
    
      # 获取移动轨迹
      track = self.get_track(gap)
      print('滑动轨迹', track)
    

6 ) 按照轨迹拖动,完全验证

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 crack(self):
    #...

    # 拖动滑块
    self.move_to_gap(slider, track)

    success = self.wait.until(
        EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
    print(success)

7 ) 完成登录

def login(self):
    ''' 执行登录 return: None '''
    submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
    submit.click()
    time.sleep(10)
    print('登录成功')

def crack(self):
    #...

    # 失败后重试
    if not success:
        self.crack()
    else:
        self.login()

完整代码

  • 请点击这里查看

你可能感兴趣的:(Python,Data,Structure,and,Algorithms,滑动验证码识别,Selenium,图片算法)