什么时候别用 browser-use:浏览器 Agent 的三道判断题
本文基于 browser-use 仓库 commit f0aa3a8(2026-07-27)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/browser-use/browser-use 最新代码与文档为准。
浏览器 Agent 是兜底手段,不是首选手段。 一件事你之所以要让模型去点页面,通常是因为前面两条路都走不通:拿不到接口,流程也不稳定到能写死。如果这两条里有一条走得通,上 browser-use 只会让你多付一份模型钱、多担一份不确定性。这篇要给的就是这套判断顺序,以及顺序走到最后一档时,你该知道的代价。
顺便说清本篇和站内几篇的分工:硬编码流程与交给模型的取舍讲的是通用的取舍原则,状态机式编排与自由 Agent 的对比讲控制结构,Agent 成本失控的成因讲钱怎么烧掉的;本篇不重复那些通用结论,只落在 browser-use 这个具体项目上,看它的代码结构给你留了哪些档位、哪些事它明确不管。
一、这个项目到底在解决什么问题
仓库根目录的 AGENTS.md 开头一句话说得很直白:它接收一个用户定义的任务,通过 CDP(Chrome DevTools Protocol)驱动 Chromium 浏览网页,处理 HTML,然后反复查询语言模型来决定下一步动作,直到任务完成。
这句话里有三个信息值得你停一下。
一是驱动层用的是 CDP,不是别的浏览器自动化协议栈。这决定了它连的是 Chromium 系浏览器,也决定了你能通过 cdp_url 参数接到一个已经在跑的浏览器实例上。
二是”处理 HTML”。页面要变成模型能读的东西,这一步的开销是实打实的:一个现代页面的 DOM 展开后不小,每一步都要送进模型一次。
三是”反复查询”。这是整个方案的成本结构和不确定性来源——步数不是常量,同一个任务今天三步、明天七步,页面改版一次就可能变成十五步或者原地打转。
所以它解决的是这么一类问题:目标网站没有可用接口,页面结构又不稳定到能写选择器,而任务本身需要临场判断。 只有同时满足这几条,模型逐步决策才是在赚钱而不是在花钱。
二、第一道判断题:有接口就别点页面
这条听起来是废话,但实践里被跳过的频率很高——因为 Agent 版本看起来”五行代码就跑起来了”。examples/getting_started/01_basic_search.py 确实就这么短:
from browser_use import Agent, ChatBrowserUse
async def main():
llm = ChatBrowserUse(model='bu-2-0')
task = "Search Google for 'what is browser automation' and tell me the top 3 results"
agent = Agent(task=task, llm=llm)
await agent.run()
看着比自己写请求省事。但这五行背后是:启动一个真实浏览器、渲染页面、每一步把页面状态和历史送给模型、模型返回动作、执行、再来一轮。同样一件事如果目标方给了接口,你付出的是一次网络请求、一个确定的 JSON 结构、可预期的失败模式。
判断标准可以粗暴一点:
- 目标数据有公开 API、开放数据集、RSS、导出功能中的任意一种 → 不要上浏览器 Agent。
- 目标页面的数据其实来自一个前端能看到的 XHR 接口,且取用方式在对方允许的范围内 → 优先走这条路,失败模式比模拟点击清楚得多,也不必维护选择器。
- 只有内网系统/老旧后台/第三方平台既没接口也不给导出 → 才进入第二道判断。
值得强调的是,接口路线还有一个隐性优势:它不需要你带着登录态去驱动一个真浏览器。这一点在后面讲边界时会再回来。
三、第二道判断题:结构稳定的流程,写死更划算
假设没有接口。下一个问题是:这个流程每次跑的路径是不是一样的?
如果一样,你要的是”回放”,不是”决策”。browser-use 在这一档给了两样东西。
一是 initial_actions,在主任务开始前先跑一串不经过模型的动作。examples/features/initial_actions.py 里就是一段纯配置:
initial_actions = [
{'navigate': {'url': 'https://www.google.com', 'new_tab': True}},
{'navigate': {'url': 'https://en.wikipedia.org/wiki/Randomness', 'new_tab': True}},
]
agent = Agent(
task='What theories are displayed on the page?',
initial_actions=initial_actions,
llm=llm,
)
凡是路径确定的前置步骤——打开哪几个页面、切到哪个标签——都该塞进这里,省下的是实打实的模型调用次数。
二是历史回放。examples/features/rerun_history.py 展示的链路更有意思:先正常跑一遍并落盘历史,再从历史里检测出可替换的变量,最后带着新值重放。
agent = Agent(task=task, llm=llm, max_actions_per_step=1)
await agent.run(max_steps=10)
agent.save_history(history_file)
variables = agent.detect_variables()
results = await substitute_agent.load_and_rerun(
history_file,
variables=new_values,
max_step_interval=20,
delay_between_actions=1,
)
这套用法的价值在于把”探索”和”执行”分成了两个阶段:探索阶段付一次模型钱,把成功路径固化成一份历史;之后每天跑的是回放,成本和步数都可控。该文件的说明里也点明,回放时 extract 这类动作仍会重新调模型分析当前页面内容,因为页面可能已经变了——也就是说这不是纯粹的机械重播,而是”骨架固定、取数环节仍留一点弹性”。
这一档适合的场景是:每天同一个后台、同一张表单、只有填进去的值不同。相关的工程做法可以参考执行轨迹的复现与回放。
但要如实说清它的脆弱点:历史回放绑定的是当时那条路径。目标站点改版、A/B 分流命中不同版本、弹出一个新的同意弹窗,回放就会在某一步落空。你必须给回放配失败告警,而不是当成定时任务扔进 cron 就不管了。
四、第三道判断题:actor 层是那个被忽略的中间档
很多人的认知里只有两档:要么自己写脚本,要么把整件事交给 Agent。browser-use 的 browser_use/actor/ 提供了第三档,它的 README 第一句自我定位是”构建在 CDP 之上的底层浏览器自动化能力”。
这一层暴露出来的是 Page、Element、Mouse 这几个类,方法都是确定式的:goto、reload、get_elements_by_css_selector、click、fill、hover、check、select_option、drag_to、press、screenshot、get_url、get_title。你写代码控制每一步,模型完全不参与。
关键在于,这一层还留了两个”只在这一步问模型”的口子:get_element_by_prompt(用自然语言描述找元素,找不到返回 None)和它的强制版本 must_get_element_by_prompt,以及 extract_content(按你给的 Pydantic 模型从当前页面抽结构化数据)。README 里那段 extract_content 的用法就是配一个 BaseModel 子类,把页面上的商品名、价格、描述抽成对象。
browser_use/actor/playground/mixed_automation.py 演示的正是这种混合写法——流程你自己控,只有”找到那个输入框”这一步交给模型:
browser = Browser(keep_alive=True)
await browser.start()
page = await browser.get_current_page() or await browser.new_page()
await page.goto('https://browser-use.github.io/stress-tests/challenges/angularjs-form.html')
element = await page.get_element_by_prompt('zip code input', llm)
if element:
await element.click()
这个档位往往是性价比最高的落点:流程的绝大部分步骤是确定的,只有个别定位或抽取环节需要语义理解。你既拿到了脚本的可调试性,又避开了”每一步都问模型”的成本和抖动。判断你的任务能不能落在这一档,关键是先把流程画出来,逐步标注哪一步真的需要语义理解。
AGENTS.md 里也给了一条同向的提示:自定义工具里如果要做确定式操作,用 browser_session 这个参数拿到会话。而且它专门警告过,这个参数名必须一字不差地写成 browser_session、类型标注为 BrowserSession,写成别的名字工具会静默失效——因为注入是按参数名匹配的。
五、各部分对应关系速查
下面这张表的仓库位置都是可以直接打开核对的:
| 组成部分 | 它负责什么 | 对应仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 系统提示词 | 约束模型输出什么动作、怎么思考 | browser_use/agent/system_prompts/(8 份 md,按 thinking / flash 等模式分开) | 想用 override_system_message 或 extend_system_message 改行为时 |
| 默认动作集 | 注册模型可调用的动作 | browser_use/tools/service.py | 想用 exclude_actions 砍掉动作,或对照 search / navigate / extract / evaluate 的真实签名时 |
| actor 底层 | 确定式的页面/元素/鼠标操作 | browser_use/actor/(page.py、element.py、mouse.py) | 你决定自己写流程、只在个别步骤调模型时 |
| watchdog | 监听浏览器事件、拦导航、盯崩溃与下载 | browser_use/browser/watchdogs/(14 个) | 排查”为什么这次导航被拦了”、下载没落盘、页面崩掉时 |
| LLM 适配层 | 对接各家模型服务 | browser_use/llm/(15 个 provider 目录) | 换模型、接自建端点、做多模型兜底时 |
| 历史与回放 | 落盘执行轨迹、检测变量、带新值重放 | examples/features/rerun_history.py | 把探索出的路径固化成日常任务时 |
| 安全边界示例 | 限制可访问域名、屏蔽敏感值 | examples/features/secure.py、examples/features/restrict_urls.py | 上线前收紧权限时 |
examples/ 目录一共 124 个文件,遇到不确定的用法,先在这里翻,比凭印象写参数靠谱得多。项目采用 MIT 许可证。
六、边界与代价:它明确不管的那些事
这一节比前面几节重要,因为它决定你敢不敢把这套东西放到生产里。
它不承诺步数和结果的确定性。 看 Agent 的参数表就知道它对自己的预期:max_steps 给整个任务设了步数上限,max_failures 规定连续失败多少次就放弃,step_timeout 管单步的墙上时间,llm_timeout 管单次模型调用的等待,final_response_after_failure 还专门约定了失败次数用尽后要不要再强行要一次收尾输出。这一组参数存在本身就说明了设计假设:会失败、会重试、会超时、可能没跑完就得交答案。你要按”这次可能跑不完”来设计上层逻辑,而不是按”它会成功”——具体表现就是任务的成功标志必须由你自己校验,不能因为循环退出了就认为事情办成了。
它不替你评估目标站点的使用条款。 用一个自动化浏览器去访问第三方站点,是否违反对方的服务协议、是否触碰 robots 约定、抓下来的数据能不能商用,这些都是你自己的判断和责任,代码层面没有任何东西会替你把关。这条不是免责套话——真出问题时,第一个被追的是运行脚本的那一方。
它不保证你能穿过反自动化机制。 仓库里有 browser_use/browser/watchdogs/captcha_watchdog.py,但看它的文档字符串就明白定位:它监听验证码求解的开始/结束事件,并提供一个 wait_if_captcha_solving() 让 agent 的步骤循环阻塞等待——也就是”等一等”,而不是”破解”。同一个文件还坦白了一个已知限制:同一时刻只跟踪一个验证码求解,多个验证码重叠时只跟踪最新的那一个,先前的等待可能提前返回。AGENTS.md 里另有一套要 BROWSER_USE_API_KEY 的托管浏览器方案,本文不展开:换基础设施不会把你面对目标站点的合规责任转移出去,该判断的还是你自己判断。本文也不提供任何规避风控、反爬或验证码的做法——真正的结论恰恰是反过来的:如果一个任务只有靠对抗对方的防护措施才跑得通,那它就不该由自动化来做,该做的是去申请正式接口、走对方给的导出通道,或者接受这件事得人工完成。
它会带着你的登录态操作真实账号。 这是最容易被低估的一条。AGENTS.md 的 Real Browser 章节演示的就是直接指向本机 Chrome 的 executable_path 和 user_data_dir,好处是”保留认证”,代价是这个 Agent 拿到的就是你本人的登录状态——你的邮箱、你的后台、你的支付页面它都进得去。模型判断错一步,动作是真的会发生的。所以这类实验请用独立的浏览器 profile、独立的低权限账号,别拿主账号试。原则性的做法见最小权限的 Agent 设计。
它有敏感数据外泄面。 页面内容要送进模型,截图默认也会送。examples/features/secure.py 的注释里写得很清楚:sensitive_data 会把敏感信息从送给模型的输入里过滤掉、只留占位符,但默认传给模型的截图里可能仍然包含你的信息,要靠 use_vision=False 关掉。同一个文件还配了 allowed_domains=['*google.com', 'browser-use.com'] 来收窄可访问范围。至于各家模型服务商怎么保留和使用这些数据,规则不同且会调整,以官方最新说明为准。
遥测默认开启。 文档里写明它用 PostHog 收集匿名使用数据,关掉的方式是设 ANONYMIZED_TELEMETRY=false。进企业环境前把这一条列进检查项。
它不管你的成本上限。 calculate_cost 默认是 False,也就是说默认状态下你连账都不记。要控成本,得自己在外面加闸。
七、上手与避坑清单
别用主浏览器 profile 起步。 为什么会踩:AGENTS.md 的 Real Browser 示例直接指向系统 Chrome 的用户数据目录,照抄最省事,于是 Agent 拿到了你的全部登录态。怎么避:新建一个专用 user_data_dir,或者干脆传 None 走无痕模式,账号也另开一个权限最小的。
上线前一定配 allowed_domains。 为什么会踩:默认不限域,模型一旦被页面上的诱导性文字带偏,可能导航到任何地方。怎么避:照 restrict_urls.py 的写法把域名白名单收窄;security_watchdog.py 会在导航前拦下不在白名单里的 URL 并派发一个导航被拦的错误事件,日志里能看到明确的拦截记录。注意文档里说了通配符不允许出现在顶级域的位置。
自定义工具的参数名不能改。 为什么会踩:习惯性写成 browser: Browser 看着更顺眼,结果工具静默失效,你还以为是模型不肯调。怎么避:一字不差地写 browser_session: BrowserSession,文档里对这一点连着警告了两次。
先把确定的步骤搬出模型。 为什么会踩:一股脑塞进 task 描述里,模型要多花好几步去”找到入口”。怎么避:入口导航、标签页准备这类固定动作放进 initial_actions;再进一步就是整段用 actor 层写死。
别把 evaluate 当万能钥匙。 为什么会踩:默认动作里有 evaluate 可以执行任意 JavaScript,看起来什么都能干,于是被用来绕过所有正常交互。怎么避:把它当成处理 shadow DOM、复杂选择器这类特殊情况的逃生口;actor 层的 README 还专门交代了它的调用约定——page.evaluate() 和 element.evaluate() 必须写成箭头函数形式,返回值一律是字符串,对象会被自动 JSON 序列化。写错格式是很常见的第一个报错。
不要把 actor 层当成 Playwright 用。 为什么会踩:方法名看着很像,手就顺着敲下去了。怎么避:actor README 有一整段”重要用法说明”顶着写:这是 browser-use 的 actor,不是 Playwright 也不是 Selenium,只用文档里列出的方法;它明确点出不存在 element.submit()、element.dispatch_event()、element.get_property() 这类方法,下拉框要用 select_option() 而不是 fill(),表单提交要点提交按钮或者 page.press("Enter")。另外 get_elements_by_css_selector() 是立即返回、不会等元素可见的。
回放任务必须配失败告警。 为什么会踩:load_and_rerun 跑通一次之后很容易被当成稳定管道,而页面改版是不打招呼的。怎么避:把回放结果的成功标志接进你的告警,失败时回落到人工或者重新走一遍探索阶段。
压速度之前先量清楚代价。 为什么会踩:看到 flash_mode 能跳过 thinking、BrowserProfile 里 minimum_wait_page_load_time 和 wait_between_actions 都能往下调,就一路压到底。怎么避:05_fast_agent.py 里这些确实是仓库示范的加速手段,它还配了一段要求模型”尽量少说话、尽量合并动作”的 extend_system_message;但把等待时间压到极限会让”页面还没渲染完就去点”的失败模式明显变多,省下的时间要拿失败重跑还回去。先在你自己的目标站点上测出成功率曲线,再决定压到哪一档。
收尾:三句话的自检
真正要落地时,按这个顺序问自己:
- 这件事有没有接口、导出或者公开数据?有就别开浏览器。
- 没接口的话,流程路径是不是每次都一样?一样就用
initial_actions加历史回放,把模型钱付在探索阶段。 - 路径大体固定但个别步骤需要语义判断?落在
browser_use/actor/,自己写流程,只在那一步调get_element_by_prompt或extract_content。
三条都过不去,才轮到完整的 Agent 循环。到那时候,你要准备好的是失败重试、域名白名单、独立账号、成本闸门和告警——而不只是那五行启动代码。
接下来该读哪个文件:想改行为,去 browser_use/agent/system_prompts/ 看那 8 份提示词到底约束了什么;想知道模型手里究竟有哪些动作以及每个动作的真实参数,去 browser_use/tools/service.py;想自己写流程,browser_use/actor/README.md 加 browser_use/actor/playground/ 里的几个脚本够你起步。别凭方法名的印象写调用,参数名错一个就是一次静默失败。
本篇属于一个把开源浏览器操作 Agent 项目 browser-use逐层拆开讲的系列,整体地图见 browser-use 是什么;沿着这条线往下,还可以看 browser-use 两种并发 和 browser-use 源码梳理。