跳到主要内容

生产环境中的浏览器 Agent:DOM 脆弱性税

· 阅读需 14 分钟
Tian Pan
Software Engineer

一个日历日期选择器让一个生产环境浏览器 Agent 连续失效三天,无人察觉。设计师在一次小型 UI 改版中,将原生 <input type="date"> 替换为自定义 React 组件。没有 API 变化,没有内容移动,只是新布局中 24px 的单元格——而此前一直可靠点击正确日期的视觉模型,现在偏移了一格,悄悄地把预约订在了错误的日期。

这就是 DOM 脆弱性税:在从未为机器操作而设计的 Web 之上构建自动化 Agent,所持续付出的运营成本。与大多数基础设施税不同,它会复利累积。Web 在变化,反爬虫防御在进化,SPA 越来越动态,而你的 Agent 在悄然退化。

基准测试数据无法让你对此有所准备。顶级系统在精心策划的网页导航任务上宣称准确率超过 90%。而真实生产环境中,面对混乱多变的流量,落地结果接近 50–60%。这个差距不是测量误差——它反映的是受控基准测试无法暴露的结构性失败模式。

浏览器 Agent 到底如何失败

浏览器 Agent 的失败有三种截然不同的方式,理解失败模式才能决定如何修复。

坐标漂移。 基于截图的 Agent 将浏览器窗口转换为图像,通过位置识别目标元素,并在像素坐标处点击。当一个元素移动——哪怕只是因为字体变化、边距增加或相邻元素尺寸变化而偏移 10 个像素——点击就会落错位置。这对紧凑型 UI 尤为致命:日历网格、数据表格和多列表单。即使语义目标完全没变,视觉模型在这里依然举步维艰。

DOM 重构。 基于 CSS 类或 XPath 的选择器,在开发者重构 HTML 时会悄然失效。原来位于 div.sidebar > button.primary 的按钮,经过设计系统迁移后变成了 div.nav-panel > div.actions > button。功能没变,用户体验没变,但 Agent 的定位器停止工作了。这是最古老的 Web 自动化问题,AI 并未解决它——只是将其从显式故障(测试失败)转变为静默退化(Agent 做了错误的事)。

时序与状态假设。 大多数 Agent 隐式假设页面在初始渲染完成时就"就绪"了。SPA(React、Vue、Angular)彻底打破了这个假设。DOM 加载了——但组件还在获取用户数据。按钮渲染了——但点击处理程序还未绑定。搜索框可见了——但自动补全服务尚未初始化。不考虑异步状态的 Agent,要么过早操作未加载的内容,要么在永远不会到来的信号上超时等待。

最难调试的 Bug 会将三者叠加:元素改变了位置,组件发生了重构,页面还是异步加载的。没有任何单一防御策略能同时应对三者;你需要分层防御。

SPA 问题比看起来更糟

单页应用不只是更难抓取——它们在架构上与朴素的浏览器 Agent 假设根本不兼容。

传统 Web 自动化等待 DOMContentLoadedload 事件,然后行动。在 React 应用中,这两个事件在你的实际内容存在之前就触发了。真实数据通过 JavaScript 包执行后的异步 API 调用到达。组件基于这些数据渲染。交互状态在 effect 中初始化。这整个链条都在两个标准"就绪"信号之后运行。

朴素的修复方案——等更长时间——会引发另一个问题。许多 SPA 有持续的后台活动:分析 ping、WebSocket 心跳、周期性数据刷新。等待 networkidle 会无限挂起,因为网络永远不会空闲。你最终得到的 Agent,要么在健康页面上超时,要么与从未为其设计的动态内容赛跑。

正确的做法需要明确的状态验证:等待特定元素出现、特定 API 调用完成或特定 DOM 状态稳定——而不是模糊的网络信号。这需要对每个站点或每种组件类型单独设计 Agent 的等待逻辑,当你在数十个 Web 应用上自动化时,这根本无法规模化。

基于 Canvas 的应用更进一步加剧了这个问题。Google Sheets、Figma 和 Canva 在 HTML5 Canvas 上渲染。没有 DOM 树可检查,没有可访问性节点可查询,也没有 CSS 选择器可用。视觉方法是唯一的选择——但视觉坐标对缩放级别、窗口大小和像素密度极为敏感。在 1x 显示缩放下有效的方法,在 Retina 显示屏上会失效。在默认缩放下有效的方法,在用户放大时会失效。

反爬虫防御正在针对你

反爬虫系统的设计初衷是阻止爬虫和虚假账户。浏览器 Agent 看起来和爬虫一模一样。

