滑块理论与应用(以阿里云方案为例)
过滑块理论(以阿里云方案为例)
我们能看到的那种验证,基本是一张背景图,左边挂着一块拼图,底下有一条可拖拽的滑条。
1. 滑块是怎么出现的
这个图怎么出现、或者说怎么来的:页面里会加载阿里云的一段 JavaScript(如 AliyunCaptcha.js,名称不固定,但大体就是这类脚本)。脚本自己去下载背景图和拼图,用户拖动时它会计算你的位移差值,拖对了就给你一串密文。
所以写代码的话,核心就是两件事:
- 算出缺口在哪;
- 模拟真人拖动滑块,把拼图拖过去。
find_gap、piece_x这些命名不是固定的,看不懂就扔给 AI。
flowchart LR
A[浏览器打开业务页的 https 地址] --> B[加载官方滑块脚本]
B --> C[拿到背景图和拼图]
C --> D[find_gap 算出原图上的 x]
D --> E["target = x × 屏幕宽 / 原图宽"]
E --> F[按住滑条 反复读拼图位置 拖到 target]
F --> G[脚本把原文放进页面变量]
G --> H[原文交给网站]
由此可以说明:所有那种纯 HTTP 本地解码的说法都是扯淡——除非是打码平台,那个确实可以做到。
另一种思路是:把所有的图片保存下来,一个一个去对比、标注清楚。否则就必须启动浏览器——因为要加载资源、加载环境。”补环境”这操作,其实就是不想启动浏览器才搞的。
(PS:可能我有说得不对的地方,欢迎在评论区指出,给大家解惑。)
2. V2 和 V3 的区别
区别在缺口的图,不在函数叫什么名字。
flowchart TB
subgraph V2[V2 特征]
A1[背景上能看见一块缺口或阴影] --> A2[拼图常常是整块不透明]
A2 --> A3[提取边缘 或 拿整块去对一次]
A3 --> A4[分数最高的位置就是洞]
end
subgraph V3[V3 特征]
B1[洞被抹平 边上还留着原来的颜色] --> B2[拼图是带透明通道的异形]
B2 --> B3[拿颜色去对 会贴到旁边那个真物体]
B3 --> B4["三路: 边 xe、糊 xs、边减内部 xh"]
B4 --> B5["最终 x = 选中的峰 − 左边透明宽度"]
end
一句话总结:V2 直接能看到边缘,拖进去就行;V3 就要注意像素值了。
V2 的处理很简单:
1 | |
V3 中,边的峰和糊的峰通常相差不超过 20 像素,投票时会取它们的平均。
依赖库就用 opencv-python、numpy(OpenCV 的 Python 模块)。
下面这些 id 一般都是官方脚本生成的:
| 元素 | id |
|---|---|
| 背景 | aliyunCaptcha-img |
| 拼图 | aliyunCaptcha-puzzle |
| 滑条 | aliyunCaptcha-sliding-slider |
| 要点一下才出现的区域 | aliyunCaptcha-captcha-body |
| 换一张 | aliyunCaptcha-btn-refresh |
3. 一般流程
下面是一般要注意的流程:
flowchart TD
A[打开浏览器] --> B[用业务页地址打开 只替换这一次网页内容]
B --> C[等官方脚本的初始化函数出现]
C --> D[点一次开始验证]
D --> E{滑条和两张图都出现了?}
E -->|没有 连着几次| Z[这条网络不行 换一条]
E -->|出现了| F[等两张图的地址连续两次不变]
F --> G[按完整地址取出图片字节]
G --> H[find_gap]
H --> I{突出程度 >= 0.085?}
I -->|是| J[只拖主峰]
I -->|否| K[主峰 边减内部 边 糊 最多 3 个点]
J --> L[drag_to]
K --> L
L --> M{松开后还差 3 像素以上?}
M -->|是| B
M -->|否| N{页面变量里有原文?}
N -->|没有 且是超时| Z
N -->|没有| B
N -->|有| P{网站接受?}
P -->|接受| Q[告诉脚本通过 结束]
P -->|拒绝 未满 3 张| B
P -->|拒绝 已满 3 张| Z
注意:点「开始验证」之前,页面上往往只有一个按钮,没有拼图。
4. 页面脚本
1 | |
脚本用官方地址加载,加载完再执行 boot():
https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js
这段代码跑在浏览器页面里,用来拉起阿里云官方滑块脚本,并留三个窗口变量,让外面的程序能拿走验证原文。
三个变量
__cvp:脚本算出的验证原文。第一次成功回调只写一次,避免被后面的空值盖掉。__cerr:脚本自己报失败时,把原因存下来。__cvDecision:外面的程序写"ok"或"bad"。脚本在等这个字,才决定这次验证算过还是算不过。
readParam
官方回调有时直接给字符串,有时给对象。这个函数统一取出里面的 captchaVerifyParam(大小写两种都认)。取不到就整段转成字符串,保证 __cvp 里始终是一段原文。
boot
调用官方的 initAliyunCaptcha,把滑块嵌进页面:
SceneId、region、prefix换成你自己的。element是拼图容器,button是「开始验证」按钮。success:脚本认为本地滑过了,若__cvp还空着,就把原文写进去。captchaVerifyCallback:用户松手后脚本会进这里。先把原文写入__cvp,再每 200 毫秒看一次__cvDecision。外面的程序拿这段原文去问网站,网站返回接受就写"ok",拒绝就写"bad"。脚本收到后才结束这次验证。fail:脚本内部失败,原因进__cerr。
5. 替换网页与取图
替换网页时只拦截 document,其它请求放行:
1 | |
背景图、滑块图的判定标准:都加载完,地址不是空、不是同一张,而且同一对地址连续两次检查都没变。
1 | |
从页面读取后面要用的数:
1 | |
图片字节要用浏览器刚刚下载的那一份,键必须是带问号的完整地址。背景和拼图经常路径相同、只有问号后面不同,砍掉问号会拿到另一张图。
1 | |
IMREAD_UNCHANGED 用来保留透明通道。拼图变成普通彩色图后,后面的形状信息就没了。
6. 缺口怎么算
flowchart TD
IN[背景彩色图 + 拼图四通道图] --> BOX[透明值大于 20 的像素 框出外接矩形]
BOX --> X0[记下矩形左边的空白宽度 x0]
BOX --> YQ{实心高度 >= 拼图高度的 75%?}
YQ -->|是| Y1["y = round(pieceY / scale)"]
YQ -->|否| Y2["y = round(实心顶部 × 背景高 / 拼图高)"]
Y1 --> BAND[只在这条横带里比较]
Y2 --> BAND
BAND --> XE[xe 轮廓对边缘 取最高]
BAND --> XS[xs 拼图内部对细节图 取最低]
BAND --> XH["xh = 边有多像 − 中间有多像 取最高"]
XE --> VOTE[投票]
XS --> VOTE
XH --> VOTE
VOTE --> OUT["x = 选中的峰 − x0"]
6.1 框出拼图形状
1 | |
注意:拖的是整张拼图元素的左边缘,真正的形状在 x0 的右边。最后每个横坐标都要减一次 x0,不能减两次。
横带的上下位置:
1 | |
搜索范围两端留出余量:
1 | |
宽度 300 的图,0.06 × 300 = 18,所以从第 18 列才开始找。
6.2 三路分算
横带切好以后,拼图从左往右扫,每一列打一个分。
1 | |
xe 和 xh 取这一排里最高的那一列,xs 取最低的那一列。最高分如果贴在搜索范围的最左边或最右边,多半是边界,把那一头抹掉再找,最多再找两次。
还要看这个峰比旁边高出多少。把峰左右各挖掉一段,剩下的最高(xs 是剩下的最低)拿来比。挖掉的宽度是拼图短边的一半,最少 6 像素。差越大,这一列越站得住。
1 | |
margin 接近 0,说明旁边还有差不多高的峰。
6.3 三个峰合成一个 x
三路经常不指同一列。先看边和糊差多少。
1 | |
投票逻辑:边和糊相差不超过 20 像素,取它们的中点。这个中点和第三路再相差不超过 16,三路一起平均。拼图宽乘高到了 4500,大块的洞是被涂平的,颜色会认成旁边的真东西,所以改信糊的那一列;面积没到,就用边减内部。
最后换算:
1 | |
举例:峰在第 143 列,左边透明 8 像素,缺口是 143 − 8 = 135,这是原图上的列。页面上背景显示成 260 宽,原图是 300,比例 scale = 260 / 300 ≈ 0.867。鼠标要停的位置是 135 × 0.867 ≈ 117。拼图现在停在 4,还要再走 117 − 4 = 113。
记住:135 是原图上的列,117 才是屏幕上要停的位置。
突出程度不到 0.085 时,这一列不太稳,同一张图多备几个点。按主峰、边减内部、边、糊的顺序收。和已收下的点相差 10 像素以内,当成同一个。小于 12 或大于 290 的丢掉——滑条右边到不了那么远,最多留 3 个。290 是按常见滑轨写的,你的滑条更短就把 290 改小。
换下一个点之前,先把拼图拖回这一轮刚开始时的 pieceX,再从头拖。接着上次停的地方走,位置会对不上。
7. 模拟鼠标拖动
flowchart TD
T["target = x × scale"] --> D[鼠标移到滑条中心并按下]
D --> K{"相差至少 14 像素?"}
K -->|是| K2[先朝目标挪 12 像素]
K -->|否| LOOP
K2 --> LOOP[最多 48 步]
LOOP --> R[读 拼图左边缘减背景左边缘]
R --> S{"还差不超过 1.2?"}
S -->|是| U[松开]
S -->|否| P["这一步 = 还差 限制在 −18 到 18 绝对值至少 0.8"]
P --> LOOP
U --> E{"松开后还差超过 2?"}
E -->|是| A[再拖一次]
E -->|否| W[等原文]
A --> ST{仍大于 3?}
ST -->|是| RE[换一张图]
ST -->|否| W
1 | |
8. 补充
以上基本上都是流程和逻辑。
始终记得:人机验证现在一点都不难了,难的是风控机制。 因为目前所有大厂都在增强风控——以前没有 AI,机器访问并不多,现在越来越多了,所以你会发现各大厂商都在接入人机验证。
关于风控机制,注意这几点:
- 浏览器环境
- IP
- 速率
https://github.com/miku8miku/slider-captcha-lab
阿里云滑块验证码(AliyunCaptcha)自动求解:YOLO 识别缺口 + 真人形态轨迹生成 + CDP 事件注入。
效果
单次通过率 78.4% → **90.9%**,风控拦截率 15.2% → **9.1%**(n=22)。

