browser-use 命令行怎么用:交互入口、init 与配置从哪读

2026-07-30

本文基于 browser-use 仓库 commit f0aa3a8(2026-07-27)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/browser-use/browser-use 最新代码与文档为准。

browser-use 的命令行不是一堆预置子命令,它的主路径是「把 Python 代码从 stdin 喂进去、在一个常驻浏览器会话里执行」。 你如果按老习惯去找 browser-use openbrowser-use screenshot 这类命令,会直接撞到一段迁移提示并拿到退出码 2 —— 这不是你敲错了,是这条路已经被主动堵掉,并在 browser_use/cli.py 里留了逐条的替代写法。理解这一点,比记住任何参数都重要。

这篇只拆命令行这一层:入口怎么分流、init 干了什么、配置从哪几个地方读。站内另外几篇的分工是这样的:pi 的配置怎么理顺讲的是另一个项目的配置分层思路,Agent 工具调错怎么查讲的是工具调用出错后的排查方法,Claude Code 教程讲的是编码代理本体的用法;本篇不重复它们,只回答「browser-use 这个命令行本身在做什么决策」。

一、敲下命令之后,第一层分流在哪

pyproject.toml[project.scripts] 里挂了五个可执行名:

[project.scripts]
browser-use = "browser_use.cli:main"  # Browser Use CLI for agents
browseruse = "browser_use.cli:main"  # Alias for browser-use
bu = "browser_use.cli:main"  # Alias for browser-use
browser = "browser_use.cli:main"  # Alias for browser-use
browser-use-tui = "browser_use.cli:browser_use_tui_main"  # Deprecated alias for browser-use

前四个是同一个 main()bubrowser 只是短别名。最后那个 browser-use-tui 走的是 browser_use_tui_main(),函数体里第一件事就是往 stderr 打一句 browser-use-tui is deprecated; use browser-use instead.,然后原样调 main()。你在旧文档或旧脚本里看到 browser-use-tui,把它改回 browser-use 即可,行为不变。

main() 把参数交给 _dispatch(),判断顺序是硬编码的:--cli-mcp 优先,然后 --mcp,然后位置参数 installinit,然后 --template / -t(带这两个标志也当 init 处理),然后 skill。都不匹配,才去看是不是遗留命令,最后落到「执行代码」这条主路径。

这里有个容易被忽略的细节:args 为空时,行为取决于 stdin 是不是终端。

if not args:
	if sys.stdin.isatty():
		print(_QUICKSTART)
		return 0, 'quickstart'
	code = sys.stdin.read()
	if not code.strip():
		print(_EMPTY_STDIN_MESSAGE, file=sys.stderr)
		return 1, 'run'
	sys.stdin = StringIO(code)

也就是说,你在终端里裸敲 browser-use 得到的是 _QUICKSTART 那段欢迎文本,退出码 0;而在脚本、CI、或者被编码代理调用时(stdin 不是 tty),同一条命令会去读 stdin,读到空内容就打 _EMPTY_STDIN_MESSAGE 并返回 1。所谓「交互式入口」,在这个项目里指的是这段可读的引导文本加上 heredoc 写法,不是一个 REPL 界面。_QUICKSTART 里给的范例就是 heredoc:

browser-use <<'PY'
new_tab("https://news.ycombinator.com")
print(page_info())
PY

真正执行代码的活不在 cli.py 里。_run_browser_harness()browser_harness 导入 run,设置 BH_CLIENTBH_CLIENT_VERSION 两个环境变量,再调 run.main()cli.py 在中间做了一层品牌改写:_patch_browser_harness_cli_text()run.HELPrun.USAGE 里的 Browser Harnessbrowser-harness 字样替换成 Browser Use / browser-useauth 子 CLI 只在带 -h/--help 时走这层替换,telemetry 则是除 statusenabledisable 这三种调用之外都走替换。你看到的帮助文本因此是「翻译过」的,遇到疑难时按原始名字去搜索反而更容易找到线索。

二、遗留命令:一张明确的迁移映射表

