一个让大语言模型直接操控 Chromium 浏览器的 Python 库,只需告诉它”做什么”,它自己决定”怎么做”。
一、项目概览
| 维度 | 信息 |
|---|---|
| 项目名称 | browser-use |
| 版本 | 0.12.6 |
| 作者 | Gregor Zunic |
| 许可证 | MIT |
| 语言 | Python >= 3.11 |
| 核心依赖 | cdp-use(CDP 协议封装)、pydantic(数据验证)、bubus(事件总线) |
| 代码规模 | 约 64,000 行 Python 代码,176 个源文件 |
| 测试覆盖 | 78 个测试文件(CI 自动运行) |
核心定位
browser-use 解决的核心问题很简单但很深刻:大语言模型擅长理解和推理,但它们没有”手”——无法直接操作 GUI。这个库做的事情就是给 LLM 装上一双手,让它可以点击按钮、填写表单、滚动页面、提取数据,而且是在一个完整的 Agent 循环中自主决策,而不是执行预设脚本。
技术栈
| 组件 | 技术 |
|---|---|
| 浏览器协议 | CDP(Chrome DevTools Protocol) |
| 事件总线 | bubus |
| 数据验证 | pydantic v2 |
| CLI 解析 | click |
| 终端 UI | rich |
| 类型检查 | pyright |
| 代码质量 | ruff |
真实数据
项目提供了 100 个真实浏览器任务的基准测试,覆盖表单填写、数据抓取、导航流程等场景。完整的基准测试开源在 browser-use/benchmark。
二、核心设计理念
“Describe What, Not How”
传统浏览器自动化工具(Selenium、Playwright)要求开发者写出精确的操作步骤:找到某个 CSS 选择器,点击它,等待某个元素出现,输入文本。browser-use 把这个过程完全反转:你只需要描述目标(”找到最新的 Show HN 帖子”),LLM 自己决定如何导航、如何识别元素、如何提取信息。
这一设计哲学的代码体现是 Agent.run(task)——用户传入自然语言,库返回执行结果。中间的所有决策都委托给 LLM。
CDP 而非 Playwright
browser-use 选择直接通过 Chrome DevTools Protocol 与浏览器通信,而不是在 Playwright 之上构建。原因有三:更细粒度的 DOM 控制(可直接获取 DOMSnapshot、Accessibility Tree、computed styles 等多棵树的融合信息);更低延迟(少一层抽象);更好的可观测性(CDP 原生支持网络追踪、性能分析、截图录制)。
事件驱动架构
整个浏览器管理围绕 bubus 事件总线构建。BrowserSession 不是直接调用方法,而是 dispatch 事件,由独立的 Watchdog 组件监听和处理。这种设计让各个关注点(DOM、下载、安全、弹窗)完全解耦。
三、架构总览

