同花顺滑动验证码 Selenium + OpenCV 代码移植实战

登录同花顺,发现它的滑动验证码比豆瓣简单不少,就拿之前豆瓣那套过滑动验证码的python代码改了一下,前后花了半个多小时就搞定了。

同花顺滑动验证码 Selenium + OpenCV 代码移植实战
同豆顺登录页面

改动的地方

1. 图片获取

某瓣的图片 URL 藏在 style 属性里,要用正则去抠。同某顺直接给 src,省事多了。

# 同某顺:直接取src
bg_url = driver.find_element(By.ID, 'slicaptcha-img').get_attribute("src")
slider_url = driver.find_element(By.ID, 'slicaptcha-block').get_attribute("src")

2. 滑块定位

某瓣用 XPath 一层层找,同某顺直接给 ID,省心。

block = driver.find_element(By.ID, 'slider')

3. 尺寸常量

同某顺背景图 340×195,显示尺寸 309×177,把 OpenCV 计算里的常量替换掉就行,算法不用动。

滑动验证码大小
验证码弹窗

完整代码

完整代码贴在下面,核心逻辑全部复用,改动不超过 30 行。

import re

import requests
import cv2

import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options


def download_image(url, path):
    headers = {"User-Agent": "Mozilla/5.0", "Referer": "https://www.douban.com/"}
    resp = requests.get(url, headers=headers)
    with open(path, "wb") as f:
        f.write(resp.content)
    print(f"已下载: {path}")