Cloudflare 的检测栈结合了 TLS 指纹(验证浏览器的 TLS 实现是否符合真实浏览器模式)、浏览器指纹(Canvas API 渲染、WebGL 输出、音频上下文行为)、行为信号(鼠标移动轨迹、点击时序、滚动速度)和 IP 声誉评分。每个信号都贡献于一个信任分数,决定是放行、发起挑战还是拦截请求。

行为信号层是当前 AI Agent 最一致地失败的地方。人类表现出特征性的移动模式:平滑的光标轨迹、真实的停留时间、偶尔的回退操作。浏览器 Agent 以匀速直线移动,以毫秒精度点击,从不误触错误元素。这些模式在统计上与人类行为可区分,即使没有任何内容分析。

Cloudflare Turnstile 作为验证码的替代品被引入,运行一个非交互式挑战,分析浏览器环境、操作系统特征和交互模式。即使 Agent 成功渲染页面并执行 JavaScript,行为信号也会在任何任务逻辑运行之前触发挑战。

军备竞赛是真实的,而且在加速。规避插件(Selenium Stealth、Puppeteer Stealth、Playwright Stealth)曾有效多年。现在它们被主要反爬虫厂商专门针对和拦截了。新技术出现、被检测、被拦截,整个周期以月为单位。任何将隐蔽性作为主要可靠性策略的 Agent,都在一台永远赢不了的跑步机上。

实际含义:浏览器 Agent 架构需要将反爬虫干扰作为基线失败模式来假设,而不是边缘案例。为被检测设计,而不是为躲避检测设计。

元素定位策略:稳定性谱系

并非所有元素定位器的脆弱程度都相同。稳定性谱系从最脆弱到最稳健排列:

XPath 和 CSS 类选择器是最脆弱的。它们编码的是实现细节——元素层级、CSS 命名约定、DOM 结构——而不是意图。不改变行为的 UI 重构会让这些选择器悄然失效。它们写起来快、执行快,这就是被过度使用的原因。在必须使用这种方法时,优先使用 idnamedata-* 属性而不是结构性选择器。

可访问性树定位器要稳定得多。可访问性树是 DOM 的简化视图,暴露语义含义:元素角色(按钮、标题、文本框)、可访问名称(来自 aria-labelaria-labelledby 或可见文本)和 ARIA 属性。Playwright 的 getByRole() 查询这棵树。一个被新 div 包裹、获得了 CSS 类或改变了样式的按钮,在可访问性树中仍然显示为具有相同可访问名称的 Button。该树对实现变化是稳定的,但对可访问性质量敏感——实现不良的自定义组件通常没有有意义的可访问性语义。

语义定位器(Google 的开源方案)在可访问性的基础上构建了人类可读的语法:{button 'Create'} 定位可访问名称为"Create"的按钮。它们自动强制良好的可访问性标记,对用户不可见的变化有弹性。它们要求底层 HTML 具有语义结构,这排除了许多遗留应用和自定义 Canvas 组件。

带验证的视觉定位器对 HTML 重构最有弹性,但对布局变化最敏感。视觉模型可以识别"右下角的蓝色提交按钮",无论 DOM 结构如何,但当按钮移动、在设计更新中改变颜色或被模态框遮挡时,同样的描述就失效了。关键的补充是验证:点击后,确认预期的状态变化发生了。基于截图的确认——比较操作前后的状态——能捕获点击落点正确但无效的情况。

实践建议:不要选择单一策略。同时使用多种策略,在主要方法失败时沿层级向下回退。视觉识别用于初始元素发现,可访问性树用于稳定交互,当两者都失败时回退到语义匹配。

真正有效的重试逻辑

简单的重试循环会让浏览器 Agent 的失败更糟。一个在同一坐标重试三次失败点击的 Agent,成功率为 0%,延迟代价却是 3 倍。有效的重试需要改变方法,而不仅仅是重复它。

浏览器 Agent 的重试层级应该是策略升级,而不仅仅是时序重复:

  1. 首次尝试:主要定位策略(可访问性树或语义定位器)
  2. 第一次重试:带截图验证的备用定位策略
  3. 第二次重试:滚动确保元素在视口内,重新定位,验证
  4. 第三次重试:页面重载或导航重置,从最后稳定的检查点重新执行
  5. 最终重试/升级:带序列化 Agent 状态的人工接管

重试之间应用指数退避,但需要针对浏览器 Agent 修改。标准指数退避假设底层服务会自行恢复。浏览器自动化失败通常由不会自行解决的状态引起:从未加载的元素、无法清除的反爬虫挑战、阻塞交互的 JavaScript 错误。退避而不改变状态没有帮助。退避应该触发状态验证:页面是否仍处于预期状态?如果不是,在重试之前重置。

操作后验证在生产环境中不可或缺。点击"提交"后,明确检查预期结果是否发生——确认消息出现、页面跳转发生、表单字段清空。没有验证,你无法区分"点击成功且操作完成"、"点击成功但操作静默失败"和"点击落在错误位置"。

