使用opencv破解滑块验证码:以今日头条PC端登录页面滑块验证码为例

       本文目标人群:python爬虫工程师

       一、首先看看破解的效果图

     效果一

      效果二

      效果三

      效果四

       二、滑块验证码的破解

       滑块验证码的破解的难点主要有两个:计算出滑块到缺口的距离和模拟人拖动滑块的轨迹。

       如何计算出滑块到缺口的距离?从网上的资料来看,主要有两种方式:自己使用Pillow库实现算法,使用OpenCV库提供的现成方法。本文就使用后者,简单而又强大、成功率高。

      本文主要参考https://juejin.im/post/5cf4cbd4f265da1b8e7089b4,但是由于它的注释太少,也未给出完整代码。所以,决定写这篇博客。

      三、什么是OpenCV?什么是opencv-python?

      OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,提供了很多处理图片、视频的方法。虽然是C/C++开发的,但是提供了 Python、Java、MATLAB 等其他语言的接口。

      OpenCV-python库就是使用pthon操作OpenCV的一个库。

      四、为什么要用OpenCV-python库?

      说白了,就是OpenCV库提供了一个方法(matchTemplate()):从一张较大的图片中搜索一张较小图片,计算出这张大图上各个区域和小图相似度。调用这个方法后返回一个二维数组(numpy库中ndarray对象),从中就能拿到最佳匹配区域的座标。换到滑块验证码上面,滑块背景图片是大图,滑块是小图。

      opencv-python参见官网:https://docs.opencv.org/4.0.0/index.html

     cv.matchTemplate()方法参见:

     https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_template_matchin/py_template_matching.html

    今日头条PC端登录页面: https://sso.toutiao.com/

    五、环境

    首先,selenium、谷歌浏览器、谷歌浏览器驱动等环境是必须的。

    安装OpenCV-python库

    pip install  opencv-python

    或者使用阿里源:

    pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com opencv-python

    六、代码

   源码也可以在我的github找到https://github.com/chushiyan/slide_captcha_cracking

# create by chushiyan
# date: 20190926
# email: Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)

import cv2 as cv
from selenium import webdriver
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
import time
import urllib.request
import random


#  传入滑块背景图片本地路径和滑块本地路径,返回滑块到缺口的距离
def findPic(img_bg_path, img_slider_path):
    """
    找出图像中最佳匹配位置
    :param img_bg_path: 滑块背景图本地路径
    :param img_slider_path: 滑块图片本地路径
    :return: 返回最差匹配、最佳匹配对应的x座标
    """

    # 读取滑块背景图片,参数是图片路径,OpenCV默认使用BGR模式
    # cv.imread()是 image read的简写
    # img_bg 是一个numpy库ndarray数组对象
    img_bg = cv.imread(img_bg_path)

    # 对滑块背景图片进行处理,由BGR模式转为gray模式(即灰度模式,也就是黑白图片)
    # 为什么要处理? BGR模式(彩色图片)的数据比黑白图片的数据大,处理后可以加快算法的计算
    # BGR模式:常见的是RGB模式
    # R代表红,red; G代表绿,green;  B代表蓝,blue。
    # RGB模式就是,色彩数据模式,R在高位,G在中间,B在低位。BGR正好相反。
    # 如红色:RGB模式是(255,0,0),BGR模式是(0,0,255)
    img_bg_gray = cv.cvtColor(img_bg, cv.COLOR_BGR2GRAY)

    # 读取滑块,参数1是图片路径,参数2是使用灰度模式
    img_slider_gray = cv.imread(img_slider_path, 0)

    # 在滑块背景图中匹配滑块。参数cv.TM_CCOEFF_NORMED是opencv中的一种算法
    res = cv.matchTemplate(img_bg_gray, img_slider_gray, cv.TM_CCOEFF_NORMED)

    print('#' * 50)
    print(type(res))  # 打印:<class 'numpy.ndarray'>
    print(res)
    # 打印:一个二维的ndarray数组
    # [[0.05604218  0.05557462  0.06844381... - 0.1784117 - 0.1811338 - 0.18415523]
    #  [0.06151756  0.04408009  0.07010461... - 0.18493137 - 0.18440475 - 0.1843424]
    # [0.0643926    0.06221284  0.0719175... - 0.18742703 - 0.18535161 - 0.1823346]
    # ...
    # [-0.07755355 - 0.08177952 - 0.08642308... - 0.16476074 - 0.16210903 - 0.15467581]
    # [-0.06975575 - 0.07566144 - 0.07783117... - 0.1412715 - 0.15145643 - 0.14800543]
    # [-0.08476129 - 0.08415948 - 0.0949327... - 0.1371379 - 0.14271489 - 0.14166716]]

    print('#' * 50)

    # cv2.minMaxLoc() 从ndarray数组中找到最小值、最大值及他们的座标
    value = cv.minMaxLoc(res)
    # 得到的value,如:(-0.1653602570295334, 0.6102921366691589, (144, 1), (141, 56))

    print(value, "#" * 30)

    # 获取x座标,如上面的144、141
    return value[2:][0][0], value[2:][1][0]