真人拖滑块不是平滑曲线。用 30 条真人轨迹对比合成轨迹训练分类器,
AUC=1.000 完美可分——教科书式的平滑曲线全是机器指纹:
| 指纹 | 真人 | 平滑曲线 |
|---|---|---|
| 峰值速度位置 | 75% 进度(末端甩鞭) | 5~20%(前载爆发) |
| 峰值速度 | ~9700 px/s | ~1400 px/s |
| y 路径漂移 | 38px(手臂线性漂移) | 2px(小幅振荡) |
| 完全静止帧 | 4% | 0% |
| 松手前微动作 | 0 | 0.25(画蛇添足) |
真人形态是「慢逼近 → 谷底犹豫 → 末端甩鞭 → 收尾」,
本生成器按此重构(细节见 轨迹形态理论.md与 human_track.py 注释)。
使用
1 | |
文件
| 文件 | 说明 |
|---|---|
slider_cdp.py |
求解库:识别 → 轨迹 → 注入 → 判定 |
human_track.py |
轨迹生成器(核心算法) |
track_features.py |
59 维形态特征 |
record_human_drag.py |
真人轨迹采集 |
train_shape_scorer.py / shape_gap_diagnosis.py |
人/机分类器诊断闭环 |
calib_cdp.py |
位移响应模型标定(piece.left = A·m + B·m²) |
判定码:T001 通过 · F001 风控拦截 · F015 位置误差
安装
1 | |
captcha-recognizer提供 YOLO 缺口识别(自带模型权重)- 换站点时先用
calib_cdp.py标定该站的位移响应系数(slider_cdp.py顶部的A_CDP/B_CDP)