browser-use 的架构可以分解为四个核心层次,从用户-facing 到基础设施:
第一层:Agent 编排层
主循环驱动(run → step → multi_act),负责 LLM 交互(含重试、降级、超时)、规划系统(plan_update、todo.md)、循环检测与错误恢复、历史录制与回放。
第二层:Tools 工具注册中心
约 20 个内置动作(click、input、navigate 等),支持依赖注入(browser_session、file_system 等)、自定义工具扩展(@tools.action 装饰器)、结构化输出支持。
第三层:BrowserSession + Watchdogs
CDP WebSocket 连接管理,12+ 个 Watchdog 独立处理各关注点(标签页、下载、安全、弹窗、验证码),支持云浏览器(Browser Use Cloud)。
第四层:DomService + CDP
多树融合(DOM + Snapshot + AX Tree),可见性检测、Shadow DOM 支持,跨域 iframe 处理,序列化输出给 LLM 消费。
四、数据流:一次 Step 的完整生命周期
理解 browser-use 的关键在于跟踪一个 step 从开始到结束的完整过程:
用户任务
│
▼
┌─────────────────────────────────┐
│ 1. 上下文准备 │
│ get_browser_state_summary() │
│ EventBus → DOMWatchdog │
│ 并行 CDP: DOMSnapshot + AX Tree │
│ → EnhancedDOMTreeNode │
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 2. 序列化 │
│ DOMTreeSerializer │
│ → XML 树格式(带数字索引) │
│ [33]<div /> / *[38]<button /> │
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 3. LLM 决策 │
│ 输入: 浏览器状态 + 历史记录 │
│ 输出: thinking + action[] │
│ JSON: {thinking, evaluation, │
│ memory, next_goal, action}│
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 4. 动作执行 │
│ Tools.act() → 页面变化守卫 │
│ 静态标记(terminates_sequence) │
│ 运行时检测(URL/焦点变化) │
│ dispatch 事件 → BrowserSession │
└──────────────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ 5. 后处理与循环 │
│ 更新 AgentHistory │
│ done() 或继续下一步 │
└─────────────────────────────────┘
理解了这条主线之后,以下四个独特设计分别对应数据流中的关键节点:Watchdog 架构对应步骤 1 的浏览器状态捕获,多树融合 DOM 对应步骤 1-2 的数据融合与序列化,Agent 智能控制贯穿步骤 3-5 的决策与执行循环,LLM 提供商无关性支撑步骤 3 的模型选择灵活性。
五、独特设计深度解析
5.1 Watchdog 架构——浏览器状态的”守护者”
问题:浏览器是一个复杂的状态机,包含导航、下载、弹窗、安全策略、验证码等多种关注点。如果用一个大而全的 Browser 类管理所有事情,代码会迅速膨胀且难以维护。
机制:browser-use 用 12 个独立的 Watchdog 各自处理一个关注点,通过事件总线与 BrowserSession 解耦。
| Watchdog | 职责 | 代码量 |
|---|---|---|
| DOMWatchdog | DOM 捕获、截图、元素高亮 | 33.9 KB |
| DownloadsWatchdog | PDF 自动下载、文件管理 | 51.7 KB |
| DefaultActionWatchdog | 默认动作执行(click、input 等) | 128.5 KB |
| LocalBrowserWatchdog | 本地浏览器启动管理 | 18.4 KB |
| StorageStateWatchdog | Cookie 和 localStorage 持久化 | 13.1 KB |
| AboutBlankWatchdog | 空白页面重定向 | 9.1 KB |
| SecurityWatchdog | 域名限制、安全策略 | 8.9 KB |
| CaptchaWatchdog | 验证码检测与处理 | 7.2 KB |
| PopupsWatchdog | JavaScript 弹窗、弹出窗口 | 6.4 KB |
| RecordingWatchdog | 视频/HAR 录制 | 6.3 KB |
| ScreenshotWatchdog | 截图录制 | 3.4 KB |
| PermissionsWatchdog | 浏览器权限管理 | 1.4 KB |
当导航发生时,NavigateToUrlEvent 被 dispatch,多个 Watchdog 可以同时响应——AboutBlankWatchdog 检查是否是空白页,DownloadsWatchdog 检查是否有自动下载,SecurityWatchdog 检查域名是否在允许列表中。
Trade-offs:解耦带来的优势是清晰的关注点分离和易于扩展(添加新的安全策略不需要修改核心代码);代价是事件流的可追溯性降低,调试时需要跟踪多个 Watchdog 的交互。
5.2 多树融合的 DOM 表示
问题:大多数 AI 浏览器工具只给 LLM 提供 HTML 文本或 accessibility tree,丢失了大量有用信息(可见性、绘制顺序、计算样式)。
机制:_construct_enhanced_node() 方法递归构建增强节点,每个 EnhancedDOMTreeNode 同时包含四棵树的融合信息:DOM 树(标签名、属性、子节点)、DOMSnapshot(计算样式、背景色、可见性、绘制顺序)、Accessibility Tree(角色、名称、描述、状态)、可见性(CSS 可见性、视口边界、iframe 裁剪)。
融合的关键难点在于坐标转换——DOMSnapshot 使用视口坐标,Accessibility Tree 使用屏幕坐标。_construct_enhanced_node() 在递归过程中维护一个坐标变换矩阵,处理 iframe 嵌套和滚动偏移。
最终序列化为一种特殊的 XML 树格式:
[33]<div />
User form
[35]<input type=text placeholder=Enter name />
*[38]<button aria-label=Submit form />
Submit
只有带数字索引的元素才是可交互的;缩进表示父子关系;* 标记表示上一步操作后新出现的元素。
Trade-offs:信息丰富让 LLM 获得比纯 HTML 更丰富的上下文(可见性、语义角色、遮挡关系);代价是 token 消耗更大。
5.3 Agent 循环的智能控制
问题:简单的”调用 LLM → 执行 → 重复”循环在复杂任务中容易陷入死循环、无法从错误中恢复、或者在达到最大步数时没有有意义的结果。
机制:browser-use 内置了多个智能控制层:
规划系统:对于简单任务(1-3 步)直接执行;对于复杂任务,LLM 输出 plan_update 创建 3-10 个 todo 项;对于目标不清晰的任务,先探索几步再制定计划。计划项带有状态标记:[x] 完成、[>] 当前、[ ] 待处理、[-] 跳过。
循环检测:Agent 跟踪最近的行为模式,如果检测到在同一个 URL 上重复执行相同动作且没有进展,会注入”循环检测提示”,引导 LLM 尝试不同的方法。
错误恢复:连续失败达到阈值(默认 3 次)时注入”重新规划提示”。达到最大步数时尝试强制进行最后一次 LLM 调用来输出中间结果。
页面变化守卫:当每步允许多个动作时(默认最多 3 个),有两层保护:静态标记(terminates_sequence)和运行时检测(URL/焦点变化)。
历史回放:rerun_history() 可以用保存的历史记录重放任务。元素重新匹配使用五级级联:EXACT hash → STABLE hash → XPATH → AX_NAME → ATTRIBUTE。
Trade-offs:智能控制提高了复杂任务的完成率;代价是 Agent 逻辑复杂度显著增加(agent/service.py 达 4,109 行),理解和调试难度较高。
5.4 LLM 提供商无关性
问题:LLM 市场快速变化,被锁定在单一提供商意味着无法利用新的模型或更好的定价。
机制:BaseChatModel 抽象层支持 15+ 个提供商:OpenAI、Anthropic、Google、Browser Use 自有模型、Groq、DeepSeek、Mistral、Cerebras、Ollama(本地)、OpenRouter、Azure、AWS Bedrock、OCI、Vercel AI SDK、LiteLLM。每个提供商有独立的序列化器(serializer),将内部的 message 格式转换为提供商特定的 API 格式。
Browser Use 还推出了自己的模型 ChatBrowserUse(),专为浏览器自动化优化,定价为输入 0.20 美元/百万 tokens、输出 2.00 美元/百万 tokens。
Trade-offs:灵活性让用户可以随时切换模型;代价是需要维护 15+ 个序列化器,增加了代码维护负担。
六、browser-use vs agent-browser 对比
在 AI 浏览器自动化领域,agent-browser(Vercel Labs 出品,纯 Rust 实现的 CLI 工具)与 browser-use 解决同一问题,但走了完全不同的路线。
核心差异:Autonomous Agent vs Deterministic CLI
这是两者最根本的区别:
| 维度 | browser-use | agent-browser |
|---|---|---|
| 交互模式 | LLM 自主决策循环 | 人类/AI 发送命令 → 确定执行 |
| 决策权归属 | LLM 决定下一步做什么 | 调用者决定每一步做什么 |
| 执行范式 | “告诉我目标,我自己完成” | “告诉我点击哪个 ref,我精确执行” |
| 核心抽象 | Agent.run(task) | agent-browser click @e2 |
| 状态保持 | Agent 对象维护完整历史 | Daemon 进程维护浏览器状态 |
用一个类比:browser-use 像自动驾驶汽车——输入目的地,它自己决定路线;agent-browser 像带 GPS 的手动挡汽车——给你精确的地图和仪表盘,但方向盘在你手里。
架构对比
| 维度 | browser-use | agent-browser |
|---|---|---|
| 语言 | Python >= 3.11 | Rust |
| 代码量 | ~64,000 行 | ~47,000 行 |
| 浏览器协议 | CDP(通过 cdp-use 封装) | CDP(纯 Rust 实现) |
| 进程模型 | 单进程异步 | CLI + Daemon 分离 |
| 页面表示 | 多树融合 XML(DOM + Snapshot + AX) | Accessibility Tree snapshot(ref 标记) |
| 元素标识 | 数字索引 [33] | ref 引用 @e1 |
| 事件架构 | bubus 事件总线 + Watchdog | 直接 CDP 命令/响应 |
| 扩展方式 | Python 装饰器 + MCP | CLI 命令 + Skill 系统 |
页面状态表示方式的差异
这是两者技术路线差异最集中的体现。browser-use 把三棵树融合成一个增强节点,优势是信息丰富(可见性、绘制顺序、语义角色),代价是 token 消耗更大。agent-browser 只输出 accessibility tree 带 ref 引用,优势是简洁、确定性高(ref 实时分配,不存在选择器过时),代价是丢失了样式和布局信息。
速度对比
| 场景 | browser-use | agent-browser |
|---|---|---|
| 首次启动 | 秒级(Python 进程 + 浏览器启动) | 秒级(Daemon 启动 + 浏览器启动) |
| 单步延迟 | LLM 调用 1-5 秒 + 浏览器操作毫秒级 | 50-200ms(纯 CDP 命令,无 LLM) |
| 批量操作 | 每步都需要 LLM 调用 | batch 命令单次调用执行多条命令 |
在纯浏览器操作速度上 agent-browser 有明显优势;但在需要理解页面语义、自主决策的场景中,browser-use 的 LLM 循环是不可替代的。
选择建议
选择 browser-use,如果: 你需要自主决策(任务目标明确但路径不确定)、复杂的多步推理(比较、筛选、判断)、Python 技术栈集成、文件操作/MCP 集成/自定义工具、生产级部署(Sandbox、云浏览器、Cookie 同步)。
选择 agent-browser,如果: 你需要确定性的精确控制、极低延迟(批量操作、高频交互)、多引擎支持(Chrome、Lightpanda、Safari/iOS)、纯 CLI 接口(脚本、CI/CD、shell 管道)、内置安全策略(域名白名单、操作策略文件、认证保险箱)。
两者可以配合使用: 用 browser-use 做高层规划和复杂推理,遇到需要精确、高频操作的环节时,通过 subprocess 调用 agent-browser 执行。
七、代码质量分析
优点
- API 设计直觉:4 行代码启动 Agent,
Agent(task=..., llm=...)的参数命名和默认值让常见场景几乎不需要额外配置 - 类型安全:大量使用 pydantic v2 模型进行数据验证,内部 action schema、任务输入/输出、工具 I/O 都有严格的类型约束
- 代码风格一致:统一的 tab 缩进、单引号、现代 Python 3.12+ 类型注解(
str | None而非Optional[str]),ruff 自动格式化保证了代码库的一致性 - 模块化服务模式:每个主要组件遵循
service.py+views.py模式,主逻辑和数据模型分离,便于理解和测试 - LLM 抽象层设计良好:
BaseChatModel接口清晰,新增提供商只需实现序列化器,不需要改动核心 Agent 逻辑
潜在问题
- 大型 Watchdog 文件:DefaultActionWatchdog 达到 128.5 KB,DOMWatchdog 33.9 KB,这些文件的内聚性值得商榷,后续维护可能比较困难
- 测试覆盖有限:78 个测试文件对应 64,000 行代码,覆盖率偏低。特别是 Watchdog 之间的交互场景、复杂 DOM 结构的边界情况缺乏充分测试
- 异步超时缺失:项目的 ruff lint 配置忽略了
ASYNC109(无超时限制的异步函数),代码库中存在大量无超时的异步调用,可能导致长时间阻塞 - CDP 依赖单一浏览器:虽然支持多种浏览器 channel(Chrome、Chromium、Edge),但底层依赖 Chromium CDP 协议,不支持 Firefox(Gecko)或 WebKit
八、典型使用场景
信息搜集与聚合:从多个网站抓取特定信息,比如”比较这 5 家供应商的价格”。LLM 能理解页面结构变化,不需要像传统爬虫那样维护选择器。
表单填写与流程自动化:求职申请、保险报价、政府表格等复杂表单。LLM 能处理动态表单字段、条件显示、验证码等边界情况。
跨系统操作:需要登录 A 系统、复制数据、粘贴到 B 系统、触发 C 系统的场景。传统方案需要为每个系统写脚本,browser-use 用自然语言描述即可。
探索性任务:目标明确但路径不确定的任务,比如”找到这个产品的最优惠价格”。LLM 能自主决定搜索策略、筛选条件和比较方法。
不适合的场景包括:高频交易(LLM 延迟太高)、需要精确时序的操作、对可靠性要求极高的金融操作。
九、实践指南
快速开始
安装(需要 Python 3.11+):
uv init && uv add browser-use
uvx browser-use install # 安装 Chromium(首次)
设置 .env 文件:
BROWSER_USE_API_KEY=your-key
运行第一个 Agent:
from browser_use import Agent, Browser, ChatBrowserUse
import asyncio
async def main():
agent = Agent(
task="找到 browser-use 仓库的 star 数量",
llm=ChatBrowserUse(),
)
await agent.run()
asyncio.run(main())
实际投入估算
| 阶段 | 时间 | 成本 | 产出 |
|---|---|---|---|
| 环境搭建 | 10 分钟 | 免费(开源库) | 可运行的 Agent |
| 首次任务调试 | 1-2 小时 | LLM token 费用(约 0.01-0.10 美元/任务) | 任务脚本 |
| 生产部署 | 半天 | Sandbox/云浏览器费用 | 可扩展的自动化服务 |
常见陷阱
- 首次运行忘记安装 Chromium:执行
uvx browser-use install或手动安装 Chrome - LLM 模型选择不当:建议使用
ChatBrowserUse()模型(专为浏览器自动化优化),使用通用模型时准确率明显下降 - 任务描述过于开放:LLM 面对”帮我赚钱”这类模糊指令会迷失方向,应该具体到步骤(”打开 X 网站,点击 Y,提取 Z”)
- 忽略
max_steps设置:复杂任务默认 100 步可能不够,应该在运行时根据任务复杂度调整 - 未使用
extend_system_message添加领域知识:对于特定网站的操作,在 system message 中添加该网站的交互规则可以大幅提升成功率
十、项目评价
browser-use 的核心价值在于它填补了一个明确的市场空白:那些”人类可以 5 分钟搞定但写脚本要 2 小时”的灰色地带。它不试图替代 Selenium 或 Playwright——在需要确定性、低延迟或极高可靠性的场景中,传统方案仍然是更好的选择。它的真正价值是让非开发者也能通过自然语言驱动浏览器自动化,同时给开发者提供了一个高度可扩展的 Agent 框架。
它最适合的团队是:已有 Python 技术栈、需要快速原型化浏览器自动化任务、愿意接受 LLM 不确定性以换取开发效率的团队。不适合的场景包括:对可靠性要求极高的金融操作、高频交易、需要精确时序控制的场景。
从方向上看,browser-use 代表了 AI Agent 与 GUI 交互的一个有前景的路径:不试图让 LLM 理解屏幕像素(如传统的 UI 视觉理解方案),而是利用浏览器已有的结构化信息(DOM、Accessibility Tree)给 LLM 提供精确的操作目标。随着 LLM 推理能力的持续提升和成本的下降,这类”自主决策 + 精确执行”的混合模式会越来越有竞争力。
附录:关键命令/API 速查
| 命令/API | 功能 |
|---|---|
| Agent(task=…, llm=…) | 创建 Agent 实例 |
| await agent.run(max_steps=100) | 执行任务 |
| await agent.step() | 单步执行 |
| @tools.action(‘描述’) | 注册自定义工具 |
| uvx browser-use[cli] –mcp | 作为 MCP Server 运行 |
| @sandbox() | 生产环境部署装饰器 |
| uvx browser-use install | 安装 Chromium |
| history.final_result() | 获取最终结果 |
| history.is_done() | 检查是否完成 |
| history.errors() | 获取所有错误 |
本文基于 browser-use v0.12.6 源码分析,项目地址:github.com/browser-use/browser-use