drissionpage + opencv 解决 wsj iframe滑块

0 声明

文章仅供技术交流

1 什么是drissionpage

可查看drissionpage官方文档

2 分析

当我们访问这个网站的时候,可能会弹出滑块验证码(相较于chrome和edge,360极速浏览器触发概率更大)
在这里插入图片描述
进入开发者工具(F12)看一下具体结构,通过抓包可以找到两个url里返回了一个背景图和一个缺口图
在这里插入图片描述
可以在源码里找到这两个图片的url,但是由于是在iframe里,所以通过简单程序请求想拿到这两个url是不容易的
在这里插入图片描述

3 思路

0x01 获取iframe里的滑块url

在drissionpage里可通过get_frame()来获取iframe对象

0x02 计算滑块距离

根据网上提供的方法,有下面几种计算滑块距离的方法,个人认为其中方法4的识别成功率更高
方法1:

def distance_puzzle(bg_img_path, puzzle_img_path):
    """
    根据背景图和缺口图,计算滑块距离
    :param bg_img_path:
    :param puzzle_img_path:
    :return:
    """
    img = cv2.imdecode(np.fromfile(bg_img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
    tpl = cv2.imdecode(np.fromfile(puzzle_img_path, dtype=np.uint8), cv2.IMREAD_COLOR)

    img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, img_bw = cv2.threshold(img_gry, 127, 255, cv2.THRESH_BINARY)

    tpl = cv2.cvtColor(tpl, cv2.COLOR_BGR2GRAY)
    for row in range(tpl.shape[0]):
        for col in range(tpl.shape[1]):
            if tpl[row, col] == 0:
                tpl[row, col] = 96
    lower = np.array([96])
    upper = np.array([96])
    mask = cv2.inRange(tpl, lower, upper)
    tpl[mask == 0] = 0
    tpl[mask != 0] = 255

    result = cv2.matchTemplate(img_bw, tpl, cv2.TM_CCOEFF_NORMED)
    _, _, _, max_loc = cv2.minMaxLoc(result)
    distance = int(max_loc[0] * 0.5833333333333333) + random.randint(0, 2)
    return distance
#
if __name__ == '__main__':
    background = './images/background.png'
    gap = './images/gap.png'
    offset = distance_puzzle(background, gap)

方法2:

import ddddocr

def distance_ddddocr(gap_content, background_content):
    det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
    result = det.slide_match(gap_content, background_content, simple_target=True)
    offset = result['target'][0]
    return offset

if __name__ == '__main__':
    background = open('./images/background.png', 'rb').read()
    gap = open('./images/gap.png', 'rb').read()
    offset = distance_ddddocr(gap, background)

方法3:

import cv2
import numpy as np

def distance_cv(slice_url, bg_url):
    """
    :param slice_url: 滑块(缺口)图片地址
    :param bg_url: 背景图地址
    :return: distance
    :rtype:integer
    """
    slice_image = np.asarray(bytearray(slice_url), dtype=np.uint8)
    slice_image = cv2.imdecode(slice_image, 1)
    slice_image = cv2.Canny(slice_image, 255, 255)

    bg_image = np.asarray(bytearray(bg_url), dtype=np.uint8)
    bg_image = cv2.imdecode(bg_image, 1)
    bg_image = cv2.pyrMeanShiftFiltering(bg_image, 5, 50)
    bg_image = cv2.Canny(bg_image, 255, 255)

    result = cv2.matchTemplate(bg_image, slice_image, cv2.TM_CCOEFF_NORMED)

    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    return max_loc[0]
#
if __name__ == '__main__':
    background = open('./images/background.png', 'rb').read()
    gap = open('./images/gap.png', 'rb').read()
    offset = distance_cv(background, gap)

方法4:

import cv2
import numpy as np

def calculate_offset_masked(bg_path: str,
                            tpl_path: str,
                            y_search_range: int = 5) -> int:
    """
    掩码模板匹配 + 纵向微调:
      - bg_path: 背景图路径(彩色)
      - tpl_path: 滑块图路径(带透明或白底)
      - y_search_range: 在[-r, +r]范围内微调y
    返回: 最佳横向偏移值 x(像素),并生成结果图
    若匹配失败则抛出 ValueError。
    """
    # 读取背景图和模板
    bg_color = cv2.imread(bg_path)
    if bg_color is None:
        raise FileNotFoundError(f"无法读取背景图: {bg_path}")
    bg_gray = cv2.cvtColor(bg_color, cv2.COLOR_BGR2GRAY)

    tpl_color = cv2.imread(tpl_path, cv2.IMREAD_UNCHANGED)
    if tpl_color is None:
        raise FileNotFoundError(f"无法读取滑块图: {tpl_path}")

    # 分离模板灰度和掩码
    if tpl_color.shape[2] == 4:
        b, g, r, a = cv2.split(tpl_color)
        tpl_gray = cv2.cvtColor(cv2.merge([b, g, r]), cv2.COLOR_BGR2GRAY)
        mask = (a > 0).astype(np.uint8) * 255
    else:
        tpl_gray = cv2.cvtColor(tpl_color, cv2.COLOR_BGR2GRAY)
        _, mask = cv2.threshold(tpl_gray, 10, 255, cv2.THRESH_BINARY)

    H, W = bg_gray.shape
    h, w = tpl_gray.shape

    best_score = -1.0
    best_x = None
    best_y = None

    # 在y轴微调匹配
    for dy in range(-y_search_range, y_search_range + 1):
        res = cv2.matchTemplate(bg_gray, tpl_gray, cv2.TM_CCORR_NORMED, mask=mask)
        # 限制Y范围保持模板在图内
        res[:abs(dy), :] = -1
        res[H - h - abs(dy) + 1:, :] = -1

        # 获取当前最佳
        _, max_val, _, max_loc = cv2.minMaxLoc(res)
        if max_val > best_score:
            best_score = max_val
            best_x, best_y = max_loc

    # 检查是否匹配到有效结果
    if best_x is None:
        raise ValueError("模板匹配失败,未找到有效偏移。")

    # 在背景图上绘制结果
    result_img = bg_color.copy()
    top_left = (best_x, best_y)
    bottom_right = (best_x + w, best_y + h)
    cv2.rectangle(result_img, top_left, bottom_right, (0, 255, 0), 2)

    # 保存结果图
    result_path = f"{bg_path.replace('.jpg', '')}_result.png"
    cv2.imwrite(result_path, result_img)

    return best_x


if __name__ == "__main__":
    bg_image_path = './images/background.png'
    slider_image_path = './images/gap.png'
    x_offset = calculate_offset_masked(bg_image_path, slider_image_path)

下面的图片分别是滑块缺口图、背景图和使用方法4识别出来的图片
缺口图
背景图
识别图

0x03 滑动轨迹

我用的是网上随便找的一个匀加速和匀减速的滑动轨迹

    def get_tracks(distance):
        """
        根据偏移量获取移动轨迹
        :param distance:偏移量
        :return:移动轨迹
        """
        tracks = []
        current = 0
        mid = distance * 4 / 5
        t = 0.2
        v = 0
        while current < distance:
            if current < mid:
                a = 5
            else:
                a = -3
            v0 = v
            v = v0 + a * t
            move = v0 * t + 0.5 * a * t * t
            current += move
            tracks.append(round(move))
        return tracks

0x04 drissionpage拖动滑块

  1. 找到要拖动的元素位置
  2. 使用动作链的hold方法按住需要拖动的块
  3. 使用动作链中move方法拖动
  4. 到达指定位置后使用动作链中的release方法释放
slider = 'x://div[@class="slider"]'
iframe.actions.hold(slider)
for track in tracks:
    iframe.actions.move(offset_x=track, offset_y=round(random.uniform(1.0, 3.0), 0), duration=.1)
time.sleep(0.1)
iframe.actions.release(slider)

0x05 其他注意项

  1. 因滑块在iframe里,要区分要操作的是page = Chromium(addr_or_opts=self.co).latest_tab的page还是iframe = self.page.get_frame('t:iframe')的iframe
  2. 滑块的识别率不可能是100%,需要注意滑动失败后的重试
  3. 注意休眠,如果不加休眠可能页面没加载出来程序功能就结束了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值