破解验证码(2)滑动验证码

前言

做爬虫时,难免会遇到需要通过验证码才能访问网址进行爬取内容,所以需要额外掌握破解验证码的技巧才行。
本文将讲解另一个验证码示例来进行演示(滑动验证码
上一篇写了: 破解验证码(1)数字英文验证码,可跳转到:https://blog.csdn.net/ytraister/article/details/106307610

实例

  • 以腾讯防水墙滑动验证码为例,进行破解 python + selenium + cv2
  • 验证码地址:https://open.captcha.qq.com/online.html
  • 使用OpenCV库,需要额外安装:pip install opencv-python
  • 效果如下图:
    在这里插入图片描述
》完整代码如下(含解析):

实际开发中,可以根据自己的需求进行改动

import numpy as np                              # 矩阵计算的函数库
import random
from selenium.webdriver import ActionChains     # 控制鼠标操作
import time
from selenium import webdriver
from PIL import Image                           # Python图像库
import os
from selenium.webdriver.support.ui import WebDriverWait
import cv2                                      # 强大的图像处理和计算机视觉库


class Login(object):
    def __init__(self):
        # 初始化方法:url、driver(浏览器驱动)
        # 如果是实际应用中,可在此处添加账号和密码
        self.url = "https://open.captcha.qq.com/online.html"
        self.driver = webdriver.Chrome()

    @staticmethod
    def show(name):
        # 调用窗口显示图片,以下3步骤是必须的
        cv2.imshow("Show", name)    # 参数1:窗口名称;参数2:需要显示的图片
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    @staticmethod
    def webdriverwait_send_keys(dri, element, value):
        """
        显示等待 输入
        :param dri:     浏览器驱动driver
        :param element: 通过selenium获取到的html元素
        :param value:   需要传入的值
        """
        WebDriverWait(dri, 10, 5).until(lambda dr: element).send_keys(value)

    @staticmethod
    def webdriverwait_click(dri, element):
        """
        显示等待 click
        :param dri:     浏览器驱动driver
        :param element: 通过selenium获取到的html元素
        """
        WebDriverWait(dri, 10, 5).until(lambda dr: element).click()
        # 当需要多次调用该显示等待函数时,可以使用匿名函数lambda来获取元素     lambda函数接收一个参数word(即上面的dr)。且在冒号和末尾圆括号之间的部分为函数的定义。

    @staticmethod
    def get_position(chunk, canves):
        """
        判断缺口位置
        :param chunk:   缺口图片(验证码中的大图)
        :param canves:  验证码中的拼图
        :return:        位置 x, y
        """
        # cv2.imread()用于读取图片文件;参数1:图片路径,参数2:读取图片的形式(1表示彩色图片[默认],0表示灰度图片,-1表示原来的格式)
        chunk = cv2.imread(chunk, 0)            # 读取大图(灰化)
        canves = cv2.imread(canves, 0)          # 读取拼图(灰化)

        # 二值化后的图片名称
        slide_puzzle = "slide_puzzle.jpg"
        slide_bg = "slide_bg.jpg"
        # 将二值化后的图片进行保存
        # cv2.imwrite()用于保存图片文件;参数1:保存的图像名称,参数2:需要保存的图像
        cv2.imwrite(slide_bg, chunk)            # 保存大图
        cv2.imwrite(slide_puzzle, canves)       # 保存拼图

        chunk = cv2.imread(slide_bg)            # 使用cv2.imread()读出来的是BGR数据格式
        # cv2.cvtColor(p1, p2) 是颜色空间转换函数    参数1:需要转换的图片,参数2:转换成何种格式
        # cv2.COLOR_BGR2RGB:将BGR格式转换成RGB格式      cv2.COLOR_BGR2GRAY:将BGR格式转换成灰度图片
        chunk = cv2.cvtColor(chunk, cv2.COLOR_BGR2GRAY)

        chunk = abs(255 - chunk)                # abs用于返回数字的绝对值
        cv2.imwrite(slide_bg, chunk)            # 保存大图  此时大图缺口部分的颜色比较浅,图像较容易识别到该缺口部分

        chunk = cv2.imread(slide_bg)            # 读取大图
        canves = cv2.imread(slide_puzzle)       # 读取拼图
        # 获取偏移量
        result = cv2.matchTemplate(chunk, canves, cv2.TM_CCOEFF_NORMED)  # cv2.matchTemplate()实现模板匹配(即用来在大图中找小图,也就是说在一副图像中寻找另外一张模板图像的位置)
        # 返回的result是一个矩阵,是每个点的匹配结果

        # Login.show(chunk)                     # 展示大图二值化后的效果
        # result.argmax()返回的是矩阵中最大数值的下标;    result.shape 返回的是(680, 390)即下载的图片对象的尺寸
        y, x = np.unravel_index(result.argmax(), result.shape)      # np.unravel_index()作用:获取一个/组 int类型的索引值在一个多维数组中的位置  计算出 大图 与 拼图 位移的距离
        return x                                # 实际滑动的距离就是x轴的距离,因此只需要x的值即可

    @staticmethod
    def get_track(distance):
        """
        模拟轨迹 假装是人在操作
            匀变速运动基本公式:
                ①v=v0+at            速度公式
                ②s=v0t+(1/2)at²     位移公式

        :param distance:    移动的距离
        :return:            track
        """
        v = 0                       # 初速度
        t = 0.2                     # 时间间隔0.2s
        tracks = []                 # 移动轨迹列表,列表内的每一个元素代表0.2s的位移
        current = 0                 # 当前的位移
        mid = distance * 4 / 5      # 到达mid值开始减速(前4/5段加速 后1/5段减速)
        distance += 10              # 先滑过一点,最后再反着滑动回来
        while current < distance:
            if current < mid:
                # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
                a = random.randint(28, 30)      # 加速运动
            else:
                a = -random.randint(12, 13)     # 减速运动

            v0 = v                              # 初速度
            s = v0 * t + 0.5 * a * (t ** 2)     # 0.2秒时间内的位移
            current += s                        # 当前的位置
            tracks.append(round(s))             # 添加到轨迹列表,round()为保留一位小数且该小数要进行四舍五入
            v = v0 + a * t                      # 速度已经达到v,该速度作为下次的初速度

        # 反着滑动到大概准确位置
        for i in range(4):
            tracks.append(-random.randint(3, 4))
        return tracks

    @staticmethod
    def urllib_download(imgurl, imgsavepath):
        """
        下载图片
        :param imgurl:      需要下载图片的url
        :param imgsavepath: 图片存放位置
        """
        # python3中的urllib.request模块所提供的urlretrieve()函数。urlretrieve()方法可以直接将远程数据下载到本地
        from urllib.request import urlretrieve
        urlretrieve(imgurl, imgsavepath)

    def after_quit(self):
        """
        关闭浏览器
        """
        self.driver.quit()

    def login_main(self):
        # ssl._create_default_https_context = ssl._create_unverified_context    # 用于忽略网页的SSL认证,需要额外导入import ssl
        driver = self.driver
        driver.maximize_window()
        driver.get(self.url)

        # 获取到元素并实现点击
        click_keyi_username = driver.find_element_by_xpath("//div[@class='wp-onb-tit']/a[text()='可疑用户']")
        self.webdriverwait_click(driver, click_keyi_username)
        login_button = driver.find_element_by_id('code')
        self.webdriverwait_click(driver, login_button)
        time.sleep(1)

        driver.switch_to.frame(driver.find_element_by_id('tcaptcha_iframe'))    # 用于获取到验证码iframe元素
        time.sleep(0.5)
        bk_block = driver.find_element_by_xpath('//img[@id="slideBg"]')         # 获取验证码中的大图
        web_image_width = bk_block.size             # 获取大图的大小(高度、宽度)
        web_image_width = web_image_width['width']  # 获取大图的宽度
        # print(bk_block.location)                  # 该图片对象在弹出的验证码框中的位置,返回字典的格式,例如:{'x': 10, 'y': 61}
        bk_block_x = bk_block.location['x']         # 获取该图片对象在验证码框中的位置(x轴)

        slide_block = driver.find_element_by_xpath('//img[@id="slideBlock"]')   # 获取验证码中的拼图
        # print(bk_block.location)                  # 该图片对象在弹出的验证码框中的位置,返回字典的格式,例如:{'x': 36, 'y': 102}
        slide_block_x = slide_block.location['x']   # 获取该图片对象在验证码框中的位置(x轴)

        bk_block = driver.find_element_by_xpath('//img[@id="slideBg"]').get_attribute('src')        # 获取验证码中的大图url
        slide_block = driver.find_element_by_xpath('//img[@id="slideBlock"]').get_attribute('src')  # 获取验证码中的拼图url
        slid_ing = driver.find_element_by_xpath('//div[@id="tcaptcha_drag_thumb"]')                 # 获取滑块

        os.makedirs('./image/', exist_ok=True)      # os.makedirs():递归创建多层目录;其中参数exist_ok=True:表示在目标目录已存在的情况下不会抛出异常(即不会报错)
        self.urllib_download(bk_block, './image/bkBlock.png')           # 下载验证码中的大图
        self.urllib_download(slide_block, './image/slideBlock.png')     # 下载验证码中的拼图
        time.sleep(0.5)

        img_bkblock = Image.open('./image/bkBlock.png')                 # 因为下载的大图跟浏览器中显示的大图比例不一样,会影响到滑动的距离跟速度,所以计算出比例后面用到
        real_width = img_bkblock.size[0]                                # 获取下载的验证码中大图的宽度:680
        width_scale = float(real_width) / float(web_image_width)        # 得出比例      [下载的大图大小为:680*390;浏览器显示的大图大小为:340.81*198.45(大概)]
        position_x = self.get_position('./image/bkBlock.png', './image/slideBlock.png')               # 获取到 大图 与 拼图 位移的距离 (实际滑动的距离就是x轴的距离)
        real_position = position_x / width_scale                        # 将大图/比例,得到验证码框中大图与拼图实际的滑动距离
        real_position = real_position - (slide_block_x - bk_block_x)    # (slide_block_x - bk_block_x):即拼图到大图的左边距,所以减去左边距后才得到真正的滑动距离
        track_list = self.get_track(real_position)                      # 调用get_track()方法,传入真实距离参数,得出移动轨迹

        ActionChains(driver).click_and_hold(on_element=slid_ing).perform()              # 找到滑块元素,点击鼠标左键,按住不放
        time.sleep(0.2)
        # 拖动元素
        for track in track_list:
            ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()     # 根据运动轨迹(x轴),进行拖动
            time.sleep(0.002)
        time.sleep(1)
        ActionChains(driver).release(on_element=slid_ing).perform()     # 释放鼠标
        time.sleep(1)

        print('登录成功')
        self.after_quit()


if __name__ == '__main__':
    login = Login()
    login.login_main()
》参考文献:

https://blog.csdn.net/qq_38534107/article/details/89522481
https://blog.csdn.net/Tracy_LeBron/article/details/84567419

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值