ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Browser-Use源码解析:大模型如何操控浏览器实现自动化

Browser-Use源码解析:大模型如何操控浏览器实现自动化 这两年AI写代码已经见怪不怪了但真正让我觉得“有点东西”的方向是让大模型自己操控浏览器去完成任务。你给它一个目标它自己打开网页、看内容、点按钮、填表单、翻页、提取信息一套操作下来跟个真人似的。这里最绕不开的开源项目就是Browser-Use。我花了不少时间把它源码啃了一遍从最早只会跑demo到后来能改它的controller注册自己的动作再把整套结构迁移到自己的自动化工具里。这篇文章不打算给你念API文档而是直接聊源码AI到底是怎么“看懂”网页的又是怎么把手伸进浏览器里完成点击和输入的每个关键环节我会结合自己的调试经历讲明白也会附上能直接跑的实操代码适合正在做AI Agent、浏览器自动化、或者想深入理解RAG之外“智能体怎么和真实世界交互”的朋友。1. 项目概述browser-use到底解决了什么问题1.1 一句话理解browser-useBrowser-Use是一个开源的Python库核心目标很简单让大语言模型能像一个真实用户一样操作浏览器。整体运行逻辑不复杂就是一个“观察→决策→行动→再观察”的循环把当前浏览器页面的DOM结构提取出来转换成LLM能读懂的文本形式比如带索引的交互元素列表。LLM根据用户给出的任务和当前页面状态输出一个结构化的“动作”比如“点击索引为5的按钮”“在输入框3中输入‘Python’”。框架解析这个动作通过Playwright在真实浏览器里执行。执行完成后重新提取页面状态把新的DOM交给LLM继续下一轮直到任务完成或达到最大步数。这个项目之所以流行是因为它把“LLM和网页交互”这件事封装得很干净。你不需要自己处理复杂的DOM解析、XPath定位、浏览器调试协议、历史记录管理只需要写一个任务描述剩下的交给框架。1.2 为什么不能直接调API完成网页操作有人可能会问我直接用大模型API让它给我返回一段JS代码然后我用Playwright执行不就行了理论上可以但实际操作会遇到一堆麻烦。网页不是纯文本而是一个高度结构化的环境。直接扔给模型原始HTML几万个token就把上下文吃光了而且模型很难理解“这个按钮到底在页面哪个位置”“点击之后会不会弹出新窗口”。更麻烦的是模型生成的JS代码经常飘要么选择器写错要么没考虑到iframe、Shadow DOM、元素懒加载一旦报错整个任务就断掉。Browser-Use的做法相当于给模型发了一个“操作面板”面板上只有几个按钮和输入框每个都标好了编号和类型。模型不需要写JS只需要说“按第7号按钮”框架负责把编号翻译成具体坐标和元素。这大大降低了模型的操作难度。传统RPA是录制固定脚本页面一改就废Browser-Use是让模型实时看页面再决定怎么操作天然抗页面改版。1.3 源码整体目录与模块划分我第一次打开Browser-Use源码仓库时第一感觉是“结构比想象中清晰”。核心代码集中在browser_use目录下几个重要模块各司其职agent/控制主循环定好“每一步怎么想、怎么做、怎么记”是整个框架的大脑。browser/封装浏览器的启动、页面管理、浏览器上下文底层主要依赖Playwright。dom/DOM提取与转换把密密麻麻的HTML变成模型能读的紧凑文本。controller/动作注册与执行中心。所有模型能做的动作都从这里注册包括内置的点击、输入、滚动等操作。llm/大模型调用封装支持OpenAI、Anthropic、Google、Ollama以及各种兼容接口。我当时先跳过了agent直接从dom模块开始读因为我想搞清楚那套“页面翻译”是怎么实现的。后来才发现agent主循环才是把一切串起来的关键两个都绕不开。2. 核心设计思路Agent循环是如何转起来的2.1 一次完整的“看-想-做”循环Browser-Use的agent主循环在agent/service.py里虽然代码版本迭代过几轮但核心结构一直很稳定。最关键的方法大概是_run_step或类似命名不同版本略有差异但思路是一致的每一步都会组装一个“提示包”包含系统提示、任务描述、历史操作记录、当前页面状态发给LLM。LLM的输出会被解析成两种结果一种是Thought模型的理由另一种是Action列表。Action会逐一交给controller执行执行结果包括成功或失败、是否改变了URL、是否弹出了新标签页会被记录下来作为下一轮的历史输入。简化后的伪代码如下for step in range(max_steps): messages build_messages( tasktask, historyhistory, statecurrent_state, ) response llm.invoke(messages) thought, actions parse_llm_output(response) for action in actions: result controller.execute(action) history.append(result) current_state browser.get_state() if task_is_done(history): break我当时第一次看到这个循环时脑子里冒出来的类比是这就像一个员工坐在工位上领导给了一个目标他每做完一步就抬头看一眼屏幕然后写下“我做了啥、结果咋样”再决定下一步。Browser-Use只是把这个过程自动化了。2.2 为什么用“动作指令集”而不是让模型直接写JS这个问题我想了很多遍后来在看源码时彻底明白了。Browser-Use在controller/里定义了一套结构化的动作指令集模型只能从中选择不能自由发挥写代码。这样做有几个明显的好处第一可控性。模型永远不会直接执行任意JavaScript只能点击指定元素、输入指定文本、滚动指定方向。就算模型“发疯”造成的破坏也有限。第二可解释性。每一步记录都是结构化的比如“点击了id3的按钮”就是一条明确记录。如果模型自己写JS你很难判断它到底做了什么。第三token效率。一个动作只需要说出动作名和参数比生成完整JS代码省几十倍token。第四容错。框架可以在执行动作前做校验比如目标元素是否还存在、输入内容是否合法失败时能给出明确的错误信息反馈给模型。你可以把controller看成是给模型发了一副“只能按这几个键的手柄”模型再聪明也只能在预设操作里组合。别觉得这是限制实际用下来反而让任务成功率更高。2.3 状态历史与上下文管理细读源码就会发现历史记录不是一个简单的list而是经过了一层“压缩管理”。每一步的观察结果、动作、执行结果都会进入history但历史不能无限增长。模型上下文窗口就那么大塞满之后后面的决策质量会断崖式下跌。Browser-Use的处理策略比较聪明它主要靠两种手段一是只保留最近若干轮的关键信息。在源码里你能看到类似history.maximum_history_length这样的配置默认不会包含全量历史早期步骤会被截断。对于很长的任务还可能触发“总结模式”把早期步骤压缩成一小段摘要避免信息丢失。二是状态提取有严格的token预算。dom/模块提取出的页面内容会限制节点数量和文本长度防止单轮状态就把上下文撑爆。这个我也在实操中深有体会一个网易首页级别的复杂页面直接转成文本能有好几万个字符不控制的话几轮对话直接爆掉。3. 关键源码细节AI是怎么“看懂”网页的3.1 DOM是怎么被“翻译”成模型能读的内容这个环节是整个项目里最有技术含量的地方。我在dom/目录下翻源码时发现它并没有简单地把HTML转成文本而是做了一个“交互元素提取”。核心思想是不要给模型看所有东西只给它看能操作的东西。具体来说框架启动后会遍历页面DOM树过滤掉不可见元素、非交互元素只保留链接、按钮、输入框、下拉框、复选框、文本区域等可交互节点。每个节点会分配一个编号属性包括标签名、type、name、id、文本内容、href等最终输出类似这样的结构[2] button 提交订单 [3] input typetext placeholder请输入关键词 [5] a href/product/123 无线耳机这个过程用到了自定义的DOM遍历器和文本转换器。源码里会有一些关键类比如DomService负责从Playwright页面拿到可交互元素树TreeBuilder负责把节点转成带缩进和索引的文本树。如果你去看它的实现会发现它不依赖BeautifulSoup之类的外置解析器而是通过JavaScript在浏览器内部直接基于document对象抽取信息这样可以拿到更准确的布局和可见性数据。这个“缩小范围”的设计非常关键。原始的Google首页可能只有几十个节点但一个电商网站可能有上千个节点。如果全部塞给模型不仅token爆炸模型还会被无关内容干扰。只保留重要交互节点之后页面状态一般能控制在20006000字符内成本和准确率都更可控。3.2 坐标与元素定位点击输入是怎么精准落地的模型拿到的是带编号的元素列表但浏览器真正执行点击时需要的却是坐标或选择器。Browser-Use怎么把编号翻译成真实操作我在源码里看到每个提取出的节点都附带了一个xpath或者用于定位的索引信息。当模型说“点击第5号元素”controller会先去当前页面状态里查询编号对应的节点信息然后使用Playwright的locator(xpath)定位到真实元素再执行点击。某些版本还会计算元素在视口中的中心坐标通过鼠标事件模拟点击。这就引出一个重要体验模型看到的页面状态必须和实际页面状态保持同步。如果模型刚看完页面页面又做了异步更新那么编号对应的元素可能已经变了。所以每执行一步后框架都会重新提取状态避免模型拿着过期数据硬操作。这也是为什么我在调试时经常看到模型会先“重新观察页面”再做决策它其实是在等新状态。有一点值得提醒Browser-Use的“提取状态”时机比较讲究比如在页面跳转后或点击后它会等待网络空闲或元素稳定缩短状态和真实页面之间的差距。这个等待策略在browser/里也有对应的wait_for配置我在实操中会把wait_for_network_idle打开尤其是处理SPA单页应用时非常管用。3.3 Controller动作注册机制controller/模块是一个“插件式”动作中心。内置动作包括打开URL、点击元素、输入文本、滚动、切换标签页、提取内容、返回上一页、刷新页面等大概十几个。每个动作都以装饰器方式注册源码里长得像这样class Controller: action(打开网页, param_modelGoToUrlParams) async def go_to_url(self, url: str): await self.browser.go_to_url(url) return ActionResult(include_stateTrue) action(点击元素, param_modelClickElementParams) async def click_element(self, index: int): element self.browser.get_element_by_index(index) await element.click() return ActionResult(include_stateTrue)这里有一个细节很关键每个动作都定义了自己的参数模型基于Pydantic。LLM输出的动作是结构化JSON框架会用它反序列化成参数对象。如果模型给的参数不合法比如点击元素时没给index框架会返回参数校验错误并把错误信息反馈给模型让它下次修正。如果你用过这个库也许见过我自己注册自定义动作的例子。比如我有个任务是每天自动填一个内部报表我直接在controller上注册了一个fill_report动作动作内部只需要两个参数剩下的页面逻辑全部在函数里写死。这样模型只需要做极少的决策稳定性和速度都上来了。3.4 浏览器控制层本地浏览器、CDP、PlaywrightBrowser-Use并没有自己从零写浏览器自动化而是基于Playwright。Playwright负责启动Chromium、管理标签页、模拟鼠标键盘、截图、生成页面状态。Browser-Use在browser/模块里做了几个有用的封装多标签页管理新开标签页后状态提取会自动聚焦到当前激活的标签页。持久化用户数据可以传入user_data_dir保留登录态我处理需要登录的站点时就用这个避免每次都要重新登录。CDP支持底层也可以连接一个已有浏览器实例这让它能在某些复杂登录场景下无缝接管用户已登录的浏览器。观看源码时你会发现Playwright这一层并不“智能”它是纯粹的“手脚”。智能全在上面的决策层Browser-Use的巧妙之处就在于把“手脚”包装成了一套对LLM友好的接口。4. 实操从源码读完之后跑通一个最小Agent4.1 环境准备与安装如果你看完源码想立刻跑起来环境准备其实非常快。我建议用一个干净的新虚拟环境避免依赖冲突。pip install browser-use playwright install chromium如果你在服务器上跑可能还需要安装一些系统依赖playwright install-deps可以一键搞定。然后就是模型配置。我最早用的是OpenAI的接口后来为了省钱换成了本地Ollama和兼容OpenAI协议的模型都跑通过。选模型时有一个建议不要用小参数模型至少也要7B以上而且指令遵循能力要强否则模型经常乱给参数。如果你没有OpenAI的key用本地模型也很方便。Ollama拉一个qwen2.5:7b或类似模型再把base_url改成http://localhost:11434/v1就行。4.2 最小Demo让AI自动打开网页并提取内容下面这段代码我非常精简但确实能跑通。它让AI打开一个页面找到搜索框输入关键词然后点搜索最后提取结果标题。我这里以公有技术社区为例换成任意公开网站同理。import asyncio from browser_use import Agent from langchain_openai import ChatOpenAI # 如果你用本地模型把base_url换成Ollama地址即可 llm ChatOpenAI( modelgpt-4o-mini, base_urlhttps://api.openai.com/v1, api_keyYOUR_API_KEY, ) async def main(): agent Agent( task打开 https://news.ycombinator.com 在搜索框里输入 AI agent 点击搜索按钮然后把结果的第一条标题和链接告诉我。, llmllm, max_steps10, ) history await agent.run() print(history.final_result()) asyncio.run(main())跑这段代码时你会发现模型的思考过程会打印出来比如“我需要先定位搜索框”“我看到了输入框编号3”。如果页面复杂导致模型犯错它会收到错误信息并自动修正。第一次跑通的时候很兴奋感觉真像有个远程员工在替我操作浏览器。4.3 参数调优与常见配置实际使用时直接裸跑默认参数往往不够。我根据源码里的配置项和自己的测试整理了几个最值得调的参数max_steps最大步数。任务越复杂越要放宽但步数越多token消耗越大一般1020步能覆盖大部分简单任务。use_vision是否启用视觉理解。开启后模型会看截图对判断页面布局很有帮助但会显著增加token消耗而且小模型吃截图效果并不好。max_actions_per_step每步最多执行几个动作。默认是1可以调大减少轮次但模型出错的概率会变高。temperature生成温度。浏览器操作需要确定性我一般调到0或0.1防止模型发挥太飘。wait_for_network_idle页面跳转后等待网络空闲再提取状态。处理SPA时我强烈建议开启。这些参数都在Agent构造函数里直接传用起来非常方便。现在实践中我通常先用较高的max_actions_per_step跑快速任务遇到复杂页面则调回1让模型每一步都谨慎观察。4.4 踩坑记录我在跑源码时遇到的几个问题说几个很容易踩的坑。第一个是无头模式登录失效。默认跑在无头浏览器里很多网站会直接拦住验证码弹个不停。我的解决方案是先用普通模式手动登录一次然后保存浏览器上下文。Browser-Use支持传入user_data_dir参数指定一个持久化目录之后每次启动都能保持登录态。第二个是滚动加载的页面。有些网页是无限滚动模型看不到底部内容。给模型的任务里最好明确说“滚动到页面底部”或者“滚动一段距离”否则模型不会主动滚信息永远提不全。第三个是动态下拉框。比如你要选“省-市-区”点击某个下拉后选项是异步加载的模型点击太快会扑空。后来我用了一个经验做法在controller里自己注册一个wait_and_click动作点击前先强制等待500毫秒或者轮询目标元素稳定。这种“笨办法”反而比模型自己连续点击靠谱得多。第四个是token爆掉。页面大、历史多的时候上下文很容易超限。源码里会有设置项来限制DOM提取的最大节点数同时把history.maximum_history_length设小一点比如10。如果还是爆最简单的办法就是换更长上下文的模型。5. 常见问题与排查技巧实录5.1 模型总是乱点或者不动现象模型给出的动作明显不合理比如在搜索结果页去点击底部的版权链接或者迟迟不执行任何动作。原因一般有两个一是模型太弱没有足够能力理解页面结构二是页面状态太混乱给了太多无关元素。排查顺序先在任务描述里写得更具体比如“只在主内容区域点击链接不要点击导航和底部版权”把use_vision关掉因为带截图的提示对小模型干扰很大如果还不行就换成更强的大模型。我个人经验是7B模型做简单搜索和点击够用但严格多步任务还是得靠更大模型撑住。5.2 DOM太大导致上下文爆掉现象报错提示超过上下文长度或者模型在长任务后期明显“失忆”。这是最常遇到的问题之一。解决思路限制DOM提取的节点数量。在Browser-Use的DomService或者agent初始化那里会有max_depth、max_nodes之类的参数把它们调小。页面只保留最外层可交互元素模型没有压力任务成功率反而更高。另外缩短历史记录history.maximum_history_length调成510效果立竿见影。5.3 有些输入框输入不进去现象模型执行了输入动作但没有报错页面里的值却没变。这个大概率是输入框不是原生的input而是富文本组件或模拟输入框。框架的输入动作是直接往元素里填值很多前端组件监听的是input事件简单的赋值触发不到。我的兜底方法自己注册一个动作使用Playwright的fill()后再手动派发事件或者在输入前先点击目标元素激活焦点再输入。源码层面你可以扩展controller给输入动作加上一个强制触发input事件的后处理步骤。5.4 登录与验证码无法自动处理现象打开目标网站就跳登录验证码识别不了任务直接卡死。这个我没找到完美的自动化方案比较靠谱的做法是“人机结合”第一步手动登录把浏览器用户目录保存下来第二步让Agent基于这个已登录上下文继续操作。Browser-Use支持传入user_data_dir复用Chromium的登录状态。另外验证码我建议不要硬扛完全没有必要为这种边缘场景投入太多时间除非你想做一个专门的验证码识别服务。5.5 代码级调试技巧如果任务一直失败光看打印日志是不够的。我常用的办法是打开源码的调试日志设logging.basicConfig(levellogging.DEBUG)这样能看到每一步的提示词、模型输出、动作执行结果。还有一个办法是在浏览器层面无头改有头亲眼观察浏览器在干嘛。我自己在调试时最喜欢在controller.execute执行前打印一次动作JSON执行后再打印一次ActionResult。这样能看出是模型决策问题还是执行层问题。如果是执行层问题我会拿着元素索引去浏览器里定位看那个元素是不是隐藏的、被遮挡的或者已经消失了。6. 读完源码后顺着这个思路还能做什么6.1 注册自己的动作扩展能力边界内置动作只覆盖“通用点击输入”这些场景。实际做业务时我强烈建议把重复操作封装成自定义action。我在一个数据采集项目里注册了select_date_range、export_table、download_attachment这些动作。模型的任务描述变得非常短动作执行成功率也高得离谱。这其实是把“聪明的模型”和“确定性的代码”结合起来模型的职责从“怎么操作”退化为“什么时候操作什么动作”。6.2 与本地知识库和RAG结合你可以让Agent先从多个页面提取内容再把内容存入本地向量库后续由另一个LLM进行归纳总结。Browser-Use不擅长长文本推理但很擅长“把网页上的内容搬出来”。我有个小工具就是让Agent负责跨网站收集竞品信息统一整理成Markdown再交给下游的总结模型输出报告。这样分工明确速度和成本都更可控。6.3 用本地模型降低成本如果你对商业API的费用敏感完全可以引入本地模型。Browser-Use本身对模型来源不敏感只要支持OpenAI协议就能接入。我在本机用Ollama跑qwen2.5时简单任务效果还能接受复杂布局就差点意思。但如果你只是让Agent执行“打开某页面点击某个固定按钮”本地模型完全能胜任。这个思路特别适合内部自动化工具隐私和成本都能兼顾。6.4 把它改造成定时巡检助手最后一个我很推荐的扩展方向是“定时巡检”。业务流程中经常需要每天查看后台数据、检查订单状态、关注某个页面有没有更新。用Browser-Use加一个cron定时任务Agent每天自动登录、切换页面、提取关键数据、推送通知。我在团队内已经跑了好几个月最大的体会是“不用再教新人去哪找数据了”Agent自己会用自然语言理解任务。如果你也想往这个方向做我建议一开始就把任务拆得足够小。先让它每天只做一件事跑稳了再扩展。不要一上来就让它“维护整个后台”那会把你自己也绕晕。Browser-Use的源码给了你一个很好的起点把循环跑通之后剩下的就是不断往controller里加你需要的动作让它越来越像一个真正的“浏览器操作员”。这大概是这个开源项目最让人上瘾的地方。
返回列表