首页
看点啥
插画图片
首页 看点啥 AI 是如何操作浏览器的——browser use 实现原理

AI 是如何操作浏览器的——browser use 实现原理

2026-07-11 0

AI Agent 操作浏览器的方式分两种。第一种是截图发给 LLM,LLM 返回一个 (x, y) 坐标,让浏览器自动化工具在该坐标模拟点击;第二种是把网页转换成一棵带编号的文本树,LLM 从文本树里选编号,浏览器自动化工具按编号定位到真实的 DOM 元素再执行操作。

目前主流的 AI Agent CLI(Gemini CLI、Claude Code、Codex 等)默认采用的基本是第二种:文本树 + 编号,在需要视觉识别的情况下才使用第一种。本文将配合 x-code-cli 的源码来讲解文本树和视觉这两种实现方式的原理。

为什么需要 browser use

很多 AI Agent 都自带 webFetch 工具来抓取网页内容。但 webFetch 本质上是无状态的 HTTP 请求——没有登录状态、不会执行 JavaScript、也不支持多步交互。例如下面这些场景它就处理不了:

这些正好是能用上 browser use 的场景:通过 Playwright 这类浏览器自动化工具启动一个真实的浏览器,执行登录、渲染、点击、填表等操作,就像人一样操作页面。

反过来,如果只是读一篇公开的静态文章,webFetch 抓取一次内容就足够了;如果是搜索信息,可以使用 webSearch 工具。简单来说:读取静态公开页面内容,webFetch 就够了;如果是需要登录、需要 JS 渲染或者需要多步交互的场景,才需要使用 browser use。

LLM 自动判断

用户不需要自己判断什么时候该用 webFetch,什么时候该用 browser use。LLM 会根据工具描述自行判断当前任务应该用 webFetch 还是 browser use。x-code-cli 里名为 browser 的子 agent 的工具描述是这样写的:

LLM 读到这段描述后,会根据用户的需求自行决定是否委派给这个子 agent。

当然,这主要取决于 LLM 的判断能力和工具描述的质量。如果描述写得足够清楚并且 LLM 是强模型,委派基本都能命中;但如果页面“看起来像静态”或 LLM 偏弱,可能会用 webFetch 导致抓不到内容。

实战演示

下面让我们用一个真实例子来看看实际效果(注意:browser use 不是默认开启的,需要先使用 /browser on 命令手动启用或配置 browser.enabled: true)。

在终端启动 x-code-cli 后输入以下命令:

这个任务 webFetch 工具是完成不了的。去哪儿网是纯 SPA 站点,航班数据靠 JS 动态加载,而且需要先输入出发地、目的地、出发日期,最后点击搜索,这四步交互缺一不可。webFetch 连第一步都过不去,更不用说后面的步骤了。

在上面输入的这条命令里,没有提到“浏览器”也没有提到“browser use”,都是靠 LLM 自己判断应该使用哪个工具。

LLM 根据 browser 子 agent 的工具描述做出判断:SPA、需要多步交互,应该委派给 browser 子 agent。brower 子 agent 会静默启动一个新的 chrome 进程,不会打扰到用户正在进行的活动。

Browser 子 agent 打开去哪儿首页,填表单、点搜索,等搜索结果加载完后获取文本树快照——航班号、时间、价格都在快照里,LLM 直接提取。

LLM 看到的页面:从 HTML 到语义树

继续用去哪儿搜机票这个例子。在浏览器打开首页后,LLM 到底“看到”了什么?

页面里有大量样式、脚本和 DOM 节点,但发给 LLM 的并不是这些原始内容。经过 Playwright MCP server 的转换,LLM 收到的是这样一段 YAML 文本:

- tab "国内机票" [ref=e2] [selected] [cursor=pointer]- tab "国际·港澳台机票" [ref=e3] [cursor=pointer]- radio "单程" [ref=e5] [checked] [cursor=pointer]- radio "往返" [ref=e6] [cursor=pointer]- textbox "出发城市" [ref=e8]: 北京(BJS)- button "换" [ref=e10] [cursor=pointer]- textbox "到达城市" [ref=e12]: 上海(SHA)- textbox "出发日期" [ref=e15]: 2026-07-12- button "搜索" [ref=e20] [cursor=pointer]

LLM 不处理像素,也不解析 HTML,它拿到的是这棵语义树。树里每个可交互元素都带了一个 [ref=eNN] 编号:e8 是出发城市输入框,e12 是到达城市输入框,e15 是出发日期,e20 是搜索按钮。LLM 通过工具调用来操作页面——比如调用 browser_fill_form 把 e8 设为“北京”、e12 设为“上海”,再调用 browser_click 点击 e20,搜索就触发了。搜索完成后页面跳转到结果页,MCP 服务端返回一份新快照,里面是完整的航班列表——厦门航空 MF8561 07:50-09:45 ¥400、东航 MU9192 20:45-23:10 ¥420、中联航 KN5977 20:50-22:55 ¥420...