优雅降级架构

生产环境浏览器 Agent 需要降级层级,而不是单一策略。当主要方法失败时,系统应该回退到更简单、更可靠的替代方案,而不是让整个任务失败。

Web 自动化的实用降级层级:

  • 带 JavaScript 执行的完整浏览器 → 处理 SPA、动态内容、交互组件
  • 静态 DOM 分析 → 当 JavaScript 执行被阻塞或超时时;处理传统 HTML
  • 纯文本浏览器模拟 → 当 DOM 结构过于不稳定时;提取可见文本内容
  • 明确的人工升级 → 当在定义的重试预算内没有自动化路径成功时

关键架构原则:每一层都应该可以独立测试和独立部署。无法在没有主要视觉模式的情况下操作的 Agent 会让整个任务失败。具有明确回退路径的 Agent 会优雅降级,并告诉你哪一层失败了。

特性检测应该控制能力的使用。在尝试可访问性树策略之前,验证页面是否具有有意义的可访问性标记。在尝试视觉策略之前,验证视口是否已完全渲染。在尝试任何交互之前,验证页面是否处于错误状态。这些预检查代价很低,可以防止在根本无法解决的失败上进行昂贵的重试循环。

安全:无法忽视的问题

通过 Web 内容进行的提示注入是浏览器 Agent 中一个被低估的风险类别。当 Agent 浏览到某个页面时,该页面可能包含旨在重定向 Agent 行为的文本——隐藏在 aria-label 属性中的指令、注入到页面标题中的文本、看起来像系统指令的精心设计内容。

2025 年的一项评估发现,即使是先进的 LLM 也会被真实浏览场景中简单、低成本的注入所欺骗。OpenAI 工程团队确认,浏览器 Agent 中的提示注入在模型层面可能永远无法完全解决——使模型遵循指令的相同能力也使它们容易受到注入指令的影响。

架构层面的回应是将所有 Web 内容视为对抗性输入。在将页面内容传递给 LLM 上下文之前,先进行清理。实施范围限制,定义 Agent 无论指令内容如何都被允许执行的操作——一个预订 Agent 不应该仅仅因为页面告诉它就能执行购买。在具有受限文件系统和网络访问权限的沙箱环境中运行 Agent。记录所有操作,并提供足够的上下文来重建发生了什么以及为什么。

安全约束需要以编程方式强制执行,而不是委托给模型的判断。一个"知道"不应该点击购买按钮的模型,仍然可以通过足够聪明的提示注入被操纵去这样做。代码级的操作门控是唯一抗注入的防御。

基准测试遗漏了什么

Web Agent 的公开基准数字(WebVoyager、WebArena、OSWorld)系统性地高估了生产可靠性,原因有三。

第一,基准环境是静态的。基准任务针对 Web 应用的固定快照运行。生产流量遇到的是实时 Web:A/B 测试改变 UI、基础设施维护页面、速率限制响应、地理访问限制、身份验证挑战。WAREX 可靠性评估发现,引入基础设施故障(正常运营环境)会显著降低在干净条件下取得高分的 Agent 的任务成功率。

第二,基准测试衡量任务完成度,而不是下游正确性。填写表单并提交的 Agent"完成"了任务。填写了错误日期字段并提交了不正确数据的 Agent 按照大多数基准定义也"完成"了任务。生产质量需要衡量是否实现了正确的结果,而不仅仅是 Agent 是否到达了终止状态。

第三,基准任务是孤立的。生产 Agent 在序列中操作,早期错误会限制后续选项。错误的表单提交可能会锁定账户。错误路由的文件操作可能会覆盖现有数据。顺序任务失败具有单任务基准无法捕获的复合成本。

2026 年通用 Web Agent 在多样化、未见应用上的诚实生产基线是端到端任务成功率 50–60%。精心策划基准上的顶级表现者在那些特定任务上达到 90%+。差距就是你的可靠性工作所在的地方。

设计现实

在演示中有效的浏览器 Agent 在生产中失败,因为演示是受控的。生产在默认情况下是对抗性的:Web 在变化,防御在进化,内容是动态生成的,时序是非确定性的,真实用户以你的自动化从未预料到的方式创造状态。

2026 年交付可靠浏览器 Agent 的团队,不是构建更复杂的模型。他们构建更健壮的脚手架:分层定位策略、每次操作后的验证、明确的回退层级、对 Web 内容的对抗性处理,以及能区分"任务完成"和"任务正确完成"的监控。

DOM 脆弱性税是真实存在的。问题在于你是被动地付它——通过生产事故、用户投诉和紧急调试——还是主动地付它,通过在日历日期选择器坏掉之前做出的架构决策。

Let's stay in touch and Follow me for more thoughts and updates