A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

我们在做爬虫程序的时候,会遇到有些网站必须登录进去才能访问该站点内有价值的信息,而且这类站点一般会在登录的时候增加验证码的环节来阻碍爬虫程序的高频访问,其中有一类验证码就是滑块验证码,而且这类验证码对于爬虫程序员来说往往是不好处理的,在本篇文章就会介绍如何来破解这类验证码,为大家提供一种思路。

首先来介绍一下我们的主角,也就是我们要破解验证码的站点——极验验证码,这家公司是一家验证码服务的平台,他们自己的注册和登录接口就使用了滑块验证码的功能,本篇文章就是介绍如何来破解该站点的登录界面的滑块验证码,url为 https://auth.geetest.com/login/  ,另外该站点还提供了机器学习的方法来识别拖动滑块的轨迹,从而将判定为非人为的操作排除在外,如果拖动滑块的速度过快或者过慢或者匀速等等,都会被判定为机器操作,这就为我们的爬虫工作增加了难度,但是机制是人为定的,我们只有模拟的够逼真就可以通过对方的防御机制。

现在目标已经明确,那么就撸起袖子开始干吧

我们先来分析一下思路,为了解决这个问题要用到什么方法和工具。在登录表单中,有两个输入框,分别是邮箱和密码,一开始并没有出现验证码图片,需要填入邮箱之后再点击按钮才会出现验证码,

我们不可能去手动的输入邮箱密码,得模拟浏览器动作,这时就需要用到一个工具叫Selenium,来帮助我们完成一系列在浏览器上的操作,来简单介绍一下Selenium,这个框架是自动化测试的一款工具,是用来模拟人在浏览器上的所以操作,在爬虫方面应用也很广泛,可以帮助我解决一些难度较大的问题,但是效率相对较低;需要的工具已经知道了,但是还需要安装才行,在ubuntu上直接 pip install selenium(或者 pip3 install selenium)确保安装在我们工作的虚拟环境下,验证是否安装成功可以进入python命令行交互模式,输入import selenium,如果没有报错就是安装成功(其他系统的安装方式请自行百度谷歌一下),做到这里还并没有完成我们的需要,因为我们还需要浏览器来配合selenium的工作,接下来介绍针对于Chrome浏览器的ChromeDriver的安装配置方式,假设我们的电脑上都已经安装了Chrome浏览器,此时还需要安装ChromeDriver来驱动浏览器完成相应的操作,首先点击Chrome菜单->帮助->关于Google Chrome,即可查看Chrome的版本号, 可以看到我系统上的版本号是50,这时就需要根据版本号来找对应版本的驱动,可以参考一篇文章https://blog.csdn.net/huilan_same/article/details/51896672 查阅该文章可以看到对应Chrome版本为50的ChromeDriver版本为2.21 然后我们就可以在 https://chromedriver.storage.googleapis.com/index.html 下载对应版本了, 完成这一步之后将可执行文件配置到环境变量或者属于环境变量的目录里,进入shell,切换到chromedriver所在目录中执行命令:sudo cp chromedriver /usr/bin  ,也可以选择将chromedriver加入到环境变量,完成这些操作之后可以在命令行输入 sudo chromedriver 指令来验证是否配置成功,如果成功会显示界面 ,接下来还要在程序中验证一下, 执行该代码之后如果显示错误信息PermissionError: [Errno 13] Permission denied,那么可以在方法中添加chromedriver所在路径作为参数, 执行之后会弹出一个空白浏览器页面,这样就代表全部配置完成。

工具已经有了,再来想想思路吧,我们可以用selenium模拟输入邮箱,再点击按钮呼出验证码的图片,然后使用selenium将验证码图片保存姑且命名为image1吧,此时的验证码图片是一个完整的原图,我们需要点击一下图片下方的按钮,在短暂的震动之后图片会出现缺口,

,将此图片保存命名为image2,然后对比image1与image2之间的像素差异来获取滑块与缺口之间的偏移量,剩下的就是拖动滑块向缺口运动,所有这些操作都是selenium模拟来完成,为保证程序的稳定性,中间会加适当的延时,关键的地方就是在滑动过程中的加速度调节,这一步操作不好会被对方识别,思路已经有了,好像也没那么难吧,但是坑还是很多的,作为爬虫工程师就要有逢山开路遇水架桥的精神,越是有挑战性就越是要把它给办了。此处应该有掌声……

接下来就要开始撸代码了。

我们用类来实现一系列功能代码,首先要配置需要的参数和实现初始化代码,我们的目的是获取验证码图片,只输入邮箱就可以了。

