又一个 AI 浏览器自动化神器!
全文速览
browser-use不是又一个Selenium封装——你描述目标,LLM自己决定每一步怎么操控浏览器。v0.13最大变化:扔掉万行DOM处理代码,换成600行CDP直连的Browser Harness,因为LLM本来就会CDP协议。 最意外的涌现行为:Agent发现缺upload_file函数→读helpers.py源码→写DOM.setFileInputFiles实现→保存→继续任务。不是预设容错,是LLM+薄抽象层的自然结果。 该用:复杂多步操作、页面结构不确定、需适应改版的场景。不该用:简单爬虫、低延迟要求、成本敏感的批量任务。薄抽象不是银弹,是方向盘——只在你需要判断时才有优势。
0155k Star的浏览器Agent
如果你让一个AI帮你"从GitHub下载browser-use的README,截个图保存到桌面",传统做法是什么?你得先写个Python脚本:driver.find_element(...)找按钮、time.sleep(2)等加载、处理各种弹窗、调试选择器……折腾半小时,只为完成一个5秒就能手工干完的活。
browser-use做的事很简单:你给它一句话,它自己操控浏览器去完成。
从2024年开源到现在,103k+ Star、318位贡献者、2600+项目依赖它。6月刚发的v0.13.2,用Rust重写了核心运行时,同时推出了一个叫Browser Harness的东西——只有约600行代码。
这个项目在PyPI上已经迭代了129个版本,最新版安装只需要一行:
uv add "browser-use[core]"
然后5行代码就能跑起来:
from browser_use.beta import Agent, BrowserProfile, ChatBrowserUse
import asyncio
async def main():
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(),
browser_profile=BrowserProfile(
headless=False,
allowed_domains=["*.github.com"],
),
)
history = await agent.run()
print(history.final_result())
asyncio.run(main())02它怎么就不是又一个Selenium封装?
第一眼看browser-use,"这不就是Playwright上面套了个LLM调用吗?"
但55k人Star它不是因为它"封装得好"。是因为它解决了一个真实到令人发指的痛点:写浏览器自动化脚本太烦了。
用过Selenium或Playwright的人都知道这个循环:打开DevTools → 找元素选择器 → 写find_element → 跑一下 → 报错(元素找不到/页面还没加载/iframe挡住了/Shadow DOM进不去)→ 加time.sleep和WebDriverWait → 再跑 → 又报错 → 调试到怀疑人生。
你花90%的时间不是在实现业务逻辑,而是在跟网页的DOM结构斗智斗勇。更致命的是——页面一改版,你的脚本就挂了。XPath变了,class名改了,元素层级调整了——每个都是定时炸弹。
browser-use 的底层洞察是:LLM不需要你帮它找选择器。 它在训练数据里已经见过几百万字的网页结构、HTML语义、常见的按钮文案和表单逻辑。你说"点击搜索按钮",它自己知道去找什么。传统工具的抽象层不是帮助,是障碍。
这不是Selenium的升级版。是范式切换——从"人类写代码操控浏览器"变成"人类描述目标,AI自己决定每一步怎么操控浏览器"。你不再是程序员,你是任务描述者。
03扔掉一万行代码,换六百行CDP直连
如果你去看browser-use v0.12和之前的版本,你会看到一个"正常"的开源项目——上万行Python代码,包含DOM元素提取器、元素索引器、点击包装器、目标管理器、看门狗、跨域iframe处理器……每一行都是为了解决"网页太复杂,LLM搞不定"这个问题。
然后v0.13来了。他们把这一万行全扔了。换成了一个只有约600行的东西——Browser Harness。
这四文件分别是:
run.py | ||
helpers.py | goto_url()、click_at_xy()、type_text()、capture_screenshot()、js()、new_tab()……可以在运行时被Agent编辑。 | |
daemon.py | /tmp/bu-<NAME>.sock)。 | |
SKILL.md |
为什么这个改动是"架构转向"而不是"代码精简"?核心洞察就一句话——LLM already knows CDP。
Chrome DevTools Protocol是Chrome自己的调试协议。Page.navigate、DOM.querySelector、Runtime.evaluate、Input.dispatchMouseEvent——这些命令名、参数格式、调用方式,LLM在训练数据里已经见过几百万字了。你不需要再给它包装一层Playwright的page.click(selector)。它"本来就会"操作CDP。你给它的抽象层越厚,它的"原生能力"被遮蔽得越多。
这种"薄抽象"还有一个额外的好处:坐标点击天然穿透iframe和Shadow DOM。 传统工具里,点击iframe里的按钮要手动switch_to.frame(),遇到Shadow DOM要shadow_root.querySelector(),跨域iframe直接是死胡同。但CDP的Input.dispatchMouseEvent在浏览器的合成器层(compositor layer)执行坐标点击——根本不关心目标元素在哪个frame、哪个shadow root、哪个跨域边界里。点就完了。
04Agent怎么把自己修好的?
如果上面那段"架构转向"听起来还像PPT——下面这个故事会让你感觉到这东西到底有多不一样。
browser-use团队在开发Browser Harness的时候,有一天发现了一个git diff:
# 这个函数不在原始代码里。
# Agent在执行任务时自己写的。
def upload_file(file_path):
"""Upload a file using CDP DOM.setFileInputFiles"""
# Agent读了helpers.py,发现没有upload_file
# 然后它写了这个实现
...
完整过程是这样的:
Agent在执行一个"上传文件到XX网站"的任务 它扫描了helpers.py里的可用函数—— click_at_xy、type_text、capture_screenshot、scroll……发现没有 upload_file它没有报错,没有卡住,没有输出"抱歉我做不到" 它读了helpers.py的代码风格 写了一个使用 DOM.setFileInputFiles的upload_file()函数保存到helpers.py 调用它 继续执行任务
团队是在review git diff的时候才发现这段代码的。
还有一次,一个12MB的文件超过了CDP WebSocket的消息大小限制(约10MB)。Agent的输出不是"文件太大传不了"——它重写了上传逻辑,改成了分块传输。
这些不是预编程的容错机制。没有人在代码里写"如果缺upload_file就生成一个"。这是LLM + 薄抽象层的涌现行为——当Agent可以直接看到和修改它自己的工具代码时,它的"调试能力"被释放了出来。
注意:以上两个案例均来自browser-use团队官方博客自述,是单一案例而非系统验证。不是说"每个Agent都会自愈"——而是说这种架构设计让自愈变成了可能。
05Agent循环操作背后的全部步骤
说完了架构和案例,来看看Agent到底是怎么工作的。
browser-use的核心是一个四步循环:
每一步拆开来看:
1. Observe(观察):Agent获取当前浏览器状态。默认模式是截图+页面信息。use_vision参数控制行为:auto(只在需要时截图)、true(每步都截图,最准但token消耗大)、false(只看DOM文本,最快但可能漏信息)。另外可以用page_extraction_llm指定一个小模型专门做页面文本提取,省token。
2. Decide(决策):LLM根据当前状态、任务目标、历史动作记录,决定下一步做什么。可以一次输出最多3个动作(max_actions_per_step默认3),比如填表单时一次填3个字段。动作类型覆盖导航(搜索/打开/后退)、页面交互(点击/输入/滚动/按键)、JS执行、标签页管理、内容提取、文件操作、下拉菜单、截图、宣布任务完成。
3. Act(执行):Browser Harness通过CDP执行动作。坐标点击用Input.dispatchMouseEvent在合成器层完成——这句话翻译成人话就是:不管目标元素藏得多深,点得上去。 动作一个接一个执行,"直到页面发生变化"才进入下一步观察。
4. Verify(验证):截图确认结果。paint_order_filtering做DOM树优化——把被其他元素遮挡的节点移除,避免Agent"看到"不存在的东西。
有人会问:每步都要调一次LLM,是不是很慢很贵?是的——一个30步的任务可能耗时2-5分钟,token消耗也不是小数目。所以模型选择很关键。官方给出的benchmark数据:
| 78.0% | ||
差距很大。或者说,14个百分点在复杂任务里就是"一次成功 vs 多次重试"的区别。
06什么时候该用,什么时候不该折腾?
坦率地说,browser-use不是银弹。
该用的场景:
复杂多步Web操作:跨页面填表、提交审批、数据提取+整理 页面结构不确定的任务:你不知道目标网站长什么样,LLM去探索 需要适应页面变化:网站经常改版,维护选择器脚本成本太高 探索性调研:竞品网站信息搜集、价格监控、内容聚合
不该用的场景:
简单爬虫或固定表单提交——传统Playwright脚本更快、更便宜、结果100%确定 低延迟要求的场景——每步LLM调用1-5秒,实时交互不现实 成本敏感的批量任务——如果你要跑10万次同样的操作,写一个稳定脚本跑一次的成本远低于Agent跑10万次
开源还是云端:
如果你是个人开发者想试试,开源版足够你跑通大多数自动化任务。如果你要给客户交付一个生产级服务——云端版省掉的反检测、验证码、扩展性维护成本可能值回票价。
最后,这里是官方Quickstart改编的一个完整示例——帮你理解一个实际任务从头到尾长什么样:
from browser_use.beta import Agent, BrowserProfile, ChatBrowserUse
import asyncio
async def main():
# 创建一个Agent:告诉它任务、给它模型、配置浏览器
agent = Agent(
task="打开GitHub trending页面,找到今天Star最多的Python项目,把项目名和Star数提取出来",
llm=ChatBrowserUse(),
browser_profile=BrowserProfile(
headless=False, # 能看到浏览器操作过程
allowed_domains=["*.github.com"], # 只允许访问GitHub
),
)
# 跑!
history = await agent.run()
# 打印最终结果
print(history.final_result())
# 还可以看每一步的详细信息
for step in history.steps():
print(f"Step {step.step_num}: {step.action_name}")
# step.screenshot 有每一步的截图
# step.model_output 有LLM的完整输出
asyncio.run(main())
这个Agent实际执行时会经过:导航到GitHub Trending → 滚动找Python标签 → 点进去 → 提取第一个项目信息 → 返回结果。大约5-8步,取决于页面加载速度和模型决策。
07browser-use里的代码工程美学
browser-use真正的价值比一个"好用的浏览器Agent工具"更大。
它的设计选择相当于做了一个实验——实验结果挑战了AI工具设计领域一个常见的假设。
那个假设是:更好的AI工具 = 更完善的API封装。 LLM能力有限?给它封装更多helper函数。网页太复杂?给它做DOM预处理。可能出错?给它加错误处理和重试。每多一层封装,LLM就少犯一点错。
browser-use的实验结果指向相反方向:在browser-use这个案例范围内,抽象层越薄,LLM表现越好。
为什么?因为LLM的训练数据里有海量的CDP协议文档。Page.navigate怎么用、Input.dispatchMouseEvent的参数格式是什么——它"本来就会"。你给它Playwright的page.click(selector),它反而要经过一道"翻译":这行Playwright代码对应的是哪个CDP命令来着?参数怎么映射?多了一道翻译,就多了一出错的可能。
Browser Harness的600行代码之所以能替代一万行,不是因为它"更聪明"。是因为它信任LLM的能力。它把LLM当作一个已经懂浏览器协议的工程师——你不需要替它找选择器、不需要替它封装命令、不需要替它处理异常。你给它最原始的操控权,它自己知道怎么做。
那个"Agent自己写upload_file"的案例,就是这种信任的最好证明。你不是在给LLM建一个游乐场——你是在给它一个方向盘。
当然,方向盘哲学不是万能的。简单、重复、确定性的任务,传统脚本仍然更可靠、更便宜、更可预测。但如果你面对的任务多少需要一点"判断"——这个按钮是不是我要点的那个、这个表单应该先填哪一栏、数据提取出来应该怎么归类——那么薄抽象层会越来越有优势。
因为当LLM越来越强时,你不需要预测它需要什么。它自己会告诉你。