训练你的鼠标行为分身:让 AI 浏览器 Agent 像你一样移动鼠标
2026 年 5 月,arXiv 上线了一篇论文《FP-Agent: Fingerprinting AI Browsing Agents》。研究团队测量了 7 种主流 AI 浏览器 Agent,发现它们的鼠标轨迹、打字节奏等行为特征构成了一组独特的指纹——不仅能把 AI 和人类区分开,甚至能区分不同的 Agent 框架。
更值得关注的是:现有方案的行为一致性导致其容易被归类为自动化流量。
本文从浏览器自动化的开发者视角出发,探讨如何用深度学习让 AI Agent 的鼠标操作风格学你本人,而不是套用一个通用的"人类化"模板。
问题:为什么"人类化"反而成了指纹?
目前主流浏览器自动化框架"人类化"鼠标移动的方式,基本上都长这样:
def move_mouse(x, y):
points = bezier_curve(current_pos, x, y)
for px, py in points:
mouse.move(px, py)
time.sleep(random.uniform(0.01, 0.03))
看起来挺合理?但问题在于:
-
所有用这个框架的用户,生成的是同一类 Bezier 曲线
-
随机抖动服从的是同一个分布
-
过冲(overshoot)触发概率是同一个固定值
如果你跑 1000 个自动化实例,把它们的鼠标轨迹拿出来做聚类分析,会发现它们高度重叠。这就构成了一个行为指纹——检测到一种模式,就能识别出所有用这个框架的实例。
更讽刺的是:你加的"人类化"特征越多,反而越不像人类——因为人类没有统一的"人类化"模式。如果你所有的自动化实例都共享同一套"人类化"参数,那这套参数本身就是一个巨大的集体指纹。
换个思路:让模型学"你",而不是学"人"
如果训练的模型用的是你个人的鼠标操作数据,生成的是带有你个人风格的轨迹,情况就完全不同了:
| 通用人类化方案 | 个人行为克隆 | |
|---|---|---|
| 轨迹分布 | 所有用户共享 | 每人唯一 |
| 检测难度 | 聚一类即识别 | 需要单独为每个人建模 |
| 模型大小 | 无 | ~2MB |
这个思路的核心变化是:不是用更复杂的规则去模拟"人类",而是让模型从你身上学"你"。
数据采集:不侵入地记录你的操作习惯
要做行为克隆,第一步是拿到你个人的鼠标轨迹数据。
实现方式很简单——一个 Tampermonkey 用户脚本,监听 mousemove 事件,记录每次从鼠标移动到点击的完整轨迹。移动距离小于 20px 的视为原地点击,丢弃。因为我们关心的是移动模式,不是点击本身。
数据格式也很朴素:
{
"viewport": {"w": 2018, "h": 1075},
"trajectory": [
{"x": 1480, "y": 322, "t": 0},
{"x": 1504, "y": 317, "t": 31},
{"x": 1501, "y": 319, "t": 69}
],
"target": {"tag": "DIV", "text": "Code"}
}
x/y 是视口坐标,t 是相对于轨迹起点的时间偏移(毫秒)。加上目标 HTML 标签,是因为点击按钮和点击链接的轨迹确实有差异——按钮目标区域大,移动较随意;链接目标小,末端更谨慎。
日常上网几天,就能采集到几百到上千条轨迹。然后 Tampermonkey 菜单里一键导出 .jsonl 文件,放到项目 data/ 目录下。
架构:三个模型,各管一件事
拿到数据后,最初的方案是训练一个大 GRU 模型,端到端地预测空间和时间。但实验发现这效果不好——模型要么把空间学得很光滑(丢失个人化的弧线风格),要么把时间学得很均匀(丢失加速减速的节奏)。
后来把问题拆成了三个模块:
Bezier (骨架) → NoiseModel (空间偏离) → GRU (时序)
Bezier 曲线负责生成宏观骨架——它是个固定算法,不学任何东西,只保证从起点到终点有一条合理的路径。
NoiseModel 是一个小 GRU(~166KB),接收 Bezier 控制点,输出你个性化的 (x, y) 路径。它学的是你偏离理想路径的方式——你习惯走什么弧度、你抖动多大。
GRU(~2MB)接收 NoiseModel 输出的空间路径,只预测每个点什么时候到达——哪里快、哪里慢、哪里犹豫。
拆开后效果好了很多。直觉上也说得通:你走什么弧线和你在哪个点加速/减速,本来就是两回事。分开学,每个模型更纯粹。
NoiseModel 怎么学空间偏离
NoiseModel 的输入是 Bezier 曲线的 8 个参数(起点、终点、两个控制点的坐标,归一化到视口)。它自回归地生成一系列 (x, y) 点。
训练时前 5 轮纯用真实轨迹点"喂"模型(teacher forcing),之后逐渐减少,最终完全让模型自己预测。这样它既学到了真实数据中的模式,又能在推理时独立生成路径。
一个细节:轨迹末端 20% 的点,训练时给 4 倍的 loss 惩罚。因为末端接近目标时的减速微调阶段,是区分真人和机器最明显的特征——机器往往直挺挺抵达,而人会有细微的过冲和修正。
GRU 怎么学时序
GRU 的输入是每步的"相对空间"特征:距目标还有多远、上一步移动了多少、上一步耗时、当前进度。不是绝对坐标——因为人类移动鼠标时,大脑处理的不是"光标在屏幕上的像素坐标",而是"目标还在那个方向,大约多远"。
时间处理上有个坑:原始数据中,相邻点的时间差从 8ms 到 3494 毫米都有(极端异常值)。直接训练会被这些异常值主导。解决方式是 log1p 变换——把范围压缩到 [0, 8.8],训练完再用 expm1 反变换回去。
还有一个实用的发现:模型倾向于预测比实际慢的时间。训练完后加一个 0.70 的缩放因子,生成的总时长就匹配真实中位数了。
为什么不用 Transformer
有人可能会问,现在不都 Transformer 吗?选 GRU 有三个原因:
-
轨迹是强连续的单向序列——GRU 的归纳偏置天然匹配
-
推理时要自回归生成,每步只依赖前一步——GRU 比 Transformer 轻量得多
-
数据量小(几百条)——GRU 在小数据上泛化更好
训练:几百条数据够用吗?
坦诚说,几百条数据是勉强够用的水平。理想情况是每人数千条以上。
实际操作中,两个模型全部在真实数据上训练,不依赖合成数据。训练策略是把有限的几百条数据重复加权(默认 ×10),告诉优化器"优先拟合这些真实样本"。
训练速度也还可以:GRU 单 epoch 在 GPU 上大约 45 秒,200 轮不到 3 分钟。CPU 也能跑,多等一会儿。
最终产出:NoiseModel ~166KB,GRU ~2MB。两个文件加起来不到 3MB,CPU 推理延迟 5ms 以内。
一个容易被忽略的细节:事件率
GRU 生成的轨迹通常只有 30 个点,时间间隔大约 27ms——相当于 37Hz 的采样率。
但真实鼠标的采样率是多少?浏览器通过 mousemove 捕获,通常是 60Hz 左右(~16ms)。而实际鼠标硬件的采样率通常是 125Hz(~8ms)。
30 个点看起来太稀疏了。更关键的是:如果你的自动化每次都产生恰好 30 个点,这是一个非常明显的人工指纹。真实人类的鼠标移动,事件数不是固定的——700px 的移动,手快时可能只产生 20 个有效事件,手慢或精细调整时可能产生 80 个。
所以在 GRU 输出后面加了一层重采样——把模型预测的时序轮廓"翻译"成真实的鼠标事件率:
-
自适应间隔:快速阶段稀疏(~14ms),减速阶段密集(~4ms)
-
±3ms 时间抖动:模拟硬件采样噪声
-
±0.3px 空间抖动:模拟传感器精度
-
可变点数:同一起终点每次生成 20-80+ 个事件
这个步骤只做"翻译",不改变模型预测的速度曲线。模型说"这个动作 800ms、中间加速、末尾减速",重采样只是把这句话拆分成不规则间隔的事件点。
效果:看起来怎么样
光说无益,直接看对比。
左图是三阶段 Pipeline 生成的轨迹(Bezier 骨架 + NoiseModel 空间偏离 + GRU 时序),右图是纯贝塞尔曲线。可以看到 Pipeline 的轨迹不是光滑的数学曲线——它有个人化的弧线偏离、速度不均匀、末端有细微的犹豫。
更直观的是动态对比:
-
Mechanical(灰色):瞬移到终点,纯算法
-
Bezier(蓝色):平滑的匀速滑行,还是算法
-
GRU Model(金色):有加速、犹豫、末端微调——模型从真实数据中学到的风格
怎么用
整个流程跑一遍:
# 1. 安装
pip install torch numpy matplotlib
# 2. 把你导出的 .jsonl 文件放到 data/ 目录
# 3. 训练 NoiseModel(空间个性化)
python training/generate_trajectories.py --train-noise --epochs 100
# 4. 训练 GRU(时序个性化)
python training/train_mouse_model.py --epochs 200
# 5. 看看效果
python examples/demo.py --save output.png
python examples/animate_demo.py --save output.gif
集成到浏览器自动化:
from mouse_controller import move_to_humanized
# 在点击之前
await move_to_humanized(page, target_x, target_y, tag="BUTTON")
# 然后执行点击
await page.click(target_x, target_y)
如果模型没加载,框架自动降级为 Bezier 曲线——不影响使用。
局限性
坦诚地说,目前有几个局限:
数据量。 几百条勉强够用,理想是每人数千条以上。但好消息是这是一个正向飞轮——数据越多模型越像你。
NoiseModel 的假设。 当前假设"真实 = Bezier + 残差",这个假设偏强。更好的做法是用生成式模型(diffusion 或 VAE)直接生成全轨迹。
多模态。 鼠标轨迹只是行为指纹的一个维度。键盘节奏、滚动模式、鼠标停留时间等都没建模。比如键盘节奏,在 data/ 下加一个 keyboard.jsonl,格式仿照 trajectory,调整 training/ 下的脚本即可复用现有 GRU pipeline。
但这些局限本身也是后续迭代的方向。
写在最后
2026 年的 AI 浏览器 Agent 生态正在快速成熟,操作准确率已经不是核心瓶颈。下一个瓶颈是信任——平台不信任 AI 流量,用户不信任机械化的操作。
我始终觉得,AI 操作电脑的未来不应该是"一个通用的机器人模拟一个通用的真人",而应该是你的 AI 助手,操作风格像你,决策偏好像你,行为特征也像你。
本文所有代码和数据仅供技术研究与个性化 AI 研究之用。
论文引用:FP-Agent: Fingerprinting AI Browsing Agents, arXiv:2605.01247, May 2026