cli.py 里有个 _LEGACY_HINTS 字典,把旧 CLI 的子命令和标志逐个映射到新写法。命中任意一个键,_dispatch() 就打印 _legacy_migration_message(...) 到 stderr 并返回 2。摘几条:

'open': 'new_tab("https://example.com")',
'state': 'print(page_info())',
'screenshot': 'print(capture_screenshot())',
'eval': 'print(js("document.title"))',
'cookies': 'print(cdp("Network.getCookies"))',
'--cdp-url': '# use the BU_CDP_URL=<url> env var instead of a flag',
'--headed': '# local control always attaches to your real, visible Chrome — no flag needed',

这张表本身就是一份可读的设计说明:--headed 之所以没了,是因为本地控制默认就附着到你正在用的那个可见 Chrome;--session 没了,是因为本地只保留一个默认守护进程,命名会话被收窄成云端场景下的 BU_NAME 环境变量。匹配逻辑用的是 args[0].split('=', 1)[0],所以 --cdp-url=... 这种等号写法也会被拦住。

主路径还有一处专门的错误处理值得留意。你 piped 的代码里写了个不存在的名字时,_dispatch() 捕获 NameError,用 _raised_from_piped_code() 判断栈底帧的 co_filename 是不是 '<string>',确认异常来自你喂进去的代码,才打印 _unknown_helper_message(name) 并返回 2;否则原样抛出。这个区分让「你写错了助手函数名」和「库内部真的崩了」不会混成同一种输出。

_CLI3_GUIDE 里列出的核心助手包括 new_tab(url)goto_url(url)page_info()capture_screenshot()click_at_xy(x, y)type_text(text)fill_input(selector, text)press_key(key)scroll(x, y)js(code)cdp(method, ...)wait_for_load()wait_for_element(selector)list_tabs()switch_tab(target)close_tab(target)。仓库自带的技能文件里还额外强调:首次导航用 new_tab(url) 而不是 goto_url(url)

三、init 与 skill:两个方向完全不同的初始化

browser-use initbrowser_use/init_cmd.py。它是个 click 命令,四个选项:--template / -t--output / -o--force / -f--list / -l

关键事实是模板不在包里。文件顶部写着 TEMPLATE_REPO_URL = 'https://raw.githubusercontent.com/browser-use/template-library/main'_fetch_template_list() 用 5 秒超时去拉 templates.json,拉不到就抛 FileNotFoundError('Could not fetch templates from GitHub. Check your internet connection.'),命令直接退出 1。所以 init 是一个联网命令,离线环境下它不会退化成本地模板,而是直接失败。

没给 --template 时进交互选择:用 InquirerPy 的 fuzzy 提示,提示语是 Select a template (type to search):,可以打字模糊搜索。候选排序有讲究 —— default_template_names = ['default', 'advanced', 'tools'] 这三个先排,然后是 featured 为真的(显示带 [FEATURED] 前缀),最后是其余的,三组内部都按清单里 author 字段下的 last_modified_date 倒序(取不到就当成很早的日期垫底)。_format_choice() 还会看终端宽度:大于 100 列连作者一起显示,大于 60 列只显示名称和描述,再窄就只剩名称。终端拉窄了看不到描述,属于预期行为。

落盘逻辑:目录是 Path.cwd() / template,默认文件名 main.py,给了 --output 则是 template_dir / Path(output)。模板清单里带 files 时会继续按 source / dest 逐个拉取,支持 binaryexecutable 两个标记,其中 executable 的 chmod 在 sys.platform != 'win32' 时才执行。最后打印的 Next steps 面板优先用清单里的 next_steps,没有则用内置流程:cd <template>uv inituv add browser-use、在 .env 里配 BROWSER_USE_API_KEYuv run main.py

browser-use skill 是另一个方向:它不生成你的项目文件,而是把用法写进你的编码代理。browser_use/skills/install.pyhandle() 支持 showinstall 两个子命令,缺省是 show(直接把技能正文打到 stdout)。install 的目标目录是一张表:

