Browser-Use 深度拆解:当 AI 学会「看懂网页」——可访问性树、Playwright 内核与感知-决策-执行闭环如何重写浏览器自动化的心智模型
关键词:Browser-Use / AI 浏览器自动化 / Playwright / 可访问性树 / 感知-决策-执行闭环
视角:一个被 Selenium 和 XPath 折磨过的后端工程师,试图讲清楚"让大模型替我点网页"这件事到底是怎么发生的。
引子:一句自然语言,浏览器自己动起来了
如果你写过爬虫或自动化脚本,大概率经历过这样的夜晚:一个页面的 class 名从 btn-primary 改成了 btn-submit,你写了 200 行的 Selenium 脚本当场全部罢工;产品改了一次表单结构,你的端到端测试红成一片。
传统浏览器自动化的心智模型是:人类把"步骤"翻译成代码,机器忠实地重放步骤。问题在于——步骤是脆弱的。网页是会变的,UI 是会重构的,选择器是会过期的。你维护的不是"我要做的事",而是"网页现在的样子"。
Browser-Use 这类项目想做的事很彻底:把"步骤"彻底删掉,只留下"目标"。你告诉它"在 Google Flights 上找 1 月 12 号从巴厘岛到阿曼最便宜的单程机票",它就自己打开浏览器、自己理解页面、自己点、自己读结果、自己回来给你答案。
这不是 RPA(机器人流程自动化)的升级版,这是范式切换:从"指令式"到"目标式",从"选择器驱动"到"语义驱动"。本文从工程视角彻底拆解它背后的技术骨架——可访问性树如何成为 AI 的"眼睛"、Playwright/CDP 如何成为"手脚"、感知-决策-执行闭环如何被一个异步 Python 循环串起来,并手撸一个能跑的最小 browser agent,最后聊清楚它在生产环境里的真实成本与边界。
一、为什么"让 AI 操作浏览器"这么难
要理解 Browser-Use 的价值,先得理解它要解决的三道坎。
1.1 传统自动化的"选择器脆弱性"
Selenium / Playwright 的脚本本质上是这样工作的:
# 传统脚本:把"人看到的步骤"翻译成"机器能执行的定位"
driver.find_element(By.ID, "username").send_keys("admin")
driver.find_element(By.XPATH, "//button[@class='login-btn']").click()
这段代码的隐含假设是:页面上永远存在一个 id=username 的输入框,永远有一个 class=login-btn 的按钮。这个假设在真实世界里平均存活 3 到 6 个月。一旦前端重构(甚至只是一次 A/B 实验改了 DOM),脚本就挂。
更致命的是反脆弱性为零:脚本不会"变通"。人看到登录框换了位置照样能登,脚本看到 id 变了就彻底懵了。
1.2 大模型的"输入格式鸿沟"
第二个坎更本质:大模型吃的是 token 序列,浏览器渲染的是 像素 + DOM 树。两者之间隔着一条鸿沟。
- 你把整页 HTML 丢给模型?一个中等页面的 HTML 动辄 50k~200k 字符,直接撑爆上下文,而且里面充斥着
<script>、<style>、嵌套 div 噪音,模型根本抓不住重点。 - 你截图给多模态模型?它能"看到"页面,但很难精确地"点到第 3 个搜索结果里的'查看详情'链接",因为像素坐标不稳定、按钮位置会变。
- 你需要的是一种既结构化、又稳定、且信息密度高的中间表示。
1.3 长程任务的"规划与记忆"
第三道坎是任务的"长度"。填一个登录表单是 3 步;"比价 10 个电商、整理成表格"是 100+ 步。模型在长程任务里会:忘记前面看过啥、在循环里打转、点错后不会自纠、被弹窗打断就卡死。
Browser-Use 的解法,就是把这三道坎分别用**可访问性树(感知)、LLM 决策(大脑)、Playwright 执行(手脚)、闭环 + 历史管理(记忆)**四件套接住。下面逐层拆。
二、核心概念:AI 浏览器的"感知-决策-执行"闭环
所有 browser agent 都可以抽象成一个感知-决策-执行(Perceive-Decide-Act)闭环。这个闭环每转一圈,就叫一个 step。
┌─────────────┐
│ 感知 │ 把当前网页变成模型能懂的结构化状态
│ Perceive │ (可访问性树 + 可选的截图)
└──────┬──────┘
│ state
▼
┌─────────────┐
│ 决策 │ LLM 读 state + 历史 + 任务,输出一个动作
│ Decide │ (click / type / scroll / extract / done ...)
└──────┬──────┘
│ action
▼
┌─────────────┐
│ 执行 │ Playwright 在真实浏览器里把动作落下去
│ Act │ 得到新页面 → 回到感知
└──────┬──────┘
│
▼
(Done? 或步数耗尽? → 结束,否则循环)
这套范式和强化学习的 agent、机器人学的 sense-plan-act 是同构的。区别只在于:这里的"世界"是无头浏览器,"动作"是 DOM 操作,"奖励"由任务是否完成来隐式定义。
2.1 三个世界:像素 / DOM / 语义
理解 Browser-Use,关键是分清它面对的"三个世界":
| 世界 | 表示 | 优点 | 缺点 | 谁在用 |
|---|---|---|---|---|
| 像素世界 | 截图(PNG) | 最真实,能看到 CSS 渲染、图表、验证码 | 坐标不稳定,模型难精确定位,token 贵 | Claude Computer Use、视觉模型 |
| DOM 世界 | 原始 HTML | 信息完整 | 噪音巨大,太长,选择器易碎 | 老式爬虫 |
| 语义世界 | 可访问性树 | 只留可交互 + 有意义节点,稳定、短、结构化 | 看不到纯视觉内容(图表/canvas) | Browser-Use |
Browser-Use 的聪明之处,是默认站在"语义世界",必要时再用截图补"像素世界"。这让它比纯视觉方案便宜一个数量级,又比纯 DOM 方案干净得多。
2.2 可访问性树(Accessibility Tree)到底是什么
每个现代浏览器内核(Blink/WebKit/Gecko)在渲染时,除了生成可见的像素,还会维护一棵可访问性树——这是给屏幕阅读器(screen reader)、给残障人士用的"页面语义骨架"。
这棵树有几个关键特性,恰好完美适配 AI:
- 只保留"有意义的节点":
<div>、<span>这种纯布局标签会被折叠或丢弃,保留的是<button>、<a>、<input>、<select>、有role的元素。 - 自带语义角色:每个节点有
role(button / link / textbox / combobox)、name(可读文本)、value、state(disabled / checked)。 - 对视觉变化鲁棒:只要"这是一个登录按钮"的语义没变,哪怕它换了个位置、换了个样式,在可访问性树里的角色还是
button。
Browser-Use 做的事,就是把可访问性树里所有可交互元素,按顺序编号,然后连同它们的"角色 + 文本"一起喂给模型。于是模型看到的不再是:
<div class="relative"><button class="css-1a2b3c flex items-center bg-blue-600 rounded px-4 py-2 hover:bg-blue-700" data-testid="cta">立即登录</button></div>
而是:
[42] <button> 立即登录
[43] <input type="text" placeholder="请输入手机号">
[44] <a href="/help"> 帮助中心
模型只需要输出 {"action": "click", "index": 42},Playwright 就能精准点到那个按钮——完全不依赖 class 名、不依赖 XPath、不依赖坐标。这就是"语义驱动"的威力:前端随便重构,只要语义没变,agent 照点不误。
2.3 动作空间(Action Space)
模型不是"自由发挥"的,它在一个封闭的、预定义的动作空间里做选择。Browser-Use 的核心动作大致包括:
ClickElement:点击编号元素InputText:往编号输入框打字Scroll:向上/下滚动(拿到底部或顶部的隐藏内容)SearchGoogle:直接搜一个关键词(绕过首页)GoToUrl:跳转到指定 URLExtractPageContent:把当前页正文抽取成干净文本(用于"读答案")Done:宣告任务完成,并附上结果- 以及可扩展的自定义动作(后面实战会写)
把动作空间"封闭化 + 结构化",是工程上的关键决策:它让模型的输出从"自然语言"变成"可解析的 JSON/对象",避免了"模型写了一句'我点击了登录按钮'"这种无法执行的模糊输出。
三、架构分析:Browser-Use 是怎么把闭环跑起来的
理解了概念,看架构。Browser-Use 的技术栈极简而克制:
- 浏览器控制:Playwright(底层走 Chrome DevTools Protocol,简称 CDP)。Playwright 负责无头/有头地拉起 Chromium、导航、点击、填表、截图。
- 大脑:任意兼容 Chat Completions 协议的 LLM(OpenAI / Anthropic / 本地 Ollama 都行),通过 LangChain 的模型抽象接入。
- 并发模型:全程
asyncio异步——因为等网页加载、等模型推理都是 IO 密集。
它的核心模块可以拆成四块:
┌────────────────────────────────────────────────────────────┐
│ Agent │
│ 驱动整个 step 循环:感知→决策→执行→观察→判定是否结束 │
└───────────┬───────────────────────────┬──────────────────┘
│ │
┌────────▼─────────┐ ┌────────▼──────────┐
│ Browser │ │ MessageManager │
│ Playwright 封装 │ │ 维护 system prompt │
│ 导航/点击/截图 │ │ + 历史 + 当前状态 │
└────────┬─────────┘ └────────┬──────────┘
│ │
┌────────▼─────────┐ ┌────────▼──────────┐
│ DomService │ │ Controller │
│ 提取可访问性树 │ │ 动作定义 + 执行器 │
│ 元素编号/压缩 │ │ 内置动作 + 自定义 │
└──────────────────┘ └─────────────────────┘
3.1 Browser:Playwright 的薄封装
Browser 模块干的是脏活:启动浏览器上下文(context)、管理多标签页、执行 page.goto / page.click / page.fill / page.screenshot。它把 Playwright 的原语包成 agent 友好的方法。关键设计是用 Playwright 的 locator 而非坐标或 XPath 来落动作——当你点编号 42 的按钮,Browser-Use 实际是用 DOM 里记录的该元素特征(通常是 XPath 或 selector 缓存)去定位它,而不是屏幕坐标。这也解释了为什么它比纯视觉方案稳。
3.2 DomService:把网页"翻译"成模型语言
这是整个项目最值钱的部分。DomService 拿到 page.content()(原始 HTML)后:
- 用解析器(基于
lxml/ 自写规则)遍历 DOM; - 过滤掉
<script>、<style>、纯装饰节点; - 识别可交互元素,给它们分配递增索引;
- 生成一段干净的"带编号的元素清单"文本,外加页面可见文本摘要;
- 可选地调用
page.screenshot()拿到截图。
一个工程上很关键的细节是历史树压缩(HistoryTreeProcessor):网页状态是逐 step 累积的,但你不能把 100 步的 DOM 全塞进上下文。所以 DomService 只把"当前这一步的精简状态"发给模型,而把更早的历史交给 MessageManager 做裁剪。
3.3 MessageManager:上下文的"守门人"
LLM 的上下文窗口是有限的,而 agent 每步都在产生新内容。MessageManager 负责:
- 拼装 system prompt(定义角色、动作空间、输出格式、约束如"不要编造 URL");
- 维护对话历史(模型每步的思考 + 动作 + 执行结果);
- 在接近窗口上限时裁剪旧消息(保留 system、保留最近几步、压缩中间);
- 把"当前状态"以固定格式注入每步的用户消息。
它的设计哲学是:历史要丢,但"最近发生了什么"必须保。这也是长程任务能不能跑完的关键。
3.4 Controller 与动作执行
Controller 是"动作空间"的落地层。内置动作(点击、输入、滚动…)各自对应一个 Playwright 调用;自定义动作通过装饰器注册,等下实战会写。动作执行后,结果(成功 / 报错 / 新页面内容)被封装回消息流,喂给下一步的模型。
3.5 一个 step 的真实时序
把上面串起来,单步是这样的:
1. Agent 调 DomService.get_state() → 拿到带编号的元素清单 + 截图(可选)
2. MessageManager 组装本步 user message:"当前页面状态:...[42]按钮 登录... 你的任务:... 上一步结果:..."
3. 调 LLM.chat(messages) → 模型返回结构化动作,例如 ClickAction(index=42)
4. Controller 执行动作 → Browser.click(42)
5. 等待页面稳定(网络空闲/超时) → 得到新 DOM
6. 把"动作 + 结果"写回 history
7. 判定:模型说 Done? 或 step >= max_steps? → 结束;否则回到 1
看到这里你会意识到:Browser-Use 本身几乎不"智能",它的智能全部来自 LLM;它的工程价值在于把"网页"和"LLM"之间的接口做对了——正确的感知表示、封闭的动作空间、稳健的执行层、可控的历史管理。这也是为什么它能被任何模型驱动、也能被轻易替换模型。
四、代码实战
光讲不练假把式。这一节先跑通 Browser-Use 官方用法,再手撸一个最小可用版本,让你看清闭环全貌。
4.1 跑通第一个任务
pip install browser-use
playwright install chromium # 关键:下载无头浏览器内核
import asyncio
from langchain_openai import ChatOpenAI
from browser_use import Agent
async def main():
# 任意兼容 Chat Completions 的模型都行
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = Agent(
task=(
"打开 https://www.bing.com ,"
"搜索 '程序员茄子',"
"返回第一个结果的标题和链接。"
),
llm=llm,
# 可选:max_steps=20, use_vision=True(视觉模型才开)
)
result = await agent.run()
print(result)
asyncio.run(main())
就这几行,"打开→搜索→读结果"的整套感知-决策-执行就被驱动起来了。但注意:这背后是几十次 LLM 调用 + 几十次浏览器操作。这也是后面要聊的"成本"伏笔。
4.2 自定义动作:把"保存到数据库"接进来
Browser-Use 最强的扩展点是 @controller.action。内置动作不够用时,你可以教 agent 一个新能力:
from browser_use import ActionResult, Controller
from pydantic import BaseModel
controller = Controller()
class SaveLeadParams(BaseModel):
name: str
email: str
@controller.action("把一条销售线索存进数据库")
async def save_lead(params: SaveLeadParams):
# 这里可以是任何副作用:写 DB、调 API、发消息
row = await db.leads.insert({
"name": params.name,
"email": params.email,
"captured_at": now(),
})
return ActionResult(
extracted_content=f"已保存线索 {params.name}",
# include_in_memory=True 会把结果告诉模型
include_in_memory=True,
)
# 把带自定义动作的 controller 注入 Agent
agent = Agent(task="...遍历列表,把每个联系人的名字邮箱存库...",
llm=llm, controller=controller)
这个设计的精髓在于:agent 的动作空间是可编程的。你不是在"写脚本",而是在"教一个员工新技能"。这让 Browser-Use 能从"玩具 Demo"走向"真实业务流"(自动录入、自动比价、自动监控)。
4.3 手撸一个迷你 browser agent(核心闭环从零实现)
下面这段代码不依赖 Browser-Use,只用 Playwright + 一个 OpenAI 兼容接口,把感知-决策-执行三件套完整实现一遍。读懂它,你就读懂了 Browser-Use 的骨架。
import asyncio, json, re
from playwright.async_api import async_playwright
from openai import AsyncOpenAI # 换成任意兼容客户端
SYSTEM_PROMPT = """你是一个浏览器操作助手。
当前页面有一批带编号的可交互元素。
请只输出一个 JSON 动作,不要输出任何多余文字。
可选动作:
{"action":"click","index":N}
{"action":"type","index":N,"text":"..."}
{"action":"scroll","direction":"down|up"}
{"action":"extract","what":"要提取的内容描述"}
{"action":"done","answer":"任务最终结果"}
"""
client = AsyncOpenAI() # 读取环境变量 OPENAI_API_KEY
async def get_state(page):
"""感知:把可访问性树简化成带编号的元素清单"""
# 用 Playwright 的 accessibility snapshot(浏览器原生可访问性树)
snapshot = await page.accessibility.snapshot()
elements, idx = [], [0]
def walk(node, depth=0):
role = node.get("role", "")
name = (node.get("name") or "").strip()
if role in ("button", "link", "textbox", "combobox", "menuitem") and name:
idx[0] += 1
elements.append(f"[{idx[0]}] <{role}> {name[:60]}")
for child in node.get("children", []):
walk(child, depth + 1)
walk(snapshot)
visible_text = (await page.inner_text("body"))[:1500]
return "可交互元素:\n" + "\n".join(elements) + "\n\n页面可见文本(截断):\n" + visible_text
async def execute(page, action):
"""执行:把结构化动作落到浏览器"""
a = action.get("action")
if a == "click":
# 用编号回查元素再点——真实实现要维护 编号→locator 映射
locators = await page.locator("button,a,input,select,[role]").all()
await locators[action["index"] - 1].click()
elif a == "type":
locators = await page.locator("input,textarea,[contenteditable]").all()
await locators[action["index"] - 1].fill(action["text"])
elif a == "scroll":
await page.mouse.wheel(0, 800 if action["direction"] == "down" else -800)
elif a == "extract":
return await page.inner_text("body")
return None
async def run_agent(task: str, url: str, max_steps: int = 15):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto(url, wait_until="networkidle")
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
for step in range(max_steps):
state = await get_state(page)
messages.append({"role": "user", "content": f"任务:{task}\n\n{state}"})
resp = await client.chat.completions.create(
model="gpt-4o-mini", messages=messages, temperature=0,
)
raw = resp.choices[0].message.content
# 从模型输出里抠出第一个 JSON 对象
m = re.search(r"\{.*?\}", raw, re.DOTALL)
action = json.loads(m.group(0))
print(f"step {step}: {action}")
if action.get("action") == "done":
await browser.close()
return action.get("answer")
result = await execute(page, action)
messages.append({"role": "assistant", "content": raw})
messages.append({"role": "user", "content": f"执行结果:{result or 'OK'}。继续。"})
await browser.close()
return "步数耗尽,未完成任务"
asyncio.run(run_agent("找出页面顶部导航的第一个链接文字", "https://www.chenxutan.com"))
这段代码刻意保留了"真实工程的毛刺":
- 感知用
page.accessibility.snapshot()直接拿浏览器原生可访问性树,省去自己解析 HTML; - 决策把状态塞进 prompt,让模型吐 JSON,再用正则抠出动作对象(真实项目会用更稳的结构化输出 / function calling);
- 执行用编号回查 locator——这里用了简化映射,Browser-Use 实际维护的是更精确的"索引→元素特征"表,容错更强;
- 闭环用
for step循环 +done终止条件,外加max_steps兜底防死循环。
跑通这个 60 行版本,你会发现 Browser-Use 并没有什么魔法——它只是把"感知表示"和"动作闭环"这两件事做对了,然后把剩下的智能全部外包给 LLM。
五、性能与可靠性优化(生产落地的真相)
Demo 跑通只是开始。真要上生产,三座大山是:成本、可靠性、长程规划。
5.1 Token 成本:可访问性树也要"减肥"
最大的隐性成本是每步都重发页面状态。一个复杂页面的可访问性树轻则 2k token,重则 10k+。跑 30 步就是 30 万 token 往上走。优化手段:
- 只保留可交互节点:这是 Browser-Use 默认就做的,砍掉 80% 的噪音。
- 分块(chunking):超长页面分段发送,模型先看目录再按需展开。
- 状态 diff:不每次发全量,而是发"相对上一步的变化"。不过实现复杂,容易引入不一致,需谨慎。
- 小模型做"粗活":用
gpt-4o-mini/ 本地模型跑大部分点击决策,只在关键提取/总结时上大模型。这是性价比最高的优化。
5.2 视觉 vs 非视觉:什么时候才截图
截图很贵(一张图抵几百~上千 token),而且视觉模型推理更慢。经验法则:
- 纯表单/列表/文本页:绝对不截图,纯文本可访问性树足够,又快又便宜。
- 需要"看"视觉布局时(比如"点红色那个按钮"、"截图确认验证码位置"):再开
use_vision。 - 一个高级技巧是默认关视觉,当文本状态里出现歧义(多个同名按钮)时,再单独为那一步补一张截图。
5.3 自愈与纠错:动作失败怎么办
生产环境里,click(42) 可能因为元素被遮挡、未加载完、或在 iframe 里而失败。Browser-Use 的工程策略:
- 异常即反馈:执行报错信息(如 "Element is not visible")被原样塞回消息流,模型下一步就会换个方式(先滚动、或改点别的)。
- 重试上限:
max_failures控制连续失败多少次后放弃,避免无限 retry 烧钱。 - 等待策略:每次动作后等
networkidle或固定超时,避免"页没加载完就点下一个"。 - 回到稳定锚点:遇到意外弹窗(cookie 同意框),可以注册一个"常驻自定义动作"在每步开头先尝试关掉它。
5.4 长程任务:规划、记忆与子 Agent
"搜索→比价 10 个商品→汇总成表"这种长任务,单靠一步步"见步走步"很容易走偏。进阶打法:
- 先规划再执行(Plan Mode):任务开头先让模型产出一份步骤清单,之后每步对照清单,减少漂移。
- 子 Agent 分工:把"打开 10 个标签页各自提取价格"拆给 10 个子 agent 并行,主 agent 只做汇总。
- 外部记忆:把中间结果(已查到的价格)写入数据库/文件,而不是全堆在上下文里,规避窗口上限。
5.5 工程化部署清单
真上线要考虑的"脏活":
- 并发与限速:一个浏览器实例很重,多用
asyncio+ 浏览器池;调用 LLM 要做速率限制与退避重试。 - 超时与熔断:网页卡死、模型抽风都要有超时兜底,否则一次任务能挂几小时。
- 反爬与伦理:目标站若有 robots 协议 / 登录鉴权 / 付费墙,自动化访问可能涉及合规风险,生产前务必确认授权。
- 可观测性:每一步的"状态 + 动作 + token 消耗 + 耗时"都要落日志,否则出问题完全无法排查(这点和后端服务如出一辙)。
六、横向对比:Browser-Use vs Claude Computer Use vs WebVoyager
理解一个技术,最好的办法是看它的邻居。
| 维度 | Browser-Use(DOM/语义级) | Claude Computer Use(像素级) | WebVoyager / WebArena(研究基准) |
|---|---|---|---|
| 操作依据 | 可访问性树 + 元素编号 | 屏幕截图 + 坐标 | 多为可访问性树变种 |
| 精度 | 高(点到确切元素) | 中(坐标/视觉易偏) | 高 |
| Token 成本 | 低~中 | 高(截图贵) | 中 |
| 通用性 | 仅限网页 | 整个 OS(任意软件) | 仅限网页 |
| 视觉内容 | 弱(看不懂纯图表/canvas) | 强(看得见一切) | 中 |
| 典型用途 | 网页自动化、RPA 替代 | 通用桌面 agent | 学术评测 |
一句话总结三角关系:Browser-Use 用"牺牲通用性、换取网页场景下的精度与成本",换来了今天最实用的网页 agent 方案。Claude Computer Use 更通用但更贵更不稳;WebVoyager 是学术圈的"考试卷",用来衡量能力上限。
一个有趣的融合趋势是混合感知:默认走 DOM 树(便宜准),遇到图表/验证码等纯视觉障碍时自动切截图(贵但能看)。这正是最新版本的能力方向。
七、总结与展望:Agent 正在成为新的"浏览器 API"
把全文收一下。
Browser-Use 这类项目的本质,不是"又一个自动化工具",而是人机交互接口的一次位移:过去你编程控制浏览器(写代码 → 选择器),现在你用自然语言描述意图,浏览器自己理解、自己执行。可访问性树是 AI 的"眼睛",Playwright/CDP 是"手脚",LLM 是"大脑",闭环 + 历史管理是"工作记忆"。
它的工程启示很朴素却深刻:在 LLM 时代,很多系统的核心价值不再是"智能"本身,而是"把领域状态正确翻译成模型能消费的结构化表示"。Browser-Use 做对了的,就是网页这个领域的"翻译层"。
当然,边界也得说清:
- 安全风险:一个能自主点网页的 agent,也可能点到你不想点的东西(误删、误下单、误授权)。动作空间需要白名单、关键操作需人工确认。
- 反爬与合规:自动化访问他人站点,要尊重 robots、授权与法律边界,别把利器用歪。
- 幻觉与失控:模型可能"自信地"点错、在循环里打转、或编造不存在的 URL。生产环境必须有步数上限、失败熔断、人工兜底。
- 成本曲线:长任务的成本随步数线性甚至超线性增长,必须靠小模型分流、状态压缩、并行化来压。
展望未来,几个我比较看好的方向:
- 与 MCP(模型上下文协议)结合:让 agent 不止能操作浏览器,还能调用数据库、API、文件系统,成为真正的"数字员工"。
- 自我进化:agent 把"某类任务的操作经验"沉淀成可复用 skill,下次直接调用而非重新探索——这和第 N 代 AI 编码代理的"持久记忆"思路同源。
- 多模态融合:DOM 树 + 截图 + 可能的视频流,构成更完整的"感知",逐步抹平语义级与像素级的鸿沟。
- 从"网页"到"任意软件":浏览器 agent 验证过的那套"感知-决策-执行"范式,正在向桌面、移动端、甚至操作系统级 agent 扩散。
最后给工程师一句实在话:别神话它,也别轻视它。Browser-Use 不是银弹,它解决不了所有自动化;但它确确实实把"让 AI 替我点网页"从 PPT 概念变成了 pip install 就能跑的现实。对于被脆弱选择器折磨过的我们来说,这已经足够让人兴奋了。
本文所有代码示例均为教学用的简化实现,生产环境请直接使用 Browser-Use 等成熟项目,并务必遵守目标站点的使用条款与相关法律法规。
参考资料与延伸阅读(均来自公开技术社区):Browser-Use 官方仓库与文档、Playwright 官方文档(CDP 与 accessibility snapshot)、Chrome 开发者文档(Accessibility Tree)、LangChain 模型抽象文档、Claude Computer Use 技术报告、WebArena / WebVoyager 学术论文。