极验滑动验证码概述

该文章主要讲述了如何使用Python和Selenium模拟人类操作,破解验证码。首先,通过Selenium模拟操作获取不带缺口的图片,然后获取带缺口的图片,对比两张图片像素点的RGB值,得到缺口位置,最后模拟拖动滑块,通过匀加速匀减速运动来完成验证。 

验证码获取网站为 http://www.geetest.com/。 

极验滑动验证码比图形验证码的识别难度更大,其原理是将图片拖动到缺口处,然后拼合图像进行验证。该验证码会生成三个加密参数,通过表单提交到后台进行验证。极验验证码还采用了机器学习的方法来识别是否为恶意程序进行识别,并采用防模拟、防伪造和防暴力等方式进行保护,只需要 0.4 秒就可以完成验证,防止资源滥用和盗取。

通常情况下,只要我们的程序不是恶意的,并且遵守爬虫协议,就可以使用该验证码。但是请务必不要给服务器造成负担。

极验滑动验证码的识别思路可以通过模拟人类的操作方式来完成验证。这个过程分为三个步骤:模拟点击验证按钮、识别滑动缺口的位置和模拟拖动滑块。其中,第一步比较简单,第二步则需要使用图像处理技术,通过对比两张图片的像素差异来确定缺口的位置。第三步则需要模拟人类的移动轨迹,而人类移动轨迹是先加速后减速的,因此需要采用一些特殊方法来模拟这个过程,以通过验证。

Ps:动手能力弱的小伙伴可以直接访问www.ttocr.com进行识别

具体的实现过程可以参考以下 Python 代码:

# 注册的用户名和密码

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 get_geetest_button(self):

        """

        获取初始验证按钮

        返回值:按钮对象

        """

        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))

        return button

在调用位置即可模拟点击:

# 点击验证按钮

button = self.get_geetest_button()

button.click()

接下来识别缺口的位置,首先获取两张图片,进行对比,不一样的位置就是缺口。
获取不带缺口的图片。用selenium选取图片元素得到整个网页的截图然后裁剪即可,代码如下:

def get_screenshot(self):

        """

        获取网页截图

        :return: 截图对象

        """

        screenshot = self.browser.get_screenshot_as_png()

        screenshot = Image.open(BytesIO(screenshot))

        return screenshot

    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_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

用click()即可触发点击,如下:

# 点按呼出缺口

slider = self.get_slider()

slider.click()

接下来就是通过对比图片获取缺口,通过遍历图片上的每个坐标点,获取两张图片对应像素点的RGB数据。如果在一定范围内,那就代表两个像素相同,继续对比下一个像素点。如果差距超过一定范围,则代表像素点不同,当前位置就是缺口位置。通过设置一个阈值threshold,来进行判断,代码如下:

def is_pixel_equal(self, image1, image2, x, y):

        """

        判断两个像素是否相同

        :param image1: 图片1

        :param image2: 图片2

        :param x: 位置x

        :param y: 位置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

        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.is_pixel_equal(image1, image2, i, j):

                    left = i

                    return left

        return left

5.模拟拖动

模拟拖动并不复杂,但是里面的细节比较多。用相关的函数将滑块拖动到对应的位置即可。但是要是匀速拖动,会必然识别出是程序,非人类操作,因为人类无法做到完全匀速拖动,会识别出是机器操作,使得验证码失败。
通过不同的方法检测,我们发现把前段滑块做匀加速运动,后段滑块做匀减速运动,即可完成验证。
这里加速度用a来表示,当前速度用v表示,初速度用vo表示,位移用x表示,时间用t表示。
代码如下:

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 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()

6:全部代码

import time

from io import BytesIO

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

EMAIL = 'cqc@cuiqingcai.com'

PASSWORD = ''

BORDER = 6

INIT_LEFT = 60

# 注册的用户名和密码

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 __del__(self):

        self.browser.close()

    

    def get_geetest_button(self):

        """

        获取初始验证按钮

        返回值:按钮对象

        """

        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))

        return button

    

    def get_screenshot(self):

        """

        获取网页截图

        :return: 截图对象

        """

        screenshot = self.browser.get_screenshot_as_png()

        screenshot = Image.open(BytesIO(screenshot))

        return screenshot

    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_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 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 is_pixel_equal(self, image1, image2, x, y):

        """

        判断两个像素是否相同

        :param image1: 图片1

        :param image2: 图片2

        :param x: 位置x

        :param y: 位置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

        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.is_pixel_equal(image1, image2, i, j):

                    left = i

                    return left

        return left

    

    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 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 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):

        # 输入用户名密码

        self.open()

        # 点击验证按钮

        button = self.get_geetest_button()

        button.click()

        # 获取验证码图片

        image1 = self.get_geetest_image('captcha1.png')

        # 点按呼出缺口

        slider = self.get_slider()

        slider.click()

        # 获取带缺口的验证码图片

        image2 = self.get_geetest_image('captcha2.png')

        # 获取缺口位置

        gap = self.get_gap(image1, image2)

        print('缺口位置', gap)

        # 减去缺口位移

        gap -= BORDER

        # 获取移动轨迹

        track = self.get_track(gap)

        print('滑动轨迹', track)

        # 拖动滑块

        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)

        

        # 失败后重试

        if not success:

            self.crack()

        else:

            self.login()

if __name__ == '__main__':

    crack = CrackGeetest()

    crack.crack()

这种方法对于不同的极验滑动验证码来说都适用,关键在于识别的思路,如何识别缺口位置,如何生成运动轨迹等。之后遇到类似的验证码,都可以这样进行识别。

更多学习资源交流:Q1436423940或访问www.ttocr.com
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值