TARGET_DIR_BUILDERS = {
	'agents': lambda: _home_skill_dir('agents'),
	'claude': lambda: _home_skill_dir('claude'),
	'codex': lambda: _home_skill_dir('codex'),
	'copilot': lambda: _home_skill_dir('copilot'),
	'cursor': lambda: _home_skill_dir('cursor'),
	'gemini': lambda: _home_skill_dir('gemini'),
	'opencode': lambda: _xdg_config_home() / 'opencode' / 'skills' / SKILL_NAME,
}

_home_skill_dir(assistant) 拼的是 ~/.{assistant}/skills/browser-useopencode 单独走 XDG 路径,另外还兼容了 ~/.config/opencode/... 这条历史路径。--target 缺省是 all,也就是一次写进上面所有位置。要注意 install 默认会先跑 uv tool install --python 3.12 --upgrade --force browser-useuv 不在 PATH 里就报错退出;只想写 SKILL.md 不想动安装,加 --no-install--force 在这里是个兼容参数,源码的帮助文本直接写明:install 本来就会覆盖已有的 SKILL.md。

顺带一提 browser-use install(注意不是 init):_run_install_command() 拼的命令是 ['uvx', 'playwright', 'install', 'chromium'],Linux 上追加 --with-deps,最后统一追加 --no-shell。这条命令的存在意味着浏览器与系统依赖的安装被显式交给了外部工具,而不是包内自己实现一套。

四、配置从哪读:三条互不相通的路

这是最容易踩空的一块。browser_use/config.py 里同时存在三套东西,而它们服务的对象并不相同。

第一条是纯环境变量。OldConfig 把每个配置项写成 property,每次访问都重新 os.getenv(...)Config.__getattr__ 每次都新建一个 OldConfig 实例,注释写得很直白:确保环境变量在每次访问时被重新读取。代价是没有缓存、也没有启动期校验,改了环境变量立刻生效,写错了要等到真正访问那一刻才暴露。

第二条是 .env 加类型化字段。FlatEnvConfig(BaseSettings)model_configSettingsConfigDict(env_file='.env', env_file_encoding='utf-8', case_sensitive=True, extra='allow')case_sensitive=True 这一项意味着键名大小写必须完全对上,extra='allow' 则意味着你写错的键不会报错、只会被静默收进来。

第三条是 config.json。路径解析在 _get_config_path() 里,优先级一眼可见:

if env_config.BROWSER_USE_CONFIG_PATH:
	return Path(env_config.BROWSER_USE_CONFIG_PATH).expanduser()
elif env_config.BROWSER_USE_CONFIG_DIR:
	return Path(env_config.BROWSER_USE_CONFIG_DIR).expanduser() / 'config.json'
else:
	xdg_config = Path(env_config.XDG_CONFIG_HOME).expanduser()
	return xdg_config / 'browseruse' / 'config.json'

文件结构是 DBStyleConfigJSON,三个字典 browser_profile / llm / agent,键是 UUID,值继承 DBStyleEntry(带 iddefaultcreated_at)。取值时遍历找 default 为真的那条,找不到就取第一条。load_and_migrate_config() 的行为需要你心里有数:文件不存在就写一份默认的;识别为旧格式就用新的默认配置覆盖掉并记一条 debug 日志;解析抛异常同样覆盖。默认生成的 llm 条目里 api_key 是占位串 'your-openai-api-key-here',看到它就说明这份文件是刚被创建或刚被重置的。

_load_config()config.json 之上再叠一层环境变量覆盖:BROWSER_USE_HEADLESSBROWSER_USE_ALLOWED_DOMAINS(逗号分隔切成列表)、四个代理变量 BROWSER_USE_PROXY_URL / BROWSER_USE_NO_PROXY / BROWSER_USE_PROXY_USERNAME / BROWSER_USE_PROXY_PASSWORD(合成一个 proxy 字典,键为 server / bypass / username / password)、OPENAI_API_KEYBROWSER_USE_LLM_MODEL,以及 BROWSER_USE_DISABLE_EXTENSIONS(取反后写进 enable_default_extensions)。

