解决方案 变速滑块验证(puzzle和slider速度不一样) 通过滑动实时计算距离
评论
收藏

解决方案 变速滑块验证(puzzle和slider速度不一样) 通过滑动实时计算距离

经验分享
E
Evangelio
2025-09-01 17:38·浏览量:369
E
Evangelio
影刀专家
发布于 2025-09-01 17:38369浏览

1、打开F12查看源码,有距离像素值。拖动并查看属性值的变化。

2、通过模拟人工随机滑动每次1~2个像素,每次滑动完分别puzzle和slider的像素距离值,存在两组列表里面。


#拖动和实时获取像素点数据
def drag(browser, drag_ele: WebElement, distance: int):
    """
    拖动滑块,同时每步通过 xpath 获取滑块和滑轨 style 数据。
    :param browser: 已打开的网页对象
    :param distance: 拖动距离
    :return: (slider_style_list, track_style_list)
    """
    slider_list = []
    puzzle_list = []

    x, y, width, height = drag_ele.get_bounding()
    pos_center_x = x + width / 2
    pos_center_y = y + height / 2

    win32.manual_motion_on(
        motion_move=True, motion_click=True, motion_delay=True,
        min_time=0.01, max_time=0.1
    )

    # 移动到元素中心并按下
    win32.mouse_move(point_x=pos_center_x, point_y=pos_center_y, move_speed="middle")
    win32.mouse_click(button="left", click_type="down", hardware_driver_click=True)

    # ---- 拖到目标 ----
    current_x = pos_center_x
    target_x = pos_center_x + glv['width']
    total_distance = glv['width']
    while current_x < target_x:
        progress = (current_x - pos_center_x) / total_distance
        s_factor = math.sin(progress * math.pi / 2)
        step = random.choice([1, 2])
        current_x += step
        if current_x > target_x:
            current_x = target_x


        jitter_y = pos_center_y + random.uniform(-0.5, 0.5)
        print(f'拖动{step} 到 current_x : {current_x}, target_x : {target_x}, ')
        win32.mouse_move(point_x=current_x, point_y=jitter_y, move_speed="fast", delay_after=0.01)
        time.sleep(random.uniform(0.001, 0.01))
        puzzle_list, slider_list = get_px(browser, puzzle_list, slider_list)


    puzzle_list = [float(item.split(': ')[1].replace('px;', '')) for item in puzzle_list]
    slider_list = [float(item.split(': ')[1].replace('px;', '')) for item in slider_list]
    predict_x, deg, formula, coefs = fit_data.fit_from_y(
        puzzle_list, x_data=slider_list, max_degree=6, invert=True)
    print("拟合公式(反函数):", formula)


    distance = predict_x(distance)

   win32.mouse_move(pos_center_x + distance, pos_center_y, move_speed="middle")

    win32.mouse_click(button="left", click_type="up", hardware_driver_click=True)
    win32.manual_motion_off()
    return puzzle_list, slider_list

3、计算缺口与左边起点距离的像素值。

import cv2
import numpy as np
import base64
import requests

def load_image(input_str: str):
    """
    输入可以是图片 URL 或 base64 字符串
    返回 OpenCV 图像对象 (numpy.ndarray)
    """
    if input_str.startswith("http://") or input_str.startswith("https://"):
        # 远程 URL
        resp = requests.get(input_str, timeout=10)
        img_data = resp.content
    else:
        # base64 格式
        if input_str.startswith("data:image"):
            input_str = input_str.split(",", 1)[1]
        img_data = base64.b64decode(input_str)

    np_arr = np.frombuffer(img_data, np.uint8)
    img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
    return img


def find_gap(bg_input: str, block_input: str, debug_save: str = None):
    """
    查找滑块缺口位置
    :param bg_input: 背景图(URL 或 base64)
    :param block_input: 缺口小块图(URL 或 base64)
    :param debug_save: 调试用保存文件名(可选)
    :return: (x, y, confidence)
    """
    # 加载图像
    bg_img = load_image(bg_input)
    block_img = load_image(block_input)

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

    x, y = max_loc
    h, w = block_img.shape[:2]

    if debug_save:
        debug_img = bg_img.copy()
        cv2.rectangle(debug_img, (x, y), (x + w, y + h), (0, 0, 255), 2)
        cv2.imwrite(debug_save, debug_img)

    return x, y, max_val

4、find_gap返回的x就是puzzle缺口位置到左边起点的距离,我这里一般加1-2个像素点,因为计算出来的值就是靠左一点点的,实际需要偏移一下。