LLM 直接从中提取出最便宜的 3 个就行了,然后输出到终端。browser use 的大部分逻辑都围绕这棵树展开。后面几节将拆解它是怎么生成的。

两种元素定位方式

这两种方式的区别在于 LLM 通过什么方式获取页面信息、如何定位要操作的元素。

方式 A:截图 + 坐标(视觉 / computer use)

把页面截图发给 LLM,LLM 返回一个 (x, y) 坐标,浏览器自动化工具就在该坐标模拟点击。这种方式必须用多模态模型(支持视觉识别,价格更高),坐标不稳定(页面改版或分辨率变了就失效),而且推理速度慢。但 canvas 渲染的应用、游戏、视频、远程桌面这类内容没有 DOM 语义,文本树是空的,只能采用视觉方式。Anthropic 的 Computer Use、OpenAI 的 Operator 采用的就是这种方案。

方式 B:文本树 + 编号(无障碍树 + ref)

把页面转换成带编号的文本树,LLM 会根据快照中的 ref 编号发起工具调用(如 browser_click 点击 e20 搜索按钮),浏览器自动化工具按编号找到对应的真实 DOM 元素并执行操作。这个方案不需要视觉能力,任何 LLM 都可以做到,包括 DeepSeek 这种纯文本模型。

这两种方式不是二选一的,一般是混合使用:文本树负责常规操作,截图在文本树覆盖不到的场景做补充。

从网页到带编号的文本树

源头:浏览器一直在维护这棵树

每个主流浏览器内核里都维护着一棵无障碍树(accessibility tree)——根据 DOM 树、CSS 计算样式、ARIA 属性综合生成的语义结构。它原本是给屏幕阅读器、语音控制等辅助技术用的,存在了十几年,远早于 AI agent。browser use 直接复用了这种技术:无障碍树天生就是为程序解析页面内容而设计的,给 LLM 用正好合适。

为例,它在无障碍树里会被解析为以下内容:

其中角色和名字是浏览器推断出来的,主要有两个依据:

  1. HTML 原生语义。
  2. ARIA 属性补充。开发者用 role="dialog"aria-label="搜索" 这些属性告诉浏览器“这个
    是个对话框”。ARIA 是补充,不是必需的。

从浏览器里读出这棵树

读取无障碍树需要一个浏览器自动化工具。这里用的是 Playwright——微软开源的浏览器自动化框架,支持 Chrome、Firefox、WebKit,可以用代码控制浏览器做点击、填写、截图、读取页面内容等操作。前端开发者常用它写 E2E 测试,在 AI Agent 场景里它被用来当作操控浏览器的底层引擎。

Playwright 本身是一个 Node.js 库,LLM 不能直接调用它。@playwright/mcp 这个 npm 包的作用就是把 Playwright 的能力包装成 MCP 工具——相当于给 Playwright 套了一层 LLM 能理解的接口。前面 YAML 快照示例里把 HTML 转成语义树的就是它。

我们来看一下整个读取流程:

LLM 调用 `browser_snapshot` 工具↓@playwright/mcp 这个 MCP server 进程收到请求后,调用 Playwright 的 page.ariaSnapshot({ mode: "ai" })↓Playwright 通过 CDP(Chrome DevTools Protocol)发送请求:Accessibility.getFullAXTree↓浏览器内核返回完整的无障碍树(一个巨大的 JSON)↓Playwright 做三件事:1. 裁剪:去掉不可见节点、纯装饰节点2. 编号:给每个可交互元素分配 ref 编号(e1, e2, e3...)3. 序列化:渲染成 YAML 格式文本↓YAML 快照作为工具返回值发送给 LLM

CDP 是 Chrome 暴露给外部工具的调试协议——Chrome DevTools 的检查元素、Network 面板底层用的就是 CDP。Accessibility.getFullAXTree 是 CDP 提供的 API 方法,专门用于读取页面的完整无障碍树,任何能连接 CDP 的工具都能调用。

为什么不直接用 DOM? 因为 DOM 的内容过于庞杂。一个普通页面可能有几千个节点,其中大量的节点是样式容器(

)、SVG 图标、隐藏元素,这些内容对 LLM 来说是无效信息。无障碍树对这些内容进行了过滤:只保留有语义的内容,而且用角色统一了表达方式——不管 HTML 怎么写,按钮就是 button,链接就是 link

序列化:从无障碍树到 YAML

我们来看一下 ariaSnapshot({ mode: "ai" }) 的序列化逻辑:

猜你喜欢