AI 怎么操控浏览器?
从一个最简示例出发,看懂 browser-use 如何让 AI 像人一样"看见"网页、"点击"按钮。
5 行代码,AI 就能帮你上网干活
想象一下:你跟 AI 说"去 GitHub 查一下 browser-use 这个仓库有多少星",AI 就真的打开浏览器、搜索、找到结果、告诉你答案。这就是 browser-use 做的事情。
最简的用法只需要这几行:
from browser_use import Agent, ChatBrowserUse
agent = Agent(
task='Find the number of stars of the following repos: browser-use, playwright, stagehand, react, nextjs',
llm=ChatBrowserUse(model='bu-2-0'),
)
agent.run_sync()
引入两个主角:Agent 是"干活的机器人",ChatBrowserUse 是它的大脑(AI 模型)。
创建一个 Agent,告诉它任务是什么——这里是要查 5 个 GitHub 仓库的星数。
给它装上 browser-use 官方微调过的模型,专门擅长操控浏览器。
一行启动!run_sync() 会阻塞运行直到任务完成。
browser-use 的设计哲学是"一行代码开跑"。你不需要手动管理浏览器进程、写 DOM 选择器、处理弹窗——Agent 把这些全部包了。你的工作就是:定义任务 + 选模型。
完整一点的写法:手动配浏览器
上面的例子用了"默认一切"的写法。如果你想要更多控制——比如用自己的模型、指定浏览器配置——可以这样做:
from browser_use import Agent, Browser, ChatBrowserUse
import asyncio
async def main():
browser = Browser()
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(),
browser=browser,
)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
引入 Agent(机器人)、Browser(浏览器实例)、ChatBrowserUse(AI 大脑)。
browser-use 是异步架构,所以主函数要用 async def。
Browser() 创建一个浏览器实例,可以传各种配置参数。
创建 Agent,把任务、大脑、浏览器三者绑定在一起。
agent.run() 启动异步循环,Agent 开始一步步执行任务。
asyncio.run() 是 Python 标准的"启动异步程序"写法。
它到底在做什么?一眼看懂循环
Agent.run() 启动后,内部是一个"观察→思考→行动"的循环,每一步都是:
这个循环在源码中就是 Agent.run() 里的 while self.state.n_steps <= max_steps 循环,每次循环调用一次 step()。
测测你理解了多少
browser-use 的 Agent 执行任务时,内部是什么模式?
认识核心角色
Agent、Browser、DOM Service、Tools 四大角色各司其职——搞清谁干什么,源码就通了。
四个角色,一张图看明白
browser-use 的架构像一家小公司:老板下达任务,四个人各干各的活。
Agent
总指挥。接收用户任务,循环"观察→思考→行动",协调其他三个角色完成目标。源码在 agent/service.py。
Browser / BrowserSession
浏览器管家。通过 CDP 协议 控制真实的 Chromium 浏览器:开页面、截图、管理 Tab。源码在 browser/session.py。
DomService
AI 的眼睛。把网页 DOM 树解析成 LLM 能理解的文本格式——哪些元素可以点击、输入框在哪、按钮叫什么。源码在 dom/service.py。
Tools
AI 的手。把 LLM 输出的"动作意图"翻译成真实浏览器操作——点击、输入、滚动、导航。源码在 tools/service.py。
角色对话:一次完整执行长什么样?
下面模拟一次"查 GitHub 星数"任务中四个角色之间的对话,帮你感受数据在它们之间怎么流动的:
Agent 的 __init__:像搭积木一样组装
Agent 的构造函数接收大量参数,但核心就是"把四个角色绑在一起"。看看源码中最关键的几行:
class Agent(Generic[Context, AgentStructuredOutput]):
def __init__(
self,
task: str,
llm: BaseChatModel | None = None,
browser_session: BrowserSession | None = None,
browser: Browser | None = None,
tools: Tools[Context] | None = None,
controller: Tools[Context] | None = None,
...
):
Agent 是一个泛型类,支持自定义上下文和结构化输出类型。
task 是必填参数——你要 AI 干什么。
llm 是 AI 大脑,不传就用默认的 ChatBrowserUse。
browser_session 和 browser 是同一个东西(别名),传一个就行。
tools 和 controller 也是别名——是"手",不传就用默认的 Tools()。
省略号代表还有几十个可选参数,比如超时、截图、敏感数据等。
browser-use 大量使用"别名参数"模式:browser 和 browser_session 是同一个东西,tools 和 controller 也是。这让 API 对新手更友好——不管你叫哪个名字都能用。
测测你理解了多少
browser-use 中,哪个角色负责把网页 HTML 翻译成 LLM 能理解的文本?
AI 的眼睛和手
DomService 怎么把网页变成 AI 能读的文本?Tools 怎么把 AI 的想法变成真实操作?
DomService:给 AI 一双"读网页"的眼睛
LLM 不能直接看 HTML——它需要一段结构化的文本描述,告诉它"页面上有什么、在哪里、能干什么"。DomService 就是干这个的:
class DomService:
def __init__(
self,
browser_session: 'BrowserSession',
cross_origin_iframes: bool = False,
paint_order_filtering: bool = True,
max_iframes: int = 100,
max_iframe_depth: int = 5,
viewport_threshold: int | None = 1000,
):
DomService 绑定一个浏览器会话——它要解析哪个页面的 DOM,就看这个会话。
cross_origin_iframes:要不要解析跨域的 iframe(嵌套网页)。默认不解析,安全。
paint_order_filtering:按绘制顺序过滤元素——被遮挡的元素不传给 AI,减少噪音。
max_iframes / max_iframe_depth:防止嵌套太多 iframe 导致性能爆炸。
viewport_threshold:视口阈值——超出视口太远的元素会被标记为"不可见"。
DomService 最终调用 DOMTreeSerializer 把 DOM 树变成一段文本。这段文本长这样:
<page_stats>
12 links, 5 interactive, 1 iframes, 48 total elements
</page_stats>
[Start of page]
<nav>
[<a href="/">] Home
[<input type="search" placeholder="Search..."*>]
</nav>
[End of page]
先是页面统计:有多少链接、多少可交互元素、多少 iframe——给 AI 一个全局概览。
然后是页面的结构化表示,用类似 HTML 的格式,但只保留 AI 需要关心的元素。
方括号标记可交互元素(链接、输入框、按钮),AI 看到这些就知道"这里可以点击/输入"。
[Start/End of page] 告诉 AI 当前视口是否覆盖了整个页面——没覆盖就要滚动。
Tools:把 AI 的想法变成真实操作
LLM 返回的不是 Python 代码,而是结构化的 JSON,像这样:{"click": {"index": 5}}。Tools 类负责把这个 JSON 翻译成真实的浏览器操作。
class ClickElementAction(BaseModel):
index: int | None = Field(
default=None, ge=1,
description='Element index from browser_state'
)
coordinate_x: int | None = Field(
default=None,
description='Horizontal coordinate relative to viewport left edge'
)
coordinate_y: int | None = Field(
default=None,
description='Vertical coordinate relative to viewport top edge'
)
ClickElementAction 是一个 Pydantic 模型——定义了"点击"动作的参数格式。
index:通过元素编号点击。DomService 给每个可交互元素编了号,AI 引用编号就行。
coordinate_x / y:通过坐标点击。有些模型(如 Claude Sonnet 4)支持坐标点击,更精准。
两种方式二选一——有 index 用 index,没有就用坐标。
数据流动:从网页到 AI 再到手
一次完整的"点击"操作,数据经历了这些环节:
不只是点击:Tools 支持的所有动作
browser-use 内置了大量动作类型,每种都对应一个 Pydantic 模型:
NavigateAction
打开一个 URL——就像你在地址栏输入网址按回车
ClickElementAction
点击元素——通过编号或坐标,就像鼠标左键单击
InputTextAction
在输入框里打字——先点击,再输入文字
ScrollAction
滚动页面——上下滚、滚到指定元素位置
SearchAction
用搜索引擎搜索——默认用 DuckDuckGo(验证码少)
ExtractAction
从当前页面提取信息——用 LLM 从页面内容中抽取结构化数据
SendKeysAction
发送键盘按键——回车、Tab、Escape 等特殊键
DoneAction
任务完成!告诉 Agent "我搞定了",并附上最终答案
所有动作都用 Pydantic 模型定义——这意味着 LLM 的输出会被自动校验。如果 AI 说了"点击编号 -1 的元素",Pydantic 会拒绝(因为 ge=1 限制最小值为 1)。这种"在数据层面保证安全"的思路很值得学习。
测测你理解了多少
LLM 怎么知道要点击哪个元素?
多模型大脑
browser-use 怎么同时支持 OpenAI、Anthropic、Google、Ollama 等十几种模型?秘密在 BaseChatModel 协议。
十一个 Chat 类,一个统一协议
在 __init__.py 的导出列表里,你能看到 browser-use 支持的所有模型:
__all__ = [
'Agent', 'BrowserSession', 'Browser',
'Controller', 'DomService', 'SystemPrompt',
# Chat models
'ChatOpenAI',
'ChatGoogle',
'ChatAnthropic',
'ChatBrowserUse',
'ChatGroq',
'ChatLiteLLM',
'ChatMistral',
'ChatAzureOpenAI',
'ChatOCIRaw',
'ChatOllama',
'ChatVercel',
]
核心导出就那几个:Agent、Browser、Controller、DomService。
然后是整整 11 个 Chat 类!每个对应一家 AI 模型提供商。
ChatBrowserUse 是官方微调模型,专门为浏览器操控优化。
ChatOllama 支持本地模型——不想把数据发到云端就用它。
ChatLiteLLM 是个万能适配器,能连接 100+ 模型提供商。
这 11 个类之所以能互换使用,是因为它们都遵循同一个协议:BaseChatModel。
BaseChatModel:一份"大脑入职合同"
Protocol 是 Python 的接口机制。BaseChatModel 定义了"作为一个合格的 AI 大脑,你必须会什么":
@runtime_checkable
class BaseChatModel(Protocol):
model: str
@property
def provider(self) -> str: ...
@property
def name(self) -> str: ...
async def ainvoke(
self,
messages: list[BaseMessage],
output_format: type[T] | None = None,
**kwargs: Any
) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]: ...
@runtime_checkable 让你能在运行时检查一个对象是否符合协议。
model: str —— 你得告诉别人你是什么模型(比如 "gpt-4o"、"claude-sonnet-4-6")。
provider 属性 —— 你是哪家的?("openai"、"anthropic"、"google"...)
name 属性 —— 你的显示名称。
ainvoke 方法 —— 这是核心!接收消息列表,返回 AI 的回复。支持结构化输出(output_format)。
browser-use 用 Protocol 而非抽象基类,意味着你甚至可以传入自己写的类,只要它有 model 属性和 ainvoke 方法,Agent 就能用——零侵入式扩展。
以 ChatOpenAI 为例:怎么跟具体模型通信
看看 ChatOpenAI 是怎么实现 BaseChatModel 协议的:
@dataclass
class ChatOpenAI(BaseChatModel):
model: ChatModel | str
temperature: float | None = 0.2
frequency_penalty: float | None = 0.3
api_key: str | None = None
base_url: str | httpx.URL | None = None
max_retries: int = 5
@property
def provider(self) -> str:
return 'openai'
用 @dataclass 自动生成 __init__,省去手写样板代码。
model 是必填的——"gpt-4o"、"gpt-4.1-mini" 等等。
temperature 默认 0.2——偏向确定性输出,适合自动化场景。
frequency_penalty 0.3 防止模型无限重复(比如死循环输出制表符)。
max_retries=5:浏览器自动化网络不稳定,多试几次更可靠。
provider 属性返回 "openai"——Agent 用它来判断模型类型,自动调整提示词。
Agent 怎么根据模型自动调参?
Agent 初始化时会检查模型类型,自动调整行为:
# set flashmode = True if llm is ChatBrowserUse
if llm.provider == 'browser-use':
flash_mode = True
# Auto-configure llm_screenshot_size for Claude Sonnet
if llm_screenshot_size is None:
model_name = getattr(llm, 'model', '')
if isinstance(model_name, str) and model_name.startswith('claude-sonnet'):
llm_screenshot_size = (1400, 850)
# DeepSeek models do not support vision
if 'deepseek' in self.llm.model.lower():
self.settings.use_vision = False
如果用的是 browser-use 官方微调模型,自动开启 flash_mode(精简提示词,更快)。
如果是 Claude Sonnet,自动把截图缩放到 1400x850——这个模型对图片大小有偏好。
如果是 DeepSeek 模型,关闭视觉功能——它还不支持图片理解。
这就是 provider 属性的价值:Agent 能根据模型自动做适配。
如果你用了 DeepSeek 或 Grok-3 模型但忘了关闭 use_vision,Agent 会自动帮你关并打印警告。但最好一开始就设置好,避免浪费 API 调用。
测测你理解了多少
如果你想用自己的 AI 模型接入 browser-use,需要怎么做?
从看懂到会改
自定义 Action、System Prompt,以及实际调试技巧——学完这章你就能魔改 browser-use 了。
SystemPrompt:AI 的"工作手册"
Agent 给 LLM 的系统提示词不只是一段文字,而是一套精心设计的"工作手册"。看看它怎么加载的:
class SystemPrompt:
def __init__(
self,
max_actions_per_step: int = 3,
override_system_message: str | None = None,
extend_system_message: str | None = None,
use_thinking: bool = True,
flash_mode: bool = False,
is_anthropic: bool = False,
is_browser_use_model: bool = False,
model_name: str | None = None,
):
max_actions_per_step:每步最多执行几个动作(默认 3)。限制数量让 AI 更谨慎。
override_system_message:完全替换系统提示词——你说了算。
extend_system_message:在默认提示词后面追加内容——更安全的方式。
use_thinking:让 AI 在行动前先"想一下",输出思考过程。
flash_mode:精简模式——去掉思考和评估,追求速度。
is_anthropic / is_browser_use_model:根据模型类型选择不同的提示词模板。
它内部会根据模型类型选择不同的提示词模板文件:
if self.is_browser_use_model:
if self.flash_mode:
template_filename = 'system_prompt_browser_use_flash.md'
elif self.use_thinking:
template_filename = 'system_prompt_browser_use.md'
elif self.flash_mode and self.is_anthropic:
template_filename = 'system_prompt_flash_anthropic.md'
elif self.flash_mode:
template_filename = 'system_prompt_flash.md'
elif self.use_thinking:
template_filename = 'system_prompt.md'
else:
template_filename = 'system_prompt_no_thinking.md'
官方微调模型用专属模板(browser_use 后缀),更精简高效。
每种组合(flash/thinking/anthropic)都有对应的 .md 模板文件。
这意味着你可以在不改源码的情况下,替换这些 .md 文件来定制 AI 行为。
最终,模板里的 {max_actions} 占位符会被替换成实际数字。
自定义 System Prompt:让 AI 更懂你的场景
browser-use 提供两种方式定制系统提示词。最常用的是 extend_system_message——在默认提示词后面加内容:
agent = Agent(
task='Search for AI news and summarize',
llm=ChatOpenAI(model='gpt-4o'),
extend_system_message="""
Additional rules:
1. Always click "Accept cookies" if a cookie banner appears
2. Never sign up or log in to any website
3. If a CAPTCHA appears, skip that site and move on
""",
)
extend_system_message 是追加的——默认提示词还在,你只是在后面加了规则。
规则 1:遇到 Cookie 弹窗自动点"接受"——省得 AI 卡在这里。
规则 2:永远不要注册或登录——防止 AI 乱填表单。
规则 3:遇到验证码就跳过——不浪费时间。
override_system_message 是完全替换,适合深度定制。但新手建议用 extend_system_message,因为默认提示词经过大量测试调优,里面有处理各种边界情况的指令。
AgentMessagePrompt:每次发给 LLM 的完整上下文
每一步,Agent 都会构造一个超长的"用户消息"发给 LLM。这个消息包含了 AI 做决策需要的所有信息:
state_description = (
'<agent_history>\n'
+ (self.agent_history_description.strip('\n')
if self.agent_history_description else '')
+ '\n</agent_history>\n\n'
)
state_description += '<agent_state>\n'
+ self._get_agent_state_description().strip('\n')
+ '\n</agent_state>\n'
state_description += '<browser_state>\n'
+ self._get_browser_state_description().strip('\n')
+ '\n</browser_state>\n'
agent_history:之前做了什么——AI 需要知道历史才能避免重复。
agent_state:当前任务描述、文件系统状态、todo 列表、敏感数据等。
browser_state:当前页面的 DOM 元素列表、截图、Tab 信息、滚动位置。
三个 XML 标签把不同类型的信息分块,方便 LLM 区分理解。
循环检测:防止 AI 在原地打转
AI 有时会陷入死循环——反复点击同一个按钮,或者在两个页面之间来回跳。browser-use 内置了 ActionLoopDetector 来应对:
def get_nudge_message(self) -> str | None:
if self.max_repetition_count >= 12:
messages.append(
f'Heads up: you have repeated a similar action'
f' {self.max_repetition_count} times in the last'
f' {len(self.recent_action_hashes)} actions.'
)
elif self.max_repetition_count >= 5:
messages.append(
f'Heads up: you have repeated a similar action'
f' {self.max_repetition_count} times.'
)
循环检测器持续统计最近 20 步中每个动作的哈希值出现次数。
重复 5 次:温和提醒"你重复了,还在进步吗?"
重复 12 次:严肃警告"你可能卡住了,换个方法吧。"
注意:这只是"提醒",不是"禁止"。AI 可以忽略提醒继续执行——毕竟有时候重复是合理的(比如翻页)。
browser-use 的循环检测是"软约束"而非"硬约束"——它只给 LLM 提示,不强制阻止动作。这种设计尊重了 AI 的判断力:有时候重复是有意的(比如逐页翻页),硬阻断反而会打断正确行为。
step() 方法:一次完整执行的四阶段
Agent 的核心方法是 step(),每一步都严格按照四个阶段执行:
如果之前在解验证码,先等它解完再继续。
截图 + 获取浏览器状态 + 解析 DOM + 更新动作模型。
把上下文发给 LLM,拿到 JSON 动作,Tools 翻译成浏览器操作并执行。
更新历史、检查下载、判断是否完成、记录遥测数据。
调试技巧:看日志、存对话、设超时
设置这个参数,Agent 会把每一步发给 LLM 的完整对话保存到文件——调试时的第一选择。
llm_timeout 控制 LLM 调用的超时(默认 60s),step_timeout 控制整步超时(默认 180s)。网络慢时可以调大。
连续失败多少步后停止(默认 5)。调试时建议设成 2,快速发现问题。
agent = Agent(
task='Debug example task',
llm=ChatOpenAI(model='gpt-4o'),
save_conversation_path='./debug_conversation',
max_failures=2,
llm_timeout=90,
step_timeout=120,
max_actions_per_step=1,
)
save_conversation_path:把每步对话存到 ./debug_conversation 目录。
max_failures=2:连续失败 2 次就停——快速暴露问题。
llm_timeout=90:给 LLM 90 秒响应时间(默认 60 可能不够)。
step_timeout=120:每步最多执行 120 秒。
max_actions_per_step=1:每步只做一个动作——方便逐步调试。