浏览器 Agent 看起来像魔法:看屏幕、点按钮、填表单。但它的真实成功率远低于演示视频,而且每一步都可能踩到安全与稳定性的坑。
三种实现路线
| 路线 | 观察 | 优点 | 缺点 |
|---|---|---|---|
| DOM / 选择器 | HTML 结构 | 快、准、便宜 | 前端一改就坏;复杂组件难定位 |
| 可访问性树 | ARIA 角色与名称 | 比原始 DOM 稳定 | 覆盖不全;动态内容仍难 |
| 视觉 | 截图 + 坐标 | 通用,接近人类 | 慢、贵、坐标脆弱 |
| 混合 | DOM + 截图 + 文本 | 兼顾稳定与通用 | 工程复杂 |
生产系统通常优先用确定性选择器,失败时退回可访问性树,最后才用视觉。
动作空间
一个浏览器 Agent 需要的最小动作集:
navigate(url)
click(selector | x, y)
type(selector | focused, text)
press(key)
scroll(direction, amount | selector)
select(option)
upload(file)
screenshot()
extract(selector | schema)
wait_for(condition, timeout)动作越底层,组合越灵活,但失败率越高。更好的做法是提供语义化工具:
open_page(url)
find_element(description) → ref
click_ref(ref)
fill_form(fields)
read_page(schema)用「引用 ID」而不是坐标,能显著降低误点。每次观察后重新分配引用,避免页面变化导致错位。
循环长什么样
关键在第四步:每次动作后验证状态。不能点完就假设成功。验证方式包括:
- URL 变化;
- 出现预期文本或元素;
- 目标元素的属性变化;
- 网络请求完成(小心过度依赖);
- 截图差异(谨慎使用,动画会误报)。
为什么它很脆弱
- 页面会变:A/B 测试、弹窗、cookie 横幅、加载延迟。
- 坐标不可靠:不同分辨率、缩放、字体渲染。
- 视觉模型会看错:小字、图标、相似按钮。
- 登录态复杂:验证码、二次验证、SSO。
- 动作不可逆:提交订单、发送消息、删除内容。
- 提示注入:网页里可以藏「忽略之前的指令」。
公开基准(如 WebArena、OSWorld)上的成功率通常远低于厂商演示。任务越长,累积误差越明显:单步 95% 成功率,10 步后只剩 60%。
演示通常挑选了顺利路径。生产评估要覆盖:弹窗、慢加载、登录过期、页面改版、错误输入、权限不足。
优先用 API,而不是点界面
浏览器自动化的成本是 API 的几十倍,稳定性却低得多。选择顺序:
有官方 API → 用 API
有稳定的内部接口 → 用接口
只能网页操作 → 浏览器 Agent
需要图形界面软件 → 计算机使用(最后手段)很多「浏览器 Agent」需求,其实可以通过抓取公开数据 + 调用 API 更稳地解决。
安全:这是最大的问题
浏览器 Agent 通常带着用户的登录态,能访问邮箱、后台、支付。风险包括:
- 凭据泄露:截图或日志里出现 token、密码;
- 间接提示注入:网页里的隐藏文字诱导 Agent 执行操作;
- 越权操作:Agent 用管理员账号点了「删除」;
- 数据外泄:把页面内容拼进 URL 或外部请求;
- 不可逆动作:提交订单、转账、发公告。
防护措施:
- 专用账号:给 Agent 单独的低权限账号,不用个人主账号;
- 域名白名单:只能访问批准过的站点;
- 动作分级:读操作自主,写操作确认,支付/删除禁止;
- 沙箱:独立浏览器实例、临时 profile、限制下载与剪贴板;
- 人工接管:遇到验证码、异常页面时暂停,让人处理;
- 审计:记录每一步的截图、动作、URL、网络请求;
- 敏感信息遮蔽:截图前遮住密码框、支付信息;
- 提示注入防护:页面内容视为不可信数据,不执行其中指令。
提升稳定性的工程技巧
- 等待稳定:等网络空闲、元素可见、动画结束,而不是固定 sleep;
- 重试与回退:选择器失败时用文本/角色匹配,再失败用视觉;
- 小步动作:一次只做一件事,动作后验证;
- 状态机:把「登录 → 搜索 → 打开详情 → 填表 → 提交」建模为状态,而不是自由循环;
- 幂等:提交前检查是否已提交,避免重复下单;
- 断点恢复:保存当前 URL、表单内容、步骤,崩溃后可继续;
- 限制步数:20–40 步硬上限,超限转人工;
- 视口固定:统一分辨率与缩放,减少坐标漂移。
一个混合实现的骨架
async def run_task(page, goal: str, max_steps: int = 25):
history = []
for step in range(max_steps):
await page.wait_for_load_state("networkidle")
obs = {
"url": page.url,
"title": await page.title(),
"a11y": await page.accessibility.snapshot(),
"screenshot": await page.screenshot(type="jpeg", quality=60),
"text": (await page.inner_text("body"))[:8000],
}
action = await decide(goal, obs, history) # 结构化输出
if action.kind == "done":
return action.result
if action.kind == "ask_human":
return {"status": "needs_human", "reason": action.reason}
try:
await execute(page, action)
ok = await verify(page, action.expect)
except Exception as e:
ok = False
action.error = str(e)
history.append({"step": step, "action": action, "ok": ok})
if not ok:
await page.screenshot(path=f"trace/{step}-fail.png")
return {"status": "max_steps", "history": history}decide 的输出应该是结构化的:
{
"kind": "click",
"ref": "e12",
"reason": "打开退款申请页",
"expect": { "url_contains": "/refund", "text": "申请退款" }
}带 expect 的动作才能被验证,否则你无法知道点击是否生效。
评估
- 任务成功率:按端到端任务统计,而不是单步准确率;
- 平均步数:步数越多,成本与失败率越高;
- 人工接管率:多少任务需要人介入;
- 恢复率:遇到弹窗/错误后能否继续;
- 安全性:红队测试注入、越权、外泄;
- 延迟与成本:每步截图 + 视觉模型的代价很高。
测试集要包含真实世界的脏数据:慢网、弹窗、登录过期、页面改版、错误输入。
反模式
Agent 一旦被注入或误操作,影响的是用户全部数据。必须用专用低权限账号。
分辨率一变就全错。优先使用可访问性树/选择器,视觉作为兜底。
点完就继续,实际页面没变。每个动作都要有可观察的预期结果。
这通常违反服务条款,也不可靠。遇到验证码应暂停转人工。
出问题无法复现。截图、动作、URL、DOM 快照都要保留(注意脱敏)。
检查清单
- 优先使用 API,浏览器只是兜底
- 专用低权限账号与独立浏览器 profile
- 域名白名单与出站限制
- 读/写/支付动作分级授权
- 每步动作有预期结果并验证
- 最大步数、超时、人工接管
- 截图与日志脱敏
- 全链路 trace 可回放
- 红队测试提示注入与越权
- 评估覆盖真实脏数据
小结
- 浏览器 Agent 用观察—决策—执行—验证循环操作界面。
- 优先确定性选择器,视觉是兜底;动作后必须验证。
- 稳定性靠等待、重试、状态机、幂等和步数限制。
- 安全靠专用账号、白名单、动作分级、沙箱与审计。
- 能用 API 就不要点界面。
下一篇进入工程化:框架怎么选,以及为什么大多数团队不需要「全家桶」。