[Python] 纯文本查看 复制代码
from faker import Faker
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait


fk = Faker(locale='zh_CN')
email = fk.email()  # 伪造email
driver_path = r'/home/python/Desktop/spider/chromedriver'  # chromedriver所在路径


class GeetestLogin(object):

    def __init__(self):
        self.url = r'https://account.geetest.com/login'
        self.browser = webdriver.Chrome(driver_path)
        self.wait = WebDriverWait(self.browser, 10)  # 显示等待
        self.email = email
    
    def __del__(self):
        self.browser.close()

要获取验证码就需要输入邮箱,我们定义一个方法来封装这一步操作。

[Python] 纯文本查看 复制代码
def input_email(self):
        """
        打开网页输入邮箱
        :return: None
        """
        # 请求登录页面
        self.browser.get(self.url)
        # 显示等待10秒直到页面出现id为email的元素,并返回元素对象
        input_email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
        # 输入email
        input_email.send_keys(self.email)

第二步就是该点击验证码按钮来呼出验证码。

[Python] 纯文本查看 复制代码
def get_geetest_button(self):
        """
        获取初始验证按钮,点击呼出验证码
        :return:
        """
        # 定位可点击的class为geetest_radar_tip的元素并返回元素
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        button.click()  # 点击按钮

第三步就是获取验证码图片所在的位置和宽高,然后获取整个网页的截图,通过验证码图片的位置元素将图片裁切出来。

1. 获取验证码的位置返回位置元素

[Python] 纯文本查看 复制代码
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

2. 获取网页截图,返回截图对象

[Python] 纯文本查看 复制代码
def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

3. 获取验证码图片,保存图片,返回图片对象

[Python] 纯文本查看 复制代码
def get_geetest_image(self, name='image.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

第四步就是获取滑块按钮并点击滑块呼出缺口,然后返回滑块对象。

[Python] 纯文本查看 复制代码
def get_slider(self):
        """
        获取滑块
        :return: 滑块对象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        slider.click()
        return slider

第五步就是获取带缺口图片验证码对象并保存,这一步是重用以上第三步代码,已经封装,调用即可。

第六步就是对比不带缺口的验证码图片和带缺口的验证码图片从而获取偏移量,此步有需要注意的地方

从图中红线位置开始遍历,至于遍历的起始位置可以微调,可以放大网页,通过截图工具查看像素距离。

[Python] 纯文本查看 复制代码
def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        left = 70  # 可根据像素微调(60-80)
        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):
                    return i-6  # 减去大概6的偏移量(可微调)

    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


第七步是获取移动轨迹,返回移动轨迹的列表,这一步是有坑的,处理不好会被识别,或者滑块移动错位。

[Python] 纯文本查看 复制代码
def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 7
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:
            if current < mid:
                # 加速度为正2.2
                a = 2.2
            else:
                # 加速度为负3.5
                a = -3.5
            # 初速度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

第八步是拖动滑块开始移动。

[Python] 纯文本查看 复制代码
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()

操作步骤已完成,然后就是在类中按步骤调用各个方法。

[Python] 纯文本查看 复制代码
def crawl(self):

        # 输入邮箱
        self.input_email()
        # 点击按钮 呼出验证码
        self.get_geetest_button()
        # 获取验证码图片
        image1 = self.get_geetest_image(name='image1.png')
        # 点按呼出缺口
        slider = self.get_slider()

        # 获取带缺口的验证码图片
        image2 = self.get_geetest_image(name='image2.png')
        # 获取缺口位置
        gap = self.get_gap(image1, image2)
        print('gap:', gap)
        # 减去缺口位移
        # gap -= border
        # 获取移动轨迹
        track = self.get_track(gap)
        # 拖动滑块
        self.move_to_gap(slider, track)

        try:
            success = self.wait.until(
                EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
        except TimeoutException:
            success = None

        # print(success)

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


以上便完成了极验验证码登录界面的滑块验证码的破解,对于其他站点的滑块验证码的处理过程也可以参考此文章的流程。

完整代码,已分享到百度网盘,链接:https://pan.baidu.com/s/1oJklXeimpRWsi_jRzBNEEQ 密码:zec9




6 个回复

倒序浏览
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
smartjava 来自手机 初级黑马 2018-7-5 18:54:01
报纸
厉害好厉害
回复 使用道具 举报
smartjava 来自手机 初级黑马 2018-7-5 18:54:50
地板
Mr.TR 发表于 2018-7-5 17:07

回复 使用道具 举报
奈斯
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马