5、刚才取得的列表,需要拟合成一个函数,可能是多项式函数(找ai写即可),我一开始搞反了x和y,所以又添一个反函数的操作。


import numpy as np


def _dedupe_xy(x, y):
    """
    去重:相同 x 的 y 取平均,避免重复点引起的拟合不稳定。
    """
    x = np.asarray(x, dtype=float).ravel()
    y = np.asarray(y, dtype=float).ravel()
    uniq = {}
    for xi, yi in zip(x, y):
        if xi in uniq:
            uniq[xi].append(yi)
        else:
            uniq[xi] = [yi]
    x_new = []
    y_new = []
    for xi, ys in uniq.items():
        x_new.append(xi)
        y_new.append(np.mean(ys))
    order = np.argsort(x_new)
    x_clean = np.asarray(x_new)[order]
    y_clean = np.asarray(y_new)[order]
    return x_clean, y_clean


def _best_polyfit_by_aicc(x, y, max_degree=10):
    """
    用 AICc 自动选择最佳多项式阶数,返回最佳系数与阶数。
    """
    x = np.asarray(x, dtype=float).ravel()
    y = np.asarray(y, dtype=float).ravel()
    n = len(x)
    best = None
    best_deg = None
    best_aicc = np.inf

    max_deg = int(min(max_degree, max(1, n - 2)))  # 防止过高阶数
    for deg in range(1, max_deg + 1):
        coefs = np.polyfit(x, y, deg)  # 高次在前
        y_hat = np.polyval(coefs, x)
        rss = np.sum((y - y_hat) ** 2)
        k = deg + 1  # 参数个数
        # 避免 log(0)
        rss_per = rss / n if rss > 0 else 1e-12
        aic = n * np.log(rss_per) + 2 * k
        aicc = aic + (2 * k * (k + 1)) / max(n - k - 1, 1)
        if aicc < best_aicc:
            best_aicc = aicc
            best = coefs
            best_deg = deg
    return best, best_deg


def _poly_to_string(coefs, var='x'):
    """
    把多项式系数转成可读公式字符串。
    """
    terms = []
    deg = len(coefs) - 1
    for i, c in enumerate(coefs):
        power = deg - i
        if abs(c) < 1e-12:
            continue
        if power == 0:
            terms.append(f"{c:.8g}")
        elif power == 1:
            terms.append(f"({c:.8g})*{var}")
        else:
            terms.append(f"({c:.8g})*{var}**{power}")
    if not terms:
        return "y = 0"
    return "y = " + " + ".join(terms)


def fit_from_y(y_data, x_data=None, max_degree=10, start_index=1, step=1.0, invert=False):
    """
    拟合一个可调用函数:
    - 若仅传 y_data:默认 x = start_index, start_index+step, ...
    - 若也传 x_data:用你给的 x_data。
    - invert=False(默认):拟合 y = f(x),predict(x_new) -> y
    - invert=True:拟合 x = g(y),predict(y_new) -> x(即“反函数”拟合)

    返回:
    predict (callable), best_degree (int), formula (str), coefs (np.ndarray)
    """
    y = np.asarray(y_data, dtype=float).ravel()

    if x_data is None:
        # 只有 y:用等距索引作为 x
        n = len(y)
        x = start_index + step * np.arange(n, dtype=float)
    else:
        x = np.asarray(x_data, dtype=float).ravel()
        if len(x) != len(y):
            raise ValueError("x_data 与 y_data 长度不一致。")

    # invert 模式:把自变量/因变量交换来拟合“反函数”
    if invert:
        xx, yy = _dedupe_xy(y, x)   # 拟合 x = g(y)
        coefs, best_deg = _best_polyfit_by_aicc(xx, yy, max_degree=max_degree)

        def predict(y_new):
            y_new = np.asarray(y_new, dtype=float)
            return np.polyval(coefs, y_new)
        formula = _poly_to_string(coefs, var='y')
        return predict, best_deg, formula, coefs

    # 正常模式:拟合 y = f(x)
    xx, yy = _dedupe_xy(x, y)
    coefs, best_deg = _best_polyfit_by_aicc(xx, yy, max_degree=max_degree)

    def predict(x_new):
        x_new = np.asarray(x_new, dtype=float)
        return np.polyval(coefs, x_new)
    formula = _poly_to_string(coefs, var='x')
    return predict, best_deg, formula, coefs

总结:根据OpenCV去识别缺口距离,再通过滑动并实时抓取puzzle和slider的实际距离像素数据,通过拟合函数计算出puzzle到位的时候,slider滑动的实际距离是多少,最后把滑块移到终点放手即可。

收藏5
全部评论1
最新
发布评论
评论