现在是关键的那句判断:这套 config.json 体系的消费方不是你敲的那条 CLI 主路径。load_browser_use_config() 在仓库里的调用点是 browser_use/mcp/server.py,函数注释也写着「for MCP components」。CLI 主路径把执行交给 browser_harness 之后,连接与会话相关的开关走的是另一组前缀不同的环境变量 —— 仓库自带技能文件里列的是 BU_NAMEBU_CDP_URLBU_CDP_WS,以及 BH_RECORDBH_DOMAIN_SKILLSBH_AGENT_WORKSPACE。所以「我在 config.json 里改了 headless,为什么 CLI 没反应」这个问题,答案往往不是配置写错了,而是走错了体系。

组成部分它负责什么仓库位置你什么时候会碰到它
CLI 分流与 stdin 执行参数判断、遗留命令拦截、把代码交给 harness、遥测包装browser_use/cli.py每次敲 browser-use
init 模板生成联网拉模板清单、交互选择、落盘到 <template>/main.pybrowser_use/init_cmd.py起一个新脚本项目时
技能安装show / install,把 SKILL.md 写进各家代理目录browser_use/skills/install.py让编码代理记住用法时
技能正文连接模型、页面工作流、录制、易错点browser_use/skills/browser-use/SKILL.md想知道推荐做法时
环境变量与 config.json三套配置源、路径优先级、旧格式重置browser_use/config.py配代理、允许域名、密钥时
MCP stdio 服务--mcp 入口,仓库里读 config.json 的那一端browser_use/mcp/server.py把它挂给支持 MCP 的客户端时
CLI 版 MCP 服务--cli-mcp 入口browser_use/mcp/cli_mcp.py需要另一种 MCP 暴露方式时

顺便说清项目的规模轮廓,方便你判断该往哪儿翻:browser_use/llm/ 下有 15 个 provider 目录,browser_use/browser/watchdogs/ 下有 14 个 watchdog,browser_use/agent/system_prompts/ 下有 8 份系统提示词,examples/ 下有 124 个文件。许可证是 MIT。

五、边界与代价:它明确不管什么

把执行层外置给 browser_harness,换来的是 CLI 这边极薄,代价是可观测性被切成两段。main() 里有个 _delegated_to_harness 全局标志,一旦进了 harness,cli.py 自己就不再上报 CLI 事件;_StderrTail 只保留 stderr 的最后 500 个字符当错误上下文。你排查问题时,如果只盯着 browser_use/ 这个包,会发现线索在某处突然断掉 —— 那不是你漏看了,是边界就在那里。

遗留命令被彻底移除,代价是旧脚本一律要改。这个取舍换来的是接口收敛:不再维护一堆预置子命令的参数组合,只维护一组 Python 助手函数。但它同时意味着 CLI 的能力上限就是这组助手函数加上原始 cdp(...),需要更结构化的编排、重试、并发时,写脚本比堆 heredoc 合适得多。

init 依赖网络,离线直接失败,这一点在内网机器上会很扎人。config.json 遇到无法识别的格式会被重置而不是报错停下,对「配置文件即真相」的运维习惯不友好 —— 想保留原文件,就自己先备份。

还有一类它明确不管的事:合规与风险边界。这个工具驱动的是真实浏览器,本地流程默认附着到你正在使用的 Chrome,也就意味着它带着你的登录态操作真实账号。目标站点的使用条款是否允许自动化访问、被判定为异常流量后账号会承担什么后果、页面上的敏感数据会不会被打印进日志或截图,这些全部在工具职责之外,只能由你判断。仓库自带的技能文件也把这条写进了工作流:遇到登录墙就停下来问人,密码、多因素验证、授权确认、账号选择这些环节不要自动越过。碰到验证码与反自动化机制时,正确动作是停下重新评估这件事该不该自动化,而不是想办法绕过去。云端浏览器还有计费维度,用完记得停,各家规则不同且会调整,以官方最新说明为准。

