跳到主要内容

Browser Use — 架构与原理

30 秒导读: Browser Use 让 LLM 能真正操作浏览器——订机票、填表单、抓数据。它的核心招数是:把一张网页里成千上万个 DOM 节点,过滤、压缩成一份带编号的「可点元素清单」([14]<button>登录</button>),连同截图一起喂给模型;模型回一串结构化动作(click index=14);程序按编号去查一张 selector_map 表,找到真实元素,再用 Chrome DevTools Protocol(CDP)把点击/输入落下去。如此循环,直到模型调用 done

本项目较大(两套并存的架构 + 感知/行动/浏览器三大子系统),所以拆成多文件。本页是 Layer 0(这是什么)+ Layer 1(顶层全景)+ 阅读地图;各机制见分章。


1. 这是什么(零基础也能懂)

一句话定义: Browser Use 是一个让大语言模型(LLM)像人一样「看网页、点按钮、填表单」来完成任务的 Python 框架。

解决什么问题 / 给谁用:

假设你想让 AI 帮你「去 GitHub 查一下 browser-use 仓库有多少 star」。LLM 本身没有手脚——它不能点鼠标、不能读浏览器里的网页。Browser Use 补的就是这副「手脚 + 眼睛」:

  • 眼睛:把当前网页变成 LLM 读得懂的文字 + 截图。
  • 手脚:把 LLM 说的「点那个登录按钮」翻译成真实浏览器里的一次点击。
  • 大脑的循环:一步步「看→想→做」,直到任务完成。

给谁用:写自动化脚本的工程师、做 RPA(流程自动化)的团队、需要让 agent 上网查资料/操作 SaaS 的产品。

它能做什么(功能):

  • 导航网站、点击、输入、滚动、切换标签页、下载文件
  • 从整页里抽取结构化信息(extract 动作)
  • 自带文件系统(让长任务把进度、结果写进 todo.md / results.md)
  • 支持十几家 LLM 提供商(OpenAI、Anthropic、Google、Groq、Ollama……)
  • 多种内置「看门狗」处理 CAPTCHA、弹窗、崩溃、下载、权限

用起来什么样: 一段最小的真实调用(摘自 README.md,beta 入口):

from browser_use.beta import Agent, BrowserProfile, ChatBrowserUse
import asyncio

async def main():
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(model='openai/gpt-5.5'),
browser_profile=BrowserProfile(headless=False, allowed_domains=["*.github.com"]),
)
await agent.run()

asyncio.run(main())

你给一句话任务 + 选一个模型,agent.run() 就会自己开浏览器、一步步操作直到拿到答案。

一句话直觉/类比: 把 LLM 想象成一个只能用文字交流、看不见屏幕的盲人操作员。Browser Use 是他的助理:每一步把屏幕「念」给他听(「第 14 个是登录按钮,第 7 个是用户名输入框……」),听他下指令(「点 14 号」),然后替他动手。编号是关键——盲人操作员没法说「点屏幕左上那个蓝色的」,但能说「点 14 号」。


2. 顶层全景(它大概怎么转)

两套架构并存

读这个仓库第一件要分清的事:它现在有两套 Agent 实现,共用底层的 LLM 适配器、DOM、工具:

架构入口做什么状态
经典版from browser_use import Agentbrowser_use/agent/service.py纯 Python 实现的「感知→思考→行动」主循环,DOM 序列化 + CDP 直连成熟、源码完整、最能体现原理
beta 版from browser_use.beta import Agentbrowser_use/beta/service.pyPython 薄壳,通过 JSON-RPC 驱动一个 Rust 写的浏览器核心(browser-use-terminal 二进制)0.13 新引入,面向前沿模型

本套文档主讲经典版(原理最完整、源码可读),beta 版单列一章(§05)说清它怎么把活外包给 Rust 核心。

经典版主线:一步的生命周期

这张图从上到下是「一个 step 内发生的事」,读法:输入是当前网页,输出是真实浏览器里的一次操作。

┌─────────────────────────────────────────────────────────────┐
│ Agent.run() 主循环 │
│ while n_steps <= max_steps: 反复调用 step() │
└─────────────────────────────────────────────────────────────┘
│ 每一步 step()

① 感知 get_browser_state_summary()
真实 DOM ──► 过滤/压缩 ──► 带编号元素清单 + 截图
(DOMWatchdog + DOMTreeSerializer)


② 组装 MessageManager.create_state_messages()
system prompt + 历史 + 元素清单 + 截图 ──► 一串消息


③ 思考 llm.ainvoke(messages, output_format=AgentOutput)
LLM 返回结构化 JSON:{thinking, next_goal, action:[...]}


④ 行动 multi_act(actions)
对每个动作:按 index 查 selector_map ──► dispatch 事件
──► watchdog 用 CDP 真点/真输入
(页面变了就停掉队列里剩下的动作 = 护栏)


⑤ 收尾 记录结果/失败计数/检测死循环;若动作是 done ──► 退出

部件一句话职责

