browser-use 是什么:让模型自己开浏览器点页面的开源项目
本文基于 browser-use 仓库 commit f0aa3a8(2026-07-27)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/browser-use/browser-use 最新代码与文档为准。
browser-use 真正的技术动作只有一件:把一个网页压缩成模型看得懂、能寻址的东西,然后把「下一步点哪儿」交给模型判断。 它不是给你一套更好用的选择器 API,也不是把浏览器操作包装得更顺手——恰恰相反,它抹掉了选择器这一层,用一串整数编号替代。理解了这个替代,剩下的设计取向、能力上限、坑,基本都能自己推出来。
站内已有几篇相邻的文章各管一段:开源 AI 工具怎么挑 讲的是选型方法,Agent 框架横向对比 站在多个框架之上比取向,Agent 工具怎么设计 讲的是通用的工具契约;本篇只钉住 browser-use 这一个仓库,把它的内部结构和文件位置摊开,你可以边读边 git clone 对照。
一、它把网页变成了什么
浏览器自动化的老办法是你告诉程序「点 #submit-btn」。模型没法这么干——它看不到 DOM,你也不可能把整棵 DOM 树塞进上下文。
browser-use 的做法是先做一次有损压缩:遍历页面,挑出可交互的节点,给每个节点发一个整数下标,再用一种紧凑的树状文本渲染出来。这段渲染逻辑在 browser_use/dom/serializer/serializer.py 的 serialize_tree 里,可交互节点会带上 [编号] 前缀。默认系统提示词 browser_use/agent/system_prompts/system_prompt.md 里直接给了格式样例:
[33]<div />
User form
[35]<input type=text placeholder=Enter name />
*[38]<button aria-label=Submit form />
Submit
[40]<a />
About us
几个约定值得记住,它们直接决定模型的行为边界:只有方括号里带数字的元素是可交互的;制表符缩进表示父子关系;前缀 *[ 表示这个可交互元素是上一步之后新出现的——通常就是你上一个动作造成的结果,比如输入框下面弹出的候选列表。提示词里还写明,默认只列出视口内的元素。
于是模型的动作空间变得极小且可枚举:click(index)、input、scroll、select_dropdown……编号是唯一的寻址方式。同一份状态还会配一张带框的截图,browser_use/agent/service.py 里每一步都调 get_browser_state_summary(include_screenshot=True),即使你关掉了视觉输入也照样截,注释写的理由是截图很快、留着有用。提示词里说得更直白:截图是 ground truth;当某个编号对应的元素没有文字信息时,编号会画在它的正上方居中位置。
这套编号-截图的双通道,就是「网页」在这个项目里被转换成的可操作对象。它不稳定(编号每步都可能重排)、有损(视口外的东西看不见),但它足够小,能塞进上下文,也足够直白,模型不用学 CSS 选择器。
二、循环怎么转:一步一状态,一步一决策
主循环在 browser_use/agent/service.py 的 Agent 类里,一步(step)分成三段:_prepare_context 抓浏览器状态并拼消息、_get_next_action 调模型、_execute_actions 把模型返回的动作打出去,最后 _finalize 落历史。
模型每一步的输出不只是动作,还带一组自述字段:对上一步目标的评价、记忆、下一步目标,以及一份可选的 thinking。log_response 函数把它们打到日志里(Eval / Memory / Next goal 分别带不同颜色),这也是你调试时最先该看的东西。开了计划能力时,模型还能吐 plan_update 直接改写待办列表,或者用 current_plan_item 推进指针。
模型可以一步返回多个动作,由 multi_act 顺序执行。这里有个很容易被忽略的设计,它的 docstring 写得清楚:
async def multi_act(self, actions: list[ActionModel]) -> list[ActionResult]:
"""Execute multiple actions with page-change guards.
Two layers of protection prevent executing actions against stale DOM:
1. Static flag: actions tagged with terminates_sequence=True (navigate, search, go_back, switch)
automatically abort remaining queued actions.
2. Runtime detection: after every action, the current URL and focused target are compared
to pre-action values. Any change aborts the remaining queue.
"""
也就是说,编号一旦可能失效,队列就断。你想着「一步把八个表单字段填完」,实际执行到第二个字段就可能因为页面变化被中断——这不是 bug,是它在保护你不去点一个已经不存在的编号。
跑偏怎么办?这块有几层软约束,都在 Agent.__init__ 的参数里露头:loop_detection_enabled 配合状态里的循环检测器,会在动作重复或页面停滞时注入一段提示;planning_replan_on_stall 在连续失败到阈值时提示模型重写计划;planning_exploration_limit 在探索多步还没有计划时催它出计划;_inject_budget_warning 在步数用掉七成半以后提醒模型先把已有结果存盘再收尾。注意这些全是往上下文里塞话,不是硬停机。真正的硬闸门是 run(max_steps=...)、llm_timeout、step_timeout 这类数值上限。这套「软提示 + 硬闸门」的分工,和 Agent 陷入无限循环怎么停机 里讨论的思路是一致的。
三、组成部分与文件位置对照
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 主循环与 Agent 参数 | 每步抓状态、调模型、执行动作、落历史;几十个行为开关都在构造函数签名上 | browser_use/agent/service.py | 调行为、查默认值、看错误怎么被吞的 |
| 系统提示词 | 定义元素编号格式、动作纪律、输出结构;按模型与模式分成 8 份 | browser_use/agent/system_prompts/ | 想知道模型「被告知了什么」,或准备改提示词 |
| 消息与上下文管理 | 拼每步的状态消息、历史裁剪、敏感值遮蔽、上下文压缩 | browser_use/agent/message_manager/service.py | 上下文太长、日志里出现被遮蔽的凭据占位 |
| 动作注册表与默认动作 | click / input / navigate / extract / evaluate / done 等默认动作,以及按名字注入参数的注册机制 | browser_use/tools/service.py、browser_use/tools/registry/ | 加自定义动作、排除默认动作、查某个动作到底干了啥 |
| 页面结构序列化 | 把 DOM 压成带编号的树状文本 | browser_use/dom/serializer/serializer.py | 模型「看不到」某个按钮时 |
| 浏览器会话与守护件 | 通过 CDP 驱动 Chromium,14 个 watchdog 各管一摊(导航安全、崩溃、下载、弹窗、截图、录制等) | browser_use/browser/watchdogs/ | 域名白名单不生效、下载没接住、浏览器掉线重连 |
| 低层页面操作 | 不经过模型的确定性操作接口 | browser_use/actor/ | 想在自定义动作里写死一段确定性操作 |
| 模型接入层 | 15 个 provider 目录,各自适配一家的调用与结构化输出 | browser_use/llm/ | 换模型、接自建网关、排结构化输出解析失败 |
| 示例 | 124 个文件,覆盖入门、浏览器配置、自定义动作、用例 | examples/ | 想找一个能跑的最小样例,而不是读文档 |
一个 Python 侧的最小用法,README 里给的形状是这样(此处按语义裁剪):
from browser_use import Agent, ChatBrowserUse
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(model='<厂商前缀>/<模型 id>'),
)
history = await agent.run()
模型 id 这里故意留了占位:README 的 FAQ 说明 ChatBrowserUse 接受带厂商前缀的模型 id,一个网关凭据就能打到多家;具体可填哪些,回仓库 README 抄当时的最新写法,别抄本文。
run() 返回的是一个历史对象,仓库内嵌文档列了它的一大批读取方法:urls()、action_names()、extracted_content()、errors()、final_result()、is_done()、number_of_steps() 等等。把它当成一份可回放的执行记录,比只看最后一句 final_result() 有用得多。
想扩动作就挂装饰器,README 给的形状是:
from browser_use import Tools
tools = Tools()
@tools.action(description='Description of what this tool does.')
def custom_tool(param: str) -> str:
return f"Result: {param}"
四、和写 Playwright 脚本是两种不同的活
这两件事经常被放在一起比,但成本结构不同。
传统脚本的成本全在「写」和「维护」:每个选择器、每个等待条件、每个分支都得想清楚,写完之后执行是确定的、毫秒级的、零推理成本的。页面一改版,脚本就红。
browser-use 把成本挪到了「跑」:写只需要一句自然语言任务,但每一步都要序列化 DOM、截图、调一次模型。步数越多越慢越贵,且两次运行的路径可能不同。它换来的是对页面改版的容忍——按钮从左边挪到右边、文案改了,模型大概还能找到。
由此推出的分工是清楚的:需要严格断言、要当回归基线、要每天跑几万次的,写确定性脚本;页面结构不受你控制、流程有分支、任务描述比选择器更稳定的,用 Agent。这也是仓库 README 里 CLI 与 Python 库的分工口径——一次性任务交给你手里已有的编码 Agent 走 CLI,代码里的可重复自动化用 Python 库。
顺带说一句,两者并不互斥。examples/browser/playwright_integration.py 就是把确定性操作作为自定义动作接进来的例子;browser_use/actor/ 也提供了不经过模型的操作入口。关键动作写死、模糊环节交给模型,通常比全交给模型稳。如果你手上是一个结构稳定的纯抓取需求,别急着上 Agent,一段脚本更省。
五、边界与代价:它明确不管什么
放弃了确定性。 同一个任务两次跑,动作序列可以不一样。别拿它当 CI 里的断言基线;use_judge 那套裁决本身也是又一次模型调用,代码里裁决失败时直接返回 None,而且注释写明裁决结果不会覆盖 Agent 的自述成功状态。两个都可能错。
视野是有损的。 提示词里写明默认只列视口内的元素。跨源 iframe(也就是独立进程里的那种)默认是会处理的,但 cross_origin_iframes 这个开关的字段说明把代价交代得很清楚:关掉它就只处理同源框架,理由是避开复杂度与卡死。反过来读就是——跨源框架这条路径本身是已知的不稳定来源,遇到状态采集卡住不动,它是第一个该试着关掉的开关。同一层还有两个上限约束:同时处理的 iframe 文档数量有帽子,跨源递归的深度也有帽子,超出的框架不会进入模型视野。canvas 里画出来的界面、被遮挡的元素、需要滚动很久才出现的内容,也都可能「不存在」。
延迟和费用随步数走。 每步一次模型调用加一次状态采集,长任务的账单和耗时是线性叠上去的。各家模型服务商的计费与限制规则不同且会调整,以官方最新说明为准;你在代码侧能控的是步数上限、单步超时、是否传截图、以及历史裁剪与上下文压缩。
验证码与反自动化不是这个开源库的职责。 README 的 FAQ 里把这件事明确指向了云端 stealth 浏览器与代理,开源库本身不解决。从工程角度也该这么理解:你碰到验证码,说明目标站点表达了不希望被自动化访问的意图。正确的下一步是去看它有没有官方 API、有没有数据导出、条款允许你做什么,而不是想办法绕过去。
合规与账号风险完全在你这边。 这个库驱动的是真实浏览器。把自己的 Chrome 配置目录交给它,它就带着你的全部登录态在互联网上活动。目标站点的使用条款、账号被判异常的可能、被自动填进第三方表单的个人数据——库不会替你评估任何一条。
它也不管你的任务描述有多含糊。 仓库内嵌的提示指南把这点摆得很明白:具体的分步任务明显好于「去网上赚钱」这种开放式指令,甚至建议你在任务里直接点名要用哪个动作。
六、上手与避坑清单
别拿日常 Chrome 配置目录起步。 会踩是因为它太方便了——user_data_dir 指向真实 profile,登录态立刻可用。代价是模型和它访问的每个站点都可能触到这份登录态。避法:起步阶段用独立的配置目录,并给 Browser 配 allowed_domains 白名单,导航安全由 browser_use/browser/watchdogs/security_watchdog.py 里的 _is_url_allowed 兜住;prohibited_domains 是黑名单侧的对应物,两者同时给出时白名单优先。
传 sensitive_data 却不锁域名。 会踩是因为你以为凭据被遮蔽了就安全。机制是双向的:发给模型的消息里凭据被替换成占位,动作真正执行时才在参数里换回真值。但如果没有域名白名单,Agent 走到一个恶意页面吃了提示注入,凭据就可能被填到不该填的地方。代码里为此专门打了一条警告:
⚠️ Agent(sensitive_data=••••••••) was provided but Browser(allowed_domains=[...]) is not locked down! ⚠️
看到这条别当噪音。相关的攻击面和防线可以对着 提示注入怎么防 一起看。
别照抄内嵌文档里的默认值。 会踩是因为仓库里同时存在文档段落和代码签名,两者会漂移:AGENTS.md 里那份文档段落给 max_steps 标的默认值,和 browser_use/agent/service.py 里 run() 签名上的实际默认值不是同一个数(差距还不小),max_actions_per_step、max_failures、step_timeout 三个也各自存在同类出入。这不是谁写错了,是内嵌文档快照与代码不同步的常态。避法:所有默认值以 Agent.__init__ 和 run() 的签名为准,重要的那几个自己显式传,别依赖默认。
自定义动作的参数名写错。 会踩是因为注入是按名字匹配的,不是按类型。文档里为此加了两处 Warning:需要浏览器的动作,参数必须精确叫 browser_session 且标注 BrowserSession 类型,写成 browser: Browser 会静默失败。避法:写完新动作立刻单独跑一次,确认它真的被调用过、真的拿到了会话对象。
指望一步填完整张表。 会踩是因为 max_actions_per_step 允许多动作,看起来能批量提交。实际上 multi_act 的两层保护会在导航类动作或 URL/焦点变化后中断剩余队列。避法:把「输入后会触发页面变化」的字段单独成步,尤其是带候选下拉的搜索框——提示词里明确要求输入后等下一步看有没有出现 *[ 标记的新元素,有就点它,别直接回车。
让 extract 反复啃同一个页面。 会踩是因为 extract 看起来是万能读取器。它内部要再调一次模型,提示词里直接写了这个动作很贵、不要用同样的查询重复问同一页。避法:能从状态里的可见文本直接读到的信息,就别调 extract;确实要抽的,先确认自己已经在正确的页面上。
把循环检测当停机开关。 会踩是因为参数名看着像硬保护。实际是往上下文里注入提示,模型可以不听。避法:max_steps、llm_timeout、step_timeout 这些数值闸门自己设明确值,再在外层加一个你自己的挂钟超时。
先跑 examples/,别先读文档。 124 个示例文件里,examples/getting_started/ 下的五个从基础搜索到多步任务是一条完整梯子,examples/browser/ 下则是各种浏览器接法。跑通一个再回头读参数列表,效率高得多。
一份最小自检
动手之前对着四条过一遍:任务描述有没有分步和验收口径;要跑的域名是否已进白名单、用的是独立配置目录还是日常登录态;步数上限与两级超时分别是多少、超时后中间结果落在哪;这活是不是真的需要模型每步判断。
接下来该读哪个文件,取决于你卡在哪:模型行为不对,读 browser_use/agent/system_prompts/system_prompt.md;模型看不见元素,读 browser_use/dom/serializer/serializer.py;动作行为和预期不符,读 browser_use/tools/service.py 里对应那个函数;浏览器层面的怪事(域名、下载、崩溃、重连),去 browser_use/browser/watchdogs/ 里按名字找那个 watchdog。这个项目采用 MIT 许可证,代码就在 https://github.com/browser-use/browser-use ,任何一句判断你都可以当场回去核。
这个系列的其余文章
这篇是总览。想往下挖,按下面两条线走:先把任务跑通,或者直接读代码。
上手与使用
- browser-use 跑通第一个任务
- browser-use 的 LLM 适配层怎么接国产模型与本地模型
- browser-use 命令行怎么用
- 给 browser-use 加自定义动作
- browser-use 结构化输出的 schema 约束与字段缺失排查
- browser-use 开源项目怎么让 Agent 登你的账号
- browser-use 的产物落盘
- browser-use 本地跑不住时
- browser-use 容器化拆解
- 读懂 browser-use 用量统计层
- browser-use 跑不动时的排查顺序
结构与机制
- browser-use 的 Agent 循环拆解
- browser-use 为什么要备 8 份系统提示词而不是 1 份
- browser-use 的 DOM 序列化
- browser-use 怎么判断按钮能不能点
- browser-use 把页面转 markdown 喂模型
- browser-use 的浏览器会话层
- browser-use 用 14 个 watchdog 分管浏览器杂事
- browser-use 的 actor 层
- 拆解 browser-use 的动作注册表
- browser-use 的 MCP 双向设计
- browser-use 的判分器怎么用
- browser-use 的三条回放线
- 拆 browser-use 的遥测与观测
- browser-use 的两层 skills
- 读 browser-use 的 beta 支线
- browser-use 两种并发
- 什么时候别用 browser-use
- browser-use 源码梳理
全部文章也汇总在 browser-use 开源专题。如果你要的不是「操作网页」而是一个常驻自托管、从聊天软件里指挥的 Agent,那是另一条路:开源自托管 Agent 项目 Hermes Agent 是什么。