def get_slide_distance(bg_path, slider_path, offset=0):
    """
    计算滑块滑动距离
    bg_path: 背景图路径 (340x195)
    slider_path: 滑块图路径 (55x55)
    offset: 偏移调整值
    """
    # 尺寸常量
    bg_width = 340
    bg_height = 195
    slider_size = 55

    # 显示尺寸比例 (CSS显示尺寸 / 实际尺寸)
    display_bg_width = 309
    display_bg_height = 177.22
    display_slider_size = 49.9853

    # 缩放比例
    scale = display_bg_width / bg_width  # 309/340 ≈ 0.9088

    # 读取图片
    bg = cv2.imread(bg_path)
    slider = cv2.imread(slider_path, cv2.IMREAD_UNCHANGED)

    # 处理滑块 - 取alpha通道作为灰度图
    if slider.shape[2] == 4:
        slider_gray = slider[:, :, 3]  # alpha通道
    else:
        slider_gray = cv2.cvtColor(slider, cv2.COLOR_BGR2GRAY)

    # 背景转灰度
    bg_gray = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)

    # 边缘检测
    bg_edges = cv2.Canny(bg_gray, 100, 200)
    slider_edges = cv2.Canny(slider_gray, 100, 200)

    # 模板匹配
    result = cv2.matchTemplate(bg_edges, slider_edges, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(result)

    # 缺口在原图上的X坐标 (缺口左上角 + 滑块宽度/2 = 缺口中心)
    center_x_original = max_loc[0] + slider_size // 2 + offset

    # 换算到显示坐标的left值
    left_display = center_x_original * scale - display_slider_size / 2

    return left_display


def extract_url_from_style(style):
    match = re.search(r'url("([^"]+)")', style)
    return match.group(1) if match else None

def slide_simple(distance):
    """直接滑到底"""
    return [distance]
def get_tracks(distance):
    """
    生成仿人滑动的轨迹数组
    原理:模拟人的拖动习惯 —— 先加速后减速
    :param distance: 需要滑动的总距离(像素)
    :return: 轨迹数组,每个元素是每次移动的偏移量(像素)
    """
    tracks = []  # 存储每一步移动的距离
    current = 0  # 当前已经移动的总距离
    mid = distance * 0.6  # 加速阶段的终点(前60%加速,后40%减速)
    t = 0.05  # 时间间隔(秒),越小轨迹越平滑
    v = 0  # 初始速度(像素/秒)

    # 循环生成轨迹,直到走完总距离
    while current < distance:
        # 根据当前位置决定加速度
        # 前60%:加速(a=20),模拟人启动时加快速度
        # 后40%:减速(a=-3),模拟人快到目标时减速
        a = 20 if current < mid else -3

        # 匀变速运动位移公式:s = v0 * t + 0.5 * a * t^2
        s = v * t + 0.5 * a * t * t

        # 累加位移
        current += s

        # 将本次移动距离取整后加入轨迹(像素必须是整数)
        tracks.append(round(s))

        # 更新速度:v = v0 + a * t
        v += a * t

    # 修正误差:因为取整可能导致总距离不等于目标距离
    total = sum(tracks)
    if total > distance:
        # 如果超了,从最后一步减去多余的部分
        tracks[-1] -= total - distance
    elif total < distance:
        # 如果不够,补一步剩余距离
        tracks.append(distance - total)

    return tracks
def slide(driver):
    # 切换iframe

    time.sleep(1)
    n = 1
    while n <= 5:
        try:
            # 获取背景图URL
            bg_element = driver.find_element(By.ID, 'slicaptcha-img')
            bg_url = bg_element.get_attribute("src")

            # 获取滑块大图元素(小方块那个)
            elem = driver.find_element(By.ID, 'slicaptcha-block')
            big_url = elem.get_attribute("src")


            print(f"背景图URL: {bg_url[:80]}...")
            print(f"滑块图URL: {big_url[:80]}...")

            # 下载图片
            download_image(bg_url, "bg.png")
            download_image(big_url, "slider_big.png")

            # 计算距离
            distance = get_slide_distance("bg.png", "slider_big.png")
            print(f"需要滑动: {distance:.2f}px")


            tracks = slide_simple(distance)

            # 找滑块元素
            block = driver.find_element(By.ID, 'slider')

            # 1. 按下滑块
            ActionChains(driver).click_and_hold(block).perform()
            time.sleep(0.1)
            # 2. 按轨迹滑动
            for track in tracks:
                ActionChains(driver).move_by_offset(xoffset=track, yoffset=random.uniform(-1, 1)).perform()
                time.sleep(random.uniform(0.01, 0.02))
            # 3. 松开滑块
            ActionChains(driver).release().perform()
            time.sleep(2)

            # 4. 检查验证是否成功
            # 如果验证成功,instructionText 会消失或变成其他状态

            print(f"第{n}次验证成功")
            break
            n += 1
            print(f"第{n - 1}次验证失败,重试...")
            print(f"本次将会滑动:{distance}")

        except Exception as e:
            print(e)
            print(f"第{n}次验证成功")
            break


def main():
    url = "https://upass.10jqka.com.cn/login"
    options = Options()
    options.add_argument('--disable-blink-features=AutomationControlled')
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    time.sleep(2)

    # 点击密码登录

    driver.find_element(By.ID, "to_account_login").click()
    time.sleep(2)

    # 输入账号密码
    driver.find_element(By.ID, "uname").send_keys("color")
    driver.find_element(By.ID, "passwd").send_keys("wewewrqr")
    time.sleep(1)

    # 点击登录按钮
    driver.find_element(By.CLASS_NAME, "submit_btn").click()
    time.sleep(3)

    # # 滑动验证码(内部会自动下载图片、计算距离、滑动)
    slide(driver)  # distance参数会被覆盖,随便传个0

    time.sleep(3)
    print("完成")
    driver.quit()


if __name__ == '__main__':
    main()

总结

核心改动就三处:图片获取方式、滑块定位、尺寸常量。其他代码全部复用,验证成功率 90% 以上。如果你之前跑过某瓣那套滑动验证码得到代码,这个十分钟内就能跑起来。

本文仅用于技术学习,请勿用于非法用途。

给TA打赏
共{{data.count}}人
人已打赏
技术文档

从零搭建Frida环境 超详细攻略

2026-7-3 1:23:11

技术文档

网页反调试debugger绕过方案:条件断点替换(最快)、油猴脚本Hook(一劳永逸)

2026-6-24 2:30:32

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
有新私信 私信列表
搜索