部件干什么在哪个文件
Agent拥有主循环 run() 和单步 step(),串起感知/思考/行动browser_use/agent/service.py
MessageManager把状态拼成 LLM 消息、管历史、按需压缩上下文browser_use/agent/message_manager/service.py
DOMTreeSerializer把增强 DOM 树压成 [index]<tag> 文本清单browser_use/dom/serializer/serializer.py
ClickableElementDetector判断一个节点到底算不算「可交互」browser_use/dom/serializer/clickable_elements.py
Tools / Registry注册所有动作、把它们变成 LLM 的动态 schema、分发执行browser_use/tools/service.pytools/registry/service.py
BrowserSession持有 EventBus,管标签页/CDP 连接,是浏览器的总线browser_use/browser/session.py
*Watchdog订阅事件、用 CDP 真正干活(点击/输入/截图/弹窗……)browser_use/browser/watchdogs/*.py
BaseChatModel 及各 Chat*统一的 LLM 调用协议 + 各家适配器browser_use/llm/base.pyllm/*/chat.py

3. 阅读地图(建议顺序)

按「由浅入深」读:

  1. 01-agent-loop.md — 先看主循环。一步怎么走完(step() 的三阶段)、多动作执行的「页面变了就停手」护栏、失败计数与死循环检测。先读这章建立全局节奏感。
  2. 02-dom-perception.md — 感知层。一棵几千节点的真实 DOM,怎么经过「判可交互→去重→bbox 过滤→分配编号」四步压成 LLM 看得懂的清单。这是项目工程含量最高的部分。
  3. 03-tools-and-actions.md — 行动层。@action 装饰器、create_action_model 怎么用 pydantic 动态拼出一个「只能选一个动作」的 schema,从而逼着 LLM 只输出合法动作;done 动作与结构化输出。
  4. 04-browser-cdp-events.md — 底层。EventBus + watchdog 的事件驱动架构,一次「点 14 号」如何 dispatch 成 ClickElementEvent、再由 watchdog 翻成 CDP 的 Input.dispatchMouseEvent
  5. 05-beta-rust-core.md — 新架构。beta Agent 如何 spawn 一个 Rust 二进制、用 stdio JSON-RPC 通信,把感知/行动外包出去。

4. 巧妙之处(先尝个味,细节见各章)

  • 用「编号」当 LLM 和真实 DOM 的握手协议。 LLM 只需说「点 14 号」,程序查 selector_map[14] 拿到真实节点。编号是稳定、无歧义的指针,避开了「让 LLM 描述坐标/CSS 选择器」的不可靠。(§02)
  • *[ 前缀标记「新出现的元素」。 上一步输入后弹出的自动补全下拉项,会被标星,提示 LLM「这是你刚才动作引出来的,考虑要不要点它」。(§02 / system prompt)
  • 多动作的双层页面变化护栏。 一个 step 可以下多个动作,但只要某个动作让 URL 或焦点变了(运行时检测),或动作本身被标记 terminates_sequence(静态标记,如导航),就立刻放弃队列里剩下的动作——因为后面那些动作是基于「旧页面的编号」算的,页面一变编号就失效了。(§01)
  • 动态 pydantic Union 当 schema 护栏。 每一步根据当前页面可用的动作,现拼一个 Union[ClickActionModel, InputActionModel, ...],交给 LLM 的结构化输出约束——模型从语法上就没法编造不存在的动作。(§03)

5. 边界与局限

  • beta 的 Rust 核心不在本仓库。 browser-use-terminal 是个预编译二进制,Python 侧只是 JSON-RPC 客户端;它的内部动作循环看不到源码(§05 如实说明)。
  • 感知依赖 Chrome 的 CDP / AX 树。 大量「可交互判定」靠 Chrome 给的 cursor style、accessibility 属性、JS 监听器探测(clickable_elements.py)——非 Chromium 内核或重度自定义渲染的页面会有盲区。
  • 编号是「这一步快照」的。 页面一变编号全废,所以才需要护栏;这也意味着高度动态的 SPA 上 agent 容易「点到过期编号」,代码里对此有 Element index N not available 的兜底。

6. 横向对比(browser-agents 这一类)

Browser Use 的取舍是**「DOM 文本 + 编号」为主、截图为辅**:省 token、对纯文本模型也友好,但对纯视觉/坐标驱动的玩法(直接让模型点像素坐标)只作为可选项(set_coordinate_clicking,仅对支持坐标的前沿模型开启,见 §03)。这与「纯截图 + 坐标」的 computer-use 类 agent 是两条路线:前者结构化、可解释、便宜;后者通用、不挑页面但贵且易抖。

7. 代码地图(导航索引)

主题文件关键符号
主循环browser_use/agent/service.pyAgent.runAgent.stepAgent.multi_act
单步取模型输出browser_use/agent/service.pyAgent.get_model_outputAgent._get_next_action
LLM 输出结构browser_use/agent/views.pyAgentOutputAgentBrainActionResult
DOM 序列化browser_use/dom/serializer/serializer.pyDOMTreeSerializer.serialize_accessible_elementsserialize_tree
可交互判定browser_use/dom/serializer/clickable_elements.pyClickableElementDetector.is_interactive
动作注册browser_use/tools/registry/service.pyRegistry.actionRegistry.create_action_model
动作分发browser_use/tools/service.pyTools.actRegistry.execute_action_click_by_index
浏览器总线browser_use/browser/session.pyBrowserSession.get_browser_state_summary
点击落地browser_use/browser/watchdogs/default_action_watchdog.pyon_ClickElementEvent
beta Rust 客户端browser_use/beta/service.pyRustSdkClientAgent._run_sdk_agent