六、上手与避坑清单

别在终端里裸敲 browser-use 然后等它给你交互界面。 会踩是因为「交互式」这个词容易被理解成 REPL。它给的是一段引导文本就返回了。正确姿势是用 heredoc 把代码喂进去;写在 shell 脚本里时更要注意,stdin 不是 tty 的情况下裸调会去读 stdin,读到空就返回 1。

别把 browser-use installbrowser-use init 当同一件事。 会踩是因为两个词都像「初始化」。前者调 uvx playwright install chromium 装浏览器和系统依赖,后者拉模板生成项目文件。装环境时敲错成 init,你会得到一个新目录和一个 main.py,浏览器还是没装。

别在离线或网络受限的机器上指望 init 会踩是因为习惯性认为脚手架模板打在包里。它是从 GitHub 原始文件地址拉 templates.json,超时 5 秒,失败就退出 1。内网环境下的替代做法是照着 examples/ 里的文件手写一个起点,或者在能联网的机器上生成好再拷进去。

别默认 browser-use skill install 只写文件。 会踩是因为「install skill」听起来只是安装一份文档。它默认会先执行 uv tool install --python 3.12 --upgrade --force browser-use,把你的 browser-use 升级并强制重装。不想动版本就加 --no-install;另外 --target 缺省是 all,会把 TARGET_DIR_BUILDERS 里那七家的目录一次写全,再加上 opencode 的历史路径,只想写一家就显式指定。

别把 config.json 当成 CLI 主路径的配置源。 会踩是因为项目里确实有一套完整的 config.json 体系,看着就该是全局配置。但它的调用点在 MCP 服务那一端;CLI 走 harness,用的是 BU_ / BH_ 前缀的环境变量。改之前先确认你要影响的是哪一端。

.env 时注意大小写。 会踩是因为 FlatEnvConfig 显式设了 case_sensitive=True,同时 extra='allow'。键名大小写写错不会报错,只会被当成一个无人读取的额外字段,表现是「我明明配了但没生效」。

旧格式 config.json 会被覆盖。 会踩是因为大多数工具遇到不认识的配置会报错停下。这里的实现是生成一份新的默认配置写回去,只留一条 debug 级日志。升级前先把文件复制一份。

遗留命令的退出码是 2,不是 1。 会踩是因为 CI 脚本里常按 != 0 一把抓,看不出区别。想在流水线里区分「命令写法过时」和「执行失败」,就得把 2 单独判一下 —— 主路径里 piped 代码抛出的 NameError 也是返回 2。

收尾给你一条自检顺序。手上这台机器行为不对时,按这个顺序看:先确认你敲的是不是 browser-use 本体而不是那个已弃用的 tui 别名;再确认命令有没有被 _LEGACY_HINTS 拦住(看 stderr 有没有那段迁移提示);再确认代码是通过 stdin 进去的;再确认你要改的配置属于 config.json 那一端还是 harness 环境变量那一端。接下来该读哪个文件也很清楚:想搞清连接模型与页面操作的推荐做法,读 browser_use/skills/browser-use/SKILL.md;想搞清参数分流的确切顺序,读 browser_use/cli.py 里的 _dispatch();想搞清配置优先级,读 browser_use/config.py 里的 _get_config_path()_load_config()

如果你的活最终要落到「批量抓取结构化数据」,那 CLI 只是探路阶段的工具,思路可以参考让 AI 写爬虫的正确姿势;如果你打算把它当 MCP 服务挂给客户端用,配置那一端的坑可以先看MCP 配置教程。CLI 顺手的场景很具体:一次性的页面核对、带着登录态看一眼后台数据、给编码代理一个能自己验证前端改动的手。需要重试策略、并发隔离、结果落库的活,从第一行就该写成脚本。

本篇属于一个把开源浏览器操作 Agent 项目 browser-use逐层拆开讲的系列,整体地图见 browser-use 是什么;沿着这条线往下,还可以看 browser-use 的 LLM 适配层怎么接国产模型与本地模型给 browser-use 加自定义动作

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。