# 返回两个数组:一个用于加速拖动滑块,一个用于减速拖动滑块
def generate_tracks(distance):
    # 给距离加上20,这20像素用在滑块滑过缺口后,减速折返回到缺口
    distance += 20
    v = 0
    t = 0.2
    forward_tracks = []
    current = 0
    mid = distance * 3 / 5  # 减速阀值
    while current < distance:
        if current < mid:
            a = 2  # 加速度为+2
        else:
            a = -3  # 加速度-3
        s = v * t + 0.5 * a * (t ** 2)
        v = v + a * t
        current += s
        forward_tracks.append(round(s))

    back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1]
    return forward_tracks, back_tracks


def main():
    # 创建一个参数对象,用来控制谷歌浏览器以无界面模式打开
    chrome_options = Options()
    # chrome_options.add_argument('--headless')
    # chrome_options.add_argument('--disable-gpu')

    driver = webdriver.Chrome(chrome_options=chrome_options)

    # 设置浏览器宽高
    driver.set_window_size(width=2000, height=3000)

    wait = WebDriverWait(driver, 10)

    driver.get("https://sso.toutiao.com/")

    try:
        # 获取手机号码输入框
        text_box = wait.until(expected_conditions.presence_of_element_located((By.ID, "user-mobile")))
        print(text_box)

        # 获取发送验证按钮
        send_btn = wait.until(expected_conditions.presence_of_element_located((By.ID, "mobile-code-get")))
        print(send_btn)

    except Exception as e:
        print(e)
        raise RuntimeError("获取 手机号码输入框 或者 发送验证按钮 失败,程序结束")

    text_box.send_keys("18812345678")

    # time.sleep(random.uniform(2, 3))

    try:
        action = ActionChains(driver)
        action.click(send_btn).perform()
    except Exception as e:
        print(e)
    else:
        time.sleep(1.5)

        # 获取 滑块背景图
        bg_image = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-main"]/img[1]')))
        print("\n背景:")

        print(bg_image)
        bg_image_url = bg_image.get_attribute('src')

        print(bg_image_url)

        # 使用urllib下载背景图
        # 原因是:使用bg_image.screenshot()程序卡在这里,也不报错
        urllib.request.urlretrieve(bg_image_url, "./img_bg.png")

        # 获取 滑块
        slider = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-main"]/img[2]')))

        print("\n滑块:")
        print(slider)

        # 注意:千万不能通过截图获取滑块,因为滑块不是规则的图形
        # 而截图截出的是矩形,会把滑块周围的滑块背景图一起截取,势必影响匹配
        # slider.screenshot('./img_slider.png')
        slider_url = slider.get_attribute('src')

        urllib.request.urlretrieve(slider_url, "./img_slider.png")

        value_1, value_2 = findPic('./img_bg.png', './img_slider.png')

        print("#" * 30)
        print("最差匹配和最佳匹配对应的x座标分别是:")
        print(value_1)
        print(value_2)
        print("#" * 30)

        # 获取滑动按钮
        button = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-drag-button"]/img')))

        action = ActionChains(driver)
        try:
            action.click_and_hold(button).perform()
        except StaleElementReferenceException as e:
            print(e)

        action.reset_actions()

        forward_tracks, back_tracks = generate_tracks(value_2)

        for x in forward_tracks:
            action.move_by_offset(x, 0)  # 前进移动滑块
            print(x)

        print('#' * 50)

        for x in back_tracks:
            action.move_by_offset(x, 0)  # 后退移动滑块
            print(x)

        action.release().perform()


    finally:
        # time.sleep(10000)
        driver.close()


if __name__ == '__main__':
    main()

本文仅作为技术交流,如有侵权请联系我。

邮箱:Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64编码)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章