资讯动态

RPA滑块拼图验证码破解实战:缺口定位与人类轨迹模拟

发布时间:2026/10/19 9:32:27 来源:尧图企业网站定制
平时做RPA自动化的人最怕的就是脚本写到最后一步流程卡在验证码上。尤其是电商平台里最常见的滑块拼图验证码你辛辛苦苦把页面流程、数据抓取、异常重试都跑顺了一遇到验证码弹窗整个流程就像一个跑得飞快的机器人突然撞上了一堵墙。我最早处理这类问题的时候也试过各种“暴力方案”比如用固定坐标直接拖、用模板匹配硬算缺口、甚至手动把验证码弹窗干掉。最后都栽了跟头。真正跑通一套可复用的方案其实要靠三件事缺口定位算法、人类轨迹生成、浏览器自动化事件这三者拼接得足够自然。这篇文章就是把我处理某电商平台滑块拼图验证码的完整思路和踩坑过程写下来。内容会比较偏实战涉及的代码以Python为主适合已经会用浏览器自动化工具但被验证码卡住、想自己动手解决的RPA开发者。1. 滑块拼图验证码自动化流程里最硬的一块骨头1.1 为什么偏偏是滑块拼图先明确一下滑块拼图验证码考察的从来不是“你有没有能力拖动滑块”而是“你能不能精准地找到缺口”。它交互上只有一个动作拖动但背后同时做了两种检测第一图像识别检测。你需要在一张带缺口的背景图中找到拼图块应该在的位置。这对人来说很容易但对计算机来说既要排除背景纹理干扰又要计算像素精确度一旦偏移超过两个像素左右验证就会告诉你“位置没对齐”。第二行为轨迹检测。验证码前端脚本会在你拖动的过程中持续采集鼠标坐标序列、时间间隔、加速度、是否停顿、是否抖动、甚至包括拖动前鼠标在滑块上停留的时长。这些数据会被汇总成一个“人类行为置信度”分数分数过低就算你拖到了准确缺口也会判定为可疑操作。结论就是这不是单一算法问题而是图像定位和行为模拟的组合问题。你只解决一半另一半迟早会冒出来。1.2 我预期的最终效果在开始写代码之前我给自己定了一个很实际的目标验证码的通过率稳定在85%以上单次处理时间不超过两秒。通过率低于85%说明算法不够稳定跑生产流程时会频繁卡住单次时间太长又会拉低整条自动化链路的效率。为了保证这个目标我在设计上把整个流程拆成了独立模块方便单独调试验证码图像采集模块负责从页面DOM中取出背景图和拼图块缺口定位模块负责计算出拼图块在背景图上的x坐标轨迹生成模块负责生成一段像人的拖动轨迹浏览器动作执行模块负责把轨迹转换成实际鼠标事件。这样一来就算某一天验证码样式升级我也只需要替换对应的那个模块不用把整个流程推倒重来。2. 缺口定位从图像里找到缺口那一列2.1 验证码图片资源要抓全处理滑块拼图的第一步是把页面上的图片资源完整拿到手。用浏览器自动化工具取图的时候很多人直接读背景图的src属性就完事但实际测试会发现验证码区域经常存在图片懒加载src还没填充完整就被脚本读取了最后拿到的是半张空白图。我的做法是先等待图片元素渲染完成再读取src如果有必要就再做一次宽度和高度的校验。源码大致是这样bg_el page.locator(div.captcha-panel img.bg-image) block_el page.locator(div.captcha-panel img.block-image) page.wait_for_selector(div.captcha-panel img.bg-image, statevisible) bg_src bg_el.get_attribute(src) block_src block_el.get_attribute(src)这里有一个容易被忽略的细节背景图和拼图块加载完成的时间点可能不同必须分别等待。如果只等背景图拼图块很可能还是空白状态。拿到src后用网络请求工具直接下载。下载后最好校验一下图片字节数小于5KB的图片大概率是加载失败需要重新获取。2.2 模板匹配为什么经常不靠谱定位缺口最直觉的思路是用OpenCV的模板匹配。把拼图块作为模板在背景图上搜索最相似的位置。看起来没什么问题但在实际验证码上模板匹配经常翻车。原因有两个。一是背景图的纹理太丰富拼图块区域内部往往也包含了背景图案的一部分模板匹配会把那些颜色相近、纹理相似的区域全部当作候选导致误匹配。二是滑块拼图的缺口边缘不是简单的纯色边界它往往带有一圈阴影过渡边界经过抗锯齿处理模板匹配在计算相似度时会被这些过渡像素干扰。我自己测试过一批样本直接用模板匹配的准确率大概只有六成左右而且一旦图片里有重复纹理准确率还会进一步下降。2.3 用边缘梯度定位缺口可行且简洁真正稳定下来的方法是计算背景图的垂直边缘梯度然后找梯度响应最强的列区域。原理很简单拼图缺口在背景图上左右两边各自会形成一条比较明显的垂直边缘。在自然图片里边缘方向五花八门但缺口这个区域垂直方向的梯度响应会远远高于周围区域。利用这个特点可以避开模板匹配对纹理相似的敏感问题。实现代码如下import cv2 import numpy as np def locate_gap_x(bg_img): gray cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY) sobel_x cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize3) abs_sobel np.abs(sobel_x) height, width abs_sobel.shape col_score abs_sobel.mean(axis0) # 用平滑滤波去掉单点噪声 kernel np.ones(5) / 5 col_score_smooth np.convolve(col_score, kernel, modesame) # 根据验证码区域经验缺口一般不出现在最左边和最右边的装饰条上 search_start int(width * 0.2) search_end int(width * 0.85) region col_score_smooth[search_start:search_end] threshold np.mean(region) 1.6 * np.std(region) hot_idx np.where(region threshold)[0] if len(hot_idx) 0: return None # 把连续的热力列分组成段取最长的那一段作为缺口区域 split_points np.where(np.diff(hot_idx) ! 1)[0] 1 groups np.split(hot_idx, split_points) best_group max(groups, keylen) gap_center_px int(np.mean(best_group)) search_start return gap_center_px这段代码的思路是先算垂直梯度没有任何模板参与然后用“均值 1.6倍标准差”作为阈值筛选出候选列最后取连续段最长的区域作为缺口中心。我这里加了搜索范围限制取图像宽度20%到85%的区域。这个比例不是瞎写的是基于日常采集的验证码样本统计出来的。大多数验证码缺口不会出现在图片最左端或最右端留出这段区域可以避免把图片边缘的装饰条误判成缺口。如果你处理的验证码样式比较特殊可以先用人工标注几十张图片统计缺口的分布区间再调整这两个比例参数。2.4 真实环境里的干扰项怎么压下去边缘梯度法也不是一劳永逸。我遇到的干扰主要有三类图片中存在强纹理的垂直物体比如柱子、门框、边框它们的垂直梯度也很强可能把缺口区域覆盖掉。拼图块自带阴影阴影区域也会产生梯度响应的叠加。如果缺口边缘比较浅阴影可能让缺口定位偏向阴影一侧。图片过于光滑整张图没有明显的垂直边缘缺口区域也响应很弱导致阈值设置过高找不到候选区域。处理方案有几招在算梯度之前先对灰度图做一次高斯模糊核大小取3或5能压掉一部分细纹理噪声。如果图片太光滑导致梯度整体偏低就把阈值公式中的std权重从1.6下调到1.2让候选区域更宽松。如果检测到多个连续段可以结合缺口区域在整张图中的统计分布来判断。比如某些验证码的缺口通常在右侧那就可以把搜索范围进一步右移。这个方法对多数背景图的鲁棒性已经很高了。只在极少数复杂背景下我才会考虑用深度学习坐标回归模型去兜底。2.5 用深度学习坐标回归是否值得我试过用一个小型卷积神经网络直接回归缺口坐标训练数据从页面采集人工标注缺口位置后做归一化。准确率确实更高特别是在强纹理干扰场景下能比边缘梯度法提升两三个百分点。但代价也很明显。每次验证码样式更新都需要重新标注和训练。运行环境里还得装模型推理库对RPA脚本的分发和维护来说都很麻烦。我的建议是先把边缘梯度法跑通记录它失败的具体场景。如果失败率已经能接受就不用急着上模型。只有当你发现某些固定样式的图片始终定位失败再考虑为这个场景单独训练一个小模型。大多数情况下规则算法加合理预处理已经能覆盖日常工作流。3. 人类轨迹模拟通过率的分水岭3.1 机器滑动一眼假的三个特征缺口定位解决的是“往哪拖”轨迹生成解决的是“怎么拖”。后者才是通过率的分水岭。我最早测试时直接用线性插值从起点拖到终点脚本执行倒是快结果在反复测试中被判定为异常操作的概率非常高。后来我分析了浏览器端采集到的鼠标事件序列发现机器滑动有三个非常明显的特征第一x轴位移速度恒定没有加速和减速过程。人类拖动一个物体不可能每一步都匀速总是先快后慢或者先慢后快再慢。第二y轴完全没有波动。真实鼠标拖动时人手会有自然的上下微抖哪怕幅度很小也不会是一条直线。第三总时长太规整。比如每次都刚好0.6秒速度曲线完全相同这种固定模式很容易被特征工程捕获。3.2 把一次拖动拆成加速、巡航、减速三个阶段要模拟人类轨迹先把一次完整的拖动过程拆成三个阶段加速阶段鼠标从静止开始移动速度逐渐上升。这个阶段持续较短约占总时长的30%。巡航阶段速度维持在一个相对高的水平中间可能有轻微波动。这个阶段约占总时长的40%。减速阶段鼠标逐渐接近目标缺口速度明显降低可能在最后几像素处有一个微小的停顿或回拉。这个阶段约占总时长的30%。除此之外人类在开始拖动之前通常会先在滑块上停留几百毫秒偶尔还会移动一下鼠标到滑块附近再按下鼠标。这部分“拖动前动作”同样会影响行为置信度。3.3 用贝塞尔曲线来回填轨迹点我推荐的实现方式不是直接生成一堆物理公式而是用贝塞尔曲线生成基础路径再叠加随机扰动。贝塞尔曲线的优点是它天然带有平滑的加速和减速特性比手写速度曲线更接近真实鼠标运动的几何形态。对于滑块拖动来说用一条三次或四次贝塞尔曲线就够了。示例代码import math import random def bezier_point(p0, p1, p2, p3, t): return ( (1 - t) ** 3 * p0[0] 3 * (1 - t) ** 2 * t * p1[0] 3 * (1 - t) * t ** 2 * p2[0] t ** 3 * p3[0], (1 - t) ** 3 * p0[1] 3 * (1 - t) ** 2 * t * p1[1] 3 * (1 - t) * t ** 2 * p2[1] t ** 3 * p3[1] ) def generate_human_track(start, end, steps40): distance end[0] - start[0] # 两个控制点分别偏向起点和终点但带随机偏移 p0 start p3 end p1 ( start[0] distance * random.uniform(0.25, 0.4), start[1] random.uniform(-3, 3) ) p2 ( start[0] distance * random.uniform(0.65, 0.8), end[1] random.uniform(-3, 3) ) track [] for i in range(steps 1): t i / steps # 在贝塞尔点上叠加轻微噪声模拟手部抖动 x, y bezier_point(p0, p1, p2, p3, t) # x 方向噪声较小y 方向噪声稍大 noise_x random.gauss(0, 0.4) noise_y random.gauss(0, 1.2) track.append((round(x noise_x, 2), round(y noise_y, 2))) # 强制最后一点精确落在终点避免因噪声导致末端偏差 track[-1] (round(end[0], 2), round(end[1], 2)) return track这个代码的核心在于控制点的随机偏移让每次生成的曲线都不完全一样但整体路径又保持着“从起点到终点平滑过渡”的几何特征。如果你想要更逼真可以再加一个“末端回拉”在轨迹的最后10%处让x轴位置回退几个像素再回到终点。人的手在定位时会有这种纠偏动作很自然。3.4 如何评估自己的轨迹是否过关轨迹生成完了怎么判断它是否“像人”我自己会从三个维度去评估速度曲线是否平滑。把轨迹点转成每帧位移画出来看如果是平直的直线就说明太机械如果出现太多毛刺也不正常。总时长是否在人类正常范围内。一次滑块拖动通常在0.5秒到1.2秒之间。太慢或者太快都容易起怀疑。连续多次执行的结果波动是否合理。如果每次都生成一模一样的轨迹那等于白做随机化。还有一个更直接的评估方式拿同一个缺口坐标连续跑十次统计通过率和失败原因。如果失败原因总是轨迹相关问题就调整曲线参数如果失败原因总是缺口偏移就回到定位模块去排查。4. 与RPA框架整合从图像坐标到浏览器坐标4.1 页面元素定位与图片下载缺口和轨迹都搞定了接下来要做的是把逻辑接到RPA流程里。我用浏览器自动化框架先把验证码区域的相关元素定位出来。这里要注意元素选择器的稳定命名。我建议优先用验证码面板特有的class或者data属性不要用层级嵌套过深的选择器否则页面结构一旦调整选择器就会失效。实际代码大概是这样captcha_panel page.locator(div.captcha-panel) if not captcha_panel.is_visible(): print(当前无验证码继续执行流程) return bg_el captcha_panel.locator(img.bg-image) block_el captcha_panel.locator(img.block-image)然后通过图片地址下载到本地用图像处理库读取成OpenCV格式。如果图片是通过base64内嵌的就先把base64解码再转成图像数组。4.2 坐标换算别在这步翻车这是整个流程里我踩坑最多的地方。图像处理算出来的缺口位置是“背景图自然像素坐标”但浏览器里你真正需要的是“页面CSS像素坐标”。两者之间存在一个缩放比例由背景图自然宽度和实际渲染宽度决定。如果背景图在页面上的渲染宽度是340像素而图片文件本身的自然宽度是680像素那么算出的缺口像素坐标必须除以2才能对应到页面CSS坐标。跳过这一步直接拿图片像素坐标当页面偏移量用一定偏。我最后用的换算逻辑是bg_natural_width bg_img_width # 用图像库读取 bg_box bg_el.bounding_box() scale bg_box[width] / bg_natural_width gap_x_css gap_x_px * scale block_box block_el.bounding_box() slider_box slider_btn.bounding_box() # 这里算的是鼠标从滑块中心拖到缺口中心所需移动的距离 start_x slider_box[x] slider_box[width] / 2 target_x bg_box[x] gap_x_css move_distance target_x - start_x这里还隐藏着另一个坑页面缩放。如果浏览器窗口缩放比例不是100%bounding_box返回的坐标已经经过浏览器换算不用再额外乘缩放系数。但如果你自己调节过操作系统DPI或者浏览器设置了页面缩放最好先在目标环境里做一次校准。4.3 触发真正的拖动行为坐标算完之后到了执行拖动这一步。推荐直接用浏览器自动化框架的鼠标接口按下、逐点移动、松开。我采用的方式是先生成轨迹点序列再用鼠标接口逐点移动。注意每两个点之间需要有一个极短的间隔间隔时间用随机值不要固定为同一个睡眠时长。track generate_human_track((0, 0), (move_distance, 0), steps45) page.mouse.move(slider_x, slider_y) time.sleep(random.uniform(0.2, 0.5)) page.mouse.down() for x_offset, y_offset in track: page.mouse.move(slider_x x_offset, slider_y y_offset) time.sleep(random.uniform(0.008, 0.022)) page.mouse.up()这里用到了两次随机睡眠。一次是按下鼠标前的准备时间一次是轨迹点之间的间隔。前者太容易被忽略但很重要后者随机化是为了避免时间间隔序列被风控脚本分析出固定模式。如果你用的RPA工具没有直接暴露鼠标事件接口也可以用系统级鼠标控制库但那样更容易受到桌面环境干扰比如鼠标被其他程序抢占、窗口不在前台等。相对而言浏览器内置鼠标接口更稳定。4.4 失败重试与风控观察滑块验证码做完一次拖动后页面会给出结果。成功的状态一般是滑块按钮变成了另一种颜色或者出现“通过”的提示失败的状态是提示“请再试一次”或“拖动到缺口处”。重试逻辑要写在最外层因为验证码本身允许用户重复尝试。我自己实现的执行流程是滑动一次等待结果出现如果失败等待验证码刷新重新下载图片再次计算缺口位置重新生成轨迹最多重试三次三次都失败记录日志并暂停RPA流程人工介入。连续失败时不要一直快速刷新重试每次重试之间至少要等待1到2秒给风控脚本一个“人类重新尝试”的时间感。风控观察方面我发现连续自动化跑多次验证码之后哪怕轨迹模拟得再像也可能会被临时限制。这种情况大概率不是单次轨迹的问题而是整个会话的鼠标行为模式太规律。解决办法很简单每次验证码任务之间随机插入一些额外的鼠标移动和停顿不要每次都直接从页面某点高速移动到滑块位置。5. 实际跑了一个月后的避坑记录5.1 高频问题的排查对照表这一节我直接把我跑了一个多月里遇到的高频问题和最终处理方案列成表格方便你对照排查。现象根因处理方案缺口定位偏左或偏右背景图分辨率与渲染宽度不一致统一按“自然宽度/渲染宽度”比例换算缺口定位有跳变背景图存在强垂直纹理干扰提高阈值系数或缩小搜索区域滑动完成后提示“位置不对”拼图块的初始起算坐标没有扣减用滑块中心坐标减去验证码背景左边界坐标偶尔验证通过但滑块弹回轨迹速度峰值太高或没有末端回拉降低峰值速度增加末端回拉连续多次执行后被标准警告整个会话的鼠标行为太规律在验证码前后增加随机鼠标移动背景图抓到空白图片懒加载未完成等待图片元素完全加载校验字节数这里我想重点提醒坐标起算的坑。很多人在算move_distance的时候直接用缺口中心坐标减去滑块按钮当前坐标这个算法在滑块按钮位于背景图左边缘外的时候是对的但如果滑块按钮有一部分遮住了背景图或者按钮本身有偏移就会导致每次拖动都偏几个像素。最保险的方式是把起始点定义为滑块中心点把目标点定义为背景图左边界加上缺口中心的CSS坐标两者之间做减法。5.2 可复用的执行框架以下是我最终跑通的流程顺序你可以直接照着搭第一步进入验证码面板检测元素可见性。不可见就直接继续主流程。第二步等待图片加载完成下载背景图和拼图块。第三步用OpenCV的垂直梯度算法计算缺口x坐标得到的是背景图自然像素。第四步读取背景图自然宽度和渲染宽度计算缩放比例。第五步读取滑块按钮和背景图的bounding_box计算目标移动距离。第六步生成贝塞尔曲线轨迹。第七步执行鼠标按下、逐点移动、松开。第八步等待验证结果。失败则重置重试。如果把第八步失败后的重试逻辑写好这个框架本身可以迁移到不同的电商平台的滑块验证码上只是缺口定位参数和元素选择器需要重新适配。5.3 如果要提炼一条最重要的经验整个项目做下来我最大的体会是滑块拼图验证码不是靠单点突破就能解决的。我见过不少人花大量时间优化缺口定位算法把准头做到非常极致结果滑动轨迹还是被风控判成机器操作也见过有人只研究轨迹模拟结果缺口一直定位不准折腾半天也过不了。正确的心态是把这个验证码当作一个系统工程图像算法、轨迹模拟、浏览器交互顺序、重试策略四部分都要做到合格线以上。定位精度做到偏差不超过两个像素轨迹自然到看不出规律执行顺序上尊重页面加载节奏通过率才能稳定维持在较高水平。还有一点我想单独说一下不管你的RPA脚本写得多流畅都不要把它用在未经授权的场景里更不要用来处理别人的账号或者绕过平台的安全规则。自动化可以提升效率但前提是你要对自己操作的账号和数据有合法的控制权。技术本身是工具怎么用才决定了它长远的可控性。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