selenium 模拟滑块验证码

2023-02-22 13:46:16 浏览数 (1)

参考资料

slider-captcha/slider_captcha.py at master · maxnoodles/slider-captcha (github.com)

GitHub - sml2h3/ddddocr: 带带弟弟 通用验证码识别OCR pypi版

实践代码

代码语言:javascript复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import random
import time
import traceback
from contextlib import contextmanager

import cv2  # pip install opencv_python
import requests
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By


@contextmanager
def selenium(driver):
    try:
        yield
    except Exception as e:
        traceback.print_exc()
        driver.quit()


class SliderCaptcha:

    def __init__(self):
        options = webdriver.ChromeOptions()
        options.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.start_time = time.strftime("%H_%M_%S")
        print(self.start_time)
        self.background_path = './slider_img/background_%s.png' % self.start_time
        self.slider_path = './slider_img/slider_%s.png' % self.start_time
        self.driver = webdriver.Chrome(chrome_options=options)
        self.wait = WebDriverWait(self.driver, 10)

    def login(self):
        with selenium(self.driver):
            login_url = 'http://dun.163.com/trial/jigsaw'
            self.driver.maximize_window()
            self.driver.get(url=login_url)
            # 点击按钮,触发滑块
            time.sleep(1)
            locator = (By.CLASS_NAME, 'yidun_slider')
            WebDriverWait(self.driver, 10, 1).until(
                EC.element_to_be_clickable(locator))

            self.driver.find_elements_by_class_name("yidun_slider")[0].click()

            # self.driver.find_element_by_xpath(
            #     '//div[@class="yidun_slider"]'
            # ).click()

            # 获取背景图并保存
            background = self.wait.until(
                lambda x: x.find_element_by_xpath('//img[@class="yidun_bg-img"]')
            ).get_attribute('src')
            with open(self.slider_path, 'wb') as f:
                resp = requests.get(background)
                f.write(resp.content)

            # 获取滑块图并保存
            slider = self.wait.until(
                lambda x: x.find_element_by_xpath('//img[@class="yidun_jigsaw"]')
            ).get_attribute('src')
            with open(self.background_path, 'wb') as f:
                resp = requests.get(slider)
                f.write(resp.content)

            # 这里可以用 ddddocr 模块提高准确度
            distance = self.findfic(target=self.background_path, template=self.slider_path)
            print("匹配距离:", distance)
            # 初始滑块距离边缘 4 px
            trajectory = self.get_tracks(distance   4)
            print("模拟轨迹:", trajectory)

            # 等待按钮可以点击
            slider_element = self.wait.until(
                EC.element_to_be_clickable(
                    (By.CLASS_NAME, 'yidun_jigsaw'))
            )

            # 添加行动链,向右并超出一部分
            ActionChains(self.driver).click_and_hold(slider_element).perform()
            for track in trajectory['plus']:
                ActionChains(self.driver).move_by_offset(
                    xoffset=track,
                    yoffset=round(random.uniform(1.0, 3.0), 1)
                ).perform()
            time.sleep(0.5)

            # 添加行动链,返回超出的部分
            for back_tracks in trajectory['reduce']:
                ActionChains(self.driver).move_by_offset(
                    xoffset=back_tracks,
                    yoffset=round(random.uniform(1.0, 3.0), 1)
                ).perform()

            # perform() 是执行行动链
            for i in [-4, 4]:
                ActionChains(self.driver).move_by_offset(
                    xoffset=i,
                    yoffset=0
                ).perform()

            time.sleep(0.1)
            # 释放在元素上按住的鼠标按钮。
            ActionChains(self.driver).release().perform()
            time.sleep(1)

    def close(self):
        self.driver.quit()

    def findfic(self, target='background.png', template='slider.png'):
        """

        :param target: 滑块背景图
        :param template: 滑块图片路径
        :return: 模板匹配距离
        """
        target_rgb = cv2.imread(target)
        target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
        template_rgb = cv2.imread(template, 0)
        # 使用相关性系数匹配, 结果越接近1 表示越匹配
        # https://www.cnblogs.com/ssyfj/p/9271883.html
        # target_gray 参数表示待搜索源图像,必须是8位整数或32位浮点。
        # template_rgb 参数表示模板图像,必须不大于源图像并具有相同的数据类型。
        # cv2.TM_CCOEFF_NORMED 参数表示计算匹配程度的方法。
        # res 参数表示匹配结果图像,必须是单通道32位浮点。如果image的尺寸为W x H,templ的尺寸为w x h,则result的尺寸为(W-w 1)x(H-h 1)。
        res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
        # opencv 的函数 minMaxLoc:在给定的矩阵中寻找最大和最小值,并给出它们的位置
        # minVal参数表示返回的最小值,如果不需要,则使用NULL。
        # maxVal参数表示返回的最大值,如果不需要,则使用NULL。
        # minLoc参数表示返回的最小位置的指针(在2D情况下); 如果不需要,则使用NULL。
        # maxLoc参数表示返回的最大位置的指针(在2D情况下); 如果不需要,则使用NULL。
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        print(min_val, max_val, min_loc, max_loc)
        # 因为滑块只需要 x 坐标的距离,返 回坐标元组的 [0] 即可
        if abs(1 - min_val) <= abs(1 - max_val):
            distance = min_loc[0]
        else:
            distance = max_loc[0]
        return distance

    def get_tracks(self, distance):
        """

        :param distance: 缺口距离
        :return: 轨迹
        """
        # 分割加减速路径的阀值
        value = round(random.uniform(0.55, 0.75), 2)
        # 划过缺口 20 px
        distance  = 20
        # 初始速度,初始计算周期, 累计滑动总距
        v, t, sum = 0, 0.3, 0
        # 轨迹记录
        plus = []
        # 将滑动记录分段,一段加速度,一段减速度
        mid = distance * value
        while sum < distance:
            if sum < mid:
                # 指定范围随机产生一个加速度
                a = round(random.uniform(2.5, 3.5), 1)
            else:
                # 指定范围随机产生一个减速的加速度
                a = -round(random.uniform(2.0, 3.0), 1)
            s = v * t   0.5 * a * (t ** 2)
            v = v   a * t
            sum  = s
            plus.append(round(s))

        # end_s = sum - distance
        # plus.append(round(-end_s))

        # 手动制造回滑的轨迹累积20px
        # reduce = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]
        reduce = [-6, -4, -6, -4]
        return {'plus': plus, 'reduce': reduce}


def run():
    for i in range(1):
        spider = SliderCaptcha()
        spider.login()
        spider.close()


if __name__ == '__main__':
    if not os.path.exists("./slider_img"):
        os.mkdir("./slider_img")
    run()

0 人点赞