链载Ai

标题: 如何让AI“看懂”网页?拆解 Browser-Use 的三大核心技术模块 [打印本页]

作者: 链载Ai    时间: 3 小时前
标题: 如何让AI“看懂”网页?拆解 Browser-Use 的三大核心技术模块

传统的 Browser-Use 多依赖于固定选择器和流程编排,难以应对界面变化与复杂逻辑。随着大模型驱动的智能体技术兴起,Browser-Use 正迈向智能化新阶段:LLM 作为“大脑”负责任务规划与语义理解,结合视觉识别、DOM 分析、动作预测等模块,实现对浏览器环境的感知、决策与执行闭环,从而完成注册、比价、填报、监控等多步骤复杂任务的自主自动化。

一、引言

Browser Use 是一种基于 AI 模型的浏览器自动化技术,其核心目标是通过大模型进行推理和决策,解析用户指令,然后模拟人类操作行为,通过浏览器执行具体的操作(如点击、输入、页面跳转),从而实现对浏览器的自动控制。常用场景例如自动化浏览网页、提取信息、模拟用户操作、自动化测试等。

Browser Use 是基于 LangChain 生态构建的,需要遵循 LangChain 的接口规范,其核心价值在于将 LLM 的语义理解能力与浏览器自动化深度结合。

1、Vision+HTML Extraction

融合视觉理解和HTML结构(DOM树)解析,实现对网页内容的精准定位与交互。

2、Multi-tab Management

自动管理多个浏览器标签页,支持复杂流程(如跨页面数据抓取)和并行任务处理。

3、Element Tracking

记录用户操作的元素 XPath 路径,并复现 LLM 的精确动作,确保自动化的一致性。

4、Custom Actions

可扩展自定义操作(如保存文件、数据库操作、通知)。

5、Self-correcting

自纠错机制,自动检测操作失败(如元素未找到、超时),并尝试恢复流程。

6、Any LLM Support

支持所有 LangChain 兼容的 LLM,实现模型无关的指令解析。

二、历史发展

在 BrowserUse 等 AI 驱动的浏览器自动化工具出现之前,传统 RPA(Robotic Process Automation)、爬虫框架和自动化测试工具已长期服务于数据抓取、页面操作模拟等场景,下面从技术发展历史角度,分阶段解析这些需求的实现方式及演变逻辑。

2.1 早期阶段:脚本化和人工编码

2.2 RPA阶段:规则驱动的自动化

2.3 动态网页和反爬对抗阶段:工具链逐渐复杂化

当前工具链本质是模拟人类操作浏览器,无法突破「浏览器沙箱」限制。即便结合代理IP和Puppeteer,面对浏览器指纹检测等新型反爬技术时,仍需引入Puppeteer-extra等插件进行特征伪装,导致工具链复杂度指数级上升

2.4 AI 驱动的范式跃迁

SPA(单页应用)和 WebAssembly 普及,传统爬虫难以解析完整 DOM;业务场景碎片化,任务需求复杂化,人力成本压力等等。

大语言模型如 GPT-4 等具备自然语言指令解析与任务规划能力,可将抽象需求转化为操作序列;浏览器自动化框架如 Playwright 提供浏览器控制接口;视觉理解模型可解析屏幕内容,补充 Dom 解析获取的页面信息不足。

2.5 内容小结

BrowserUse 的出现是技术矛盾(动态网页复杂性 vs 传统工具僵化性)与技术进步(LLM+浏览器控制)共同作用的结果,也标志了浏览器自动化从 “规则驱动” 向 “认知驱动” 的范式跃迁。总的来说,其实际价值在于,通过 LLM 的泛化能力减少因网页改版导致的脚本失效问题,支持自动化复杂处理(处理弹窗),以及加速开发效率。

三、核心技术解析

3.1 源码解析

Browser-Use 项目中:

service.py和ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.034em;font-style: normal;font-weight: normal;">views.py遵循了经典的分层架构设计模式。

View 层 - 数据定义层:Pydantic 数据模型定义、数据验证、数据格式转换、模块间数据传递的标准格式。

Service 层 - 业务逻辑层:实现核心的功能和算法、管理复杂的操作流程、第三方服务集成、维护对象生命周期。

    ├──Agent#AI代理│├──gif.py#历史记录可视化│├──memory#记忆模块││├──__init__.py││├──service.py││└──views.py│├──message_manager#消息管理││├──service.py││├──tests.py││├──utils.py││└──views.py│├──playwright_script_generator.py│├──playwright_script_helpers.py│├──prompts.py#提示词相关│├──service.py│├──system_prompt.md│├──tests.py│└──views.py├──browser#浏览器相关│├──__init__.py│├──browser.py│├──context.py│├──extensions.py│├──profile.py#浏览器配置│├──session.py#核心会话管理│└──views.py├──cli.py├──controller#工具Action相关│├──registry││├──service.py││└──views.py│├──service.py│└──views.py├──dom#Dom树解析&可交互元素处理│├──__init__.py│├──buildDomTree.js│├──clickable_element_processor││└──service.py│├──history_tree_processor││├──service.py││└──view.py│├──service.py│├──tests││└──test_accessibility_playground.py│└──views.py├──exceptions.py├──logging_config.py├──telemetry#产品使用情况追踪,数据收集&分析模块│├──__init__.py│├──service.py│└──views.py└──utils.py

    3.1.0 模块概览

    3.1.1 Dom 树解析

    Dom 层核心功能

    其中 buildDomTree.js 是 Dom 层的核心组件,运行在浏览器环境中,负责智能识别和处理页面元素。

      // 函数入口functionbuildDomTree(node, parentIframe =null, isParentHighlighted =false){ // node: 当前要处理的 DOM 节点 // parentIframe: 父级 iframe(用于跨 iframe 处理) // isParentHighlighted: 父节点是否已被高亮(状态传递)}
      // 递归终止条件 - 防止无限递归if(!node || node.id === HIGHLIGHT_CONTAINER_ID || (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE)) {if(debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;returnnull; // 终止当前分支的递归}
      // 根节点特殊处理if(node === document.body) {constnodeData= { tagName:'body', attributes: {}, xpath:'/body', children: [], };
      // 核心递归点1:处理 body 的所有子节点for(constchild of node.childNodes) { constdomElement=buildDomTree(child, parentIframe,false);// 🔄 递归调用 if(domElement) nodeData.children.push(domElement); }
      constid= `${ID.current++}`; DOM_HASH_MAP[id] = nodeData;returnid;}
      // 核心递归点2:根据节点类型进行不同的递归处理if(node.tagName) {consttagName= node.tagName.toLowerCase();
      // 场景1:iframe 递归处理if(tagName ==="iframe") { try{ constiframeDoc= node.contentDocument || node.contentWindow?.document; if(iframeDoc) { for(constchild of iframeDoc.childNodes) { constdomElement=buildDomTree(child, node,false);// 🔄 跨 iframe 递归 if(domElement) nodeData.children.push(domElement); } } }catch(e) { console.warn("Unable to access iframe:", e); } }// 场景2:富文本编辑器递归处理elseif( node.isContentEditable || node.getAttribute("contenteditable") ==="true"|| node.id ==="tinymce"|| node.classList.contains("mce-content-body") ) { // 处理富文本内容 - 保持高亮状态传递 for(constchild of node.childNodes) { constdomElement=buildDomTree(child, parentIframe, nodeWasHighlighted);// 🔄 递归 if(domElement) nodeData.children.push(domElement); } }// 场景3:常规元素递归处理else{ // Shadow DOM 处理 if(node.shadowRoot) { nodeData.shadowRoot =true; for(constchild of node.shadowRoot.childNodes) { constdomElement=buildDomTree(child, parentIframe, nodeWasHighlighted);// 🔄 Shadow DOM 递归 if(domElement) nodeData.children.push(domElement); } } // 最重要的递归处理:常规子节点 for(constchild of node.childNodes) { // 关键:高亮状态的递归传递 constpassHighlightStatusToChild= nodeWasHighlighted || isParentHighlighted; constdomElement=buildDomTree(child, parentIframe, passHighlightStatusToChild);// 🔄 递归调用 if(domElement) nodeData.children.push(domElement); } }}
        # service.py - _construct_dom_tree 方法@time_execution_async('--construct_dom_tree')asyncdef_construct_dom_tree(self, eval_page:dict) ->tuple[DOMElementNode, SelectorMap]: """从 JavaScript 结果构建 Python DOM 树 - 核心递归处理"""  js_node_map = eval_page['map']  js_root_id = eval_page['rootId']
        selector_map = {} node_map = {}
        # 🔄 第一轮遍历:创建所有节点 forid, node_datainjs_node_map.items(): node, children_ids = self._parse_node(node_data) ifnodeisNone: continue
        node_map[id] = node
        # 建立可交互元素的索引映射 ifisinstance(node, DOMElementNode)andnode.highlight_indexisnotNone: selector_map[node.highlight_index] = node
        # 🔄 第二轮遍历:建立父子关系(递归结构) forid, node_datainjs_node_map.items(): node = node_map.get(id) ifisinstance(node, DOMElementNode): # 关键:递归建立父子关系 forchild_idinnode_data.get('children', []): ifchild_idinnode_map: child_node = node_map[child_id] child_node.parent = node # 设置父节点 node.children.append(child_node) # 添加子节点
        returnnode_map[str(js_root_id)], selector_map
          classClickableElementProcessor:"""可点击元素处理器"""@staticmethoddefget_clickable_elements_hashes(dom_elementOMElementNode)->set[str]:"""获取所有可点击元素的哈希值集合"""clickable_elements=ClickableElementProcessor.get_clickable_elements(dom_element)return{ClickableElementProcessor.hash_dom_element(element)forelementinclickable_elements}@staticmethoddefhash_dom_element(dom_elementOMElementNode)->str:"""为DOM元素生成唯一哈希标识"""#1.父级路径哈希parent_branch_path=ClickableElementProcessor._get_parent_branch_path(dom_element)branch_path_hash=ClickableElementProcessor._parent_branch_path_hash(parent_branch_path)#2.属性哈希attributes_hash=ClickableElementProcessor._attributes_hash(dom_element.attributes)#3.XPath哈希xpath_hash=ClickableElementProcessor._xpath_hash(dom_element.xpath)#4.组合哈希returnClickableElementProcessor._hash_string(f'{branch_path_hash}-{attributes_hash}-{xpath_hash}')
            // 元素高亮 - 为 AI 提供视觉索引functionhighlightElement(element, index, parentIframe =null){// 1. 创建高亮容器letcontainer =document.getElementById(HIGHLIGHT_CONTAINER_ID);if(!container) {  container =document.createElement("div");  container.id=HIGHLIGHT_CONTAINER_ID;  container.style.zIndex="2147483640"; // 最高层级 }
            // 2. 为每个元素创建彩色边框和数字标签constcolors = ["#FF0000","#00FF00","#0000FF","#FFA500"];constbaseColor = colors[index % colors.length];
            // 3. 多矩形支持 (处理复杂布局)constrects = element.getClientRects();for(constrectofrects) { constoverlay =document.createElement("div"); overlay.style.border=`2px solid${baseColor}`; overlay.style.backgroundColor= baseColor +"1A";// 10% 透明度 // 设置位置和尺寸... }}
              [1]<headerclass='app-header'> [2]<divclass='logo'>  公司 LOGO [3]<navclass='main-nav'>  [4]<a href='/dashboard'>控制台 />  [5]<a href='/projects'>项目管理 />  [6]<divclass='user-menu'>   [7]<buttonclass='user-avatar'>    [8]<img alt='用户头像'/>   [9]<divclass='dropdown-menu'>    [10]<a href='/profile'>个人资料 />    [11]<a href='/settings'>账户设置 />    [12]<button >退出登录 />[13]<mainclass='app-content'> [14]<asideclass='sidebar'>  [15]<ulclass='nav-list'>   [16]<li >    [17]<a href='/tasks'>任务列表 />   [18]<li >    [19]<a href='/calendar'>日历视图 /> [20]<sectionclass='content-area'>  [21]<divclass='toolbar'>   [22]<buttonclass='btn-primary'>新建任务 />   [23]<input type='search'placeholder='搜索任务'/>   [24]<selectname='filter'>    [25]<optionvalue='all'>全部任务 />    [26]<optionvalue='pending'>待处理 />  [27]<divclass='task-list'>   任务列表内容   [28]<divclass='task-item'>    [29]<input type='checkbox'/>    完成网站设计    [30]<buttonclass='edit-btn'>编辑 />    [31]<buttonclass='delete-btn'>删除 />    *[32]*<button >新出现的按钮 /> # 用 * 标记新元素
                #views.py-clickable_elements_to_string方法defclickable_elements_to_string(self,include_attributes:list[str]|None=None)->str:"""将DOM树递归转换为LLM可理解的文本格式"""formatted_text=[]defprocess_node(nodeOMBaseNode,depth:int)->None:"""📍递归处理函数-深度优先遍历和格式化"""next_depth=int(depth)depth_str=depth*'\t'#缩进表示层级ifisinstance(node,DOMElementNode):#处理可交互元素ifnode.highlight_indexisnotNone:next_depth+=1#格式化当前节点信息text=node.get_all_text_till_next_clickable_element()#...属性处理和格式化逻辑formatted_text.append(formatted_line)#⭐递归处理所有子节点forchildinnode.children:process_node(child,next_depth)#递归调用elifisinstance(node,DOMTextNode):#处理文本节点if(notnode.has_parent_with_highlight_index()andnode.parentandnode.parent.is_visible):formatted_text.append(f'{depth_str}{node.text}')process_node(self,0)#从根节点开始递归return'\n'.join(formatted_text)

                3.1.2 记忆模块

                消息元数据,记录消息的 token 数和类型。

                包装实际底层 langchain 的 BaseMessage 消息对象和消息元数据。

                历史消息管理,包括消息的增加,删除和获取。

                  MessageManager(最高层-业务逻辑)↓使用MessageManagerState(状态层)↓包含MessageHistory(历史管理层)↓包含ManagedMessage(消息包装层)↓包含MessageMetadata+BaseMessage(数据层)

                  目前的消息截断策略比较简单,当 token 数量超过最大限制的时候,Agent 会优先移除最久的非系统消息。

                  Browser-Use 使用的 mem0 作为它 Memory 模块的核心引擎,并构建了一个完整的封装层来适配 Browser-Use 的特定需求,我们在开启 memory 的时候,每一次步骤执行的时候,都会对根据历史消息对话信息进行总结压缩,将历史的对话信息替换成总结压缩后的记忆信息。

                  3.1.3 工具注册&管理

                  controller 层作为 Action 的统一管理中心,一方面提供注册浏览器的各种行为管理,另一方面将抽象的 AI 指令转换为具体的浏览器操作。

                  Browser-Use 通过装饰器模式实现动作注册。@self.registry.action其中包含工具的描述和参数模型。

                  根据Action名称去registy管理的工具元数据中索引出来对应的工具信息,然后根据模型返回的参数和实际所需的上下文参数重新组装成新的参数,最后执行工具调用即可。

                  3.1.4 Browser 浏览器模块

                  1.Browser (浏览器)代表一个完整的浏览器进程,相当于启动了一个 Chrome/Firefox/Safari 程序一个 Browser 可以包含多个 BrowserContext。

                  2.BrowserContext (浏览器上下文)不是浏览器窗口,而是一个独立的浏览器会话,相当于 Chrome 的隐身模式或者不同的用户配置文件每个 BrowserContext 有自己独立的:

                  3.Page (页面)才是真正的标签页,一个 BrowserContext 可以包含多个 Page。

                    Playwright├──Browser(浏览器进程)-独立的操作系统进程│├──BrowserContext(浏览器上下文/会话)-独立的用户会话││├──Page(页面/标签页)-具体的网页标签│││├──Frame(框架)-页面中的iframe等││││└──ElementHandle(元素句柄)│││└──Worker(WebWorker)││├──Page(另一个标签页)││└──ServiceWorker(服务工作者)│├──BrowserContext(另一个独立上下文)││├──Page││└──Page│└──BrowserContext(更多上下文...)├──Browser(另一个浏览器进程)│└──...└──Browser(更多浏览器进程...)

                    下面,一个是需要注意这里设计了一个 BrowserStateSummary 的数据模型给 LLM 去处理,它记录了 browser 当前的状态信息,包括打开了哪些标签页,当前所在的页面等等。

                    BrowserSession 类这里可以看到,通过连接现有浏览器或启动新浏览器来启动浏览器会话。我们后面实战过程中,其实就是通过 cdp 协议连接到了远程服务器的 browser 实例,从而实现访问外部网站的能力。

                    browser 模块与其他模块的协作有:

                    Browser 模块这里,主要还是提供了高度可配置的 Browser 实例,以及一些浏览器相关的自动重连、错误处理和缓存机制。

                    3.1.5 多层次 Prompt 设计

                    BrowserUse 中的 Prompt 主要分为了三种类型:

                    SystemPrompt

                    系统提示词

                    核心内容从system_prompt.md文档中进行加载,主要是告诉 Agent 它是什么角色,应该如何行动,大致有如下的规则设定:

                    它提供两种扩展系统提示词的方式:

                    扩展模式:通过extend_system_message参数扩展默认提示词,其实就是将参数拼到默认的系统提示词的最后面

                    覆盖模式:通过override_system_message完全替换默认提示词

                      You are an AI agent designed to automate browser tasks. Your goal is to accomplish the ultimate task following the rules.
                      # Input FormatTaskPrevious stepsCurrent URLOpen TabsInteractive Elements[index]<type>text</type>- index: Numeric identifierforinteraction-type: HTML elementtype(button, input, etc.)- text: Element description Example: [33]<div>User form</div> \t*[35]*<button aria-label='Submit form'>Submit</button>- Only elements with numeric indexesin[] are interactive- (stacked) indentation (with \t) is important and means that the element is a(html) child of the element above(with a lower index)- Elements with \* are new elements that were added after the previous step(ifurl has not changed)# Response Rules1. RESPONSE FORMAT: You must ALWAYS respond with valid JSONinthis exact format:{{"current_state": {{"evaluation_previous_goal":"Success|Failed|Unknown - Analyze the current elements and the image to check if the previous goals/actions are successful like intended by the task. Mention if something unexpected happened. Shortly state why/why not", "memory":"Description of what has been done and what you need to remember. Be very specific. Count here ALWAYS how many times you have done something and how many remain. E.g. 0 out of 10 websites analyzed. Continue with abc and xyz", "next_goal":"What needs to be done with the next immediate action"}}, "action":[{{"one_action_name": {{// action-specific parameter}}}}, // ... more actionsinsequence]}}2. ACTIONS: You can specify multiple actionsinthe list to be executedinsequence. But always specify only one action name per item. Use maximum {max_actions} actions per sequence.Common action sequences:- Form filling: [{{"input_text": {{"index": 1,"text":"username"}}}}, {{"input_text": {{"index": 2,"text":"password"}}}}, {{"click_element": {{"index": 3}}}}]- Navigation and extraction: [{{"go_to_url": {{"url":"https://example.com"}}}}, {{"extract_content": {{"goal":"extract the names"}}}}]- Actions are executedinthe given order- If the page changes after an action, the sequence is interrupted and you get the new state.- Only provide the action sequenceuntilan actionwhichchanges the page state significantly.- Try to be efficient, e.g. fill forms at once, or chain actionswherenothing changes on the page- only use multiple actionsifit makes sense.3. ELEMENT INTERACTION:- Only use indexes of the interactive elements4. NAVIGATION & ERROR HANDLING:- If no suitable elements exist, use otherfunctionsto complete the task- If stuck, try alternative approaches - like going back to a previous page, new search, new tab etc.- Handle popups/cookies by accepting or closing them- Use scroll to find elements you are lookingfor- If you want to research something, open a new tab instead of using the current tab- If captcha pops up, try to solve it - elsetry a different approach- If the page is not fully loaded, usewaitaction5. TASK COMPLETION:- Use thedoneaction as the last action as soon as the ultimate task is complete- Dont use"done"before you aredonewith everything the user asked you, except you reach the last step of max_steps.- If you reach your last step, use thedoneaction evenifthe task is not fully finished. Provide all the information you have gathered so far. If the ultimate task is completely finishedsetsuccess totrue. If not everything the user askedforis completedsetsuccessindonetofalse!- If you have todosomething repeatedlyforexample the task saysfor"each", or"for all", or"x times", count always inside"memory"how manytimesyou havedoneit and how many remain. Don't stop until you have completed like the task asked you. Only call done after the last step.- Don't hallucinate actions- Make sure you include everything you found outforthe ultimate taskinthedonetext parameter. Do not just say you aredone, but include the requested information of the task.6. VISUAL CONTEXT:- When an image is provided, use it to understand the page layout- Bounding boxes with labels on their top right corner correspond to element indexes7. Form filling:- If you fill an input field and your action sequence is interrupted, most often something changed e.g. suggestions popped up under the field.8. Long tasks:- Keep track of the status and subresultsinthe memory.- You are provided with procedural memory summaries that condense previous taskhistory(every N steps). Use these summaries to maintain context about completed actions, current progress, and next steps. The summaries appearinchronological order and contain key information about navigationhistory, findings, errors encountered, and current state. Refer to these summaries to avoid repeating actions and to ensure consistent progress toward the task goal.9. Extraction:- If your task is to find information - call extract_content on the specific pages to get and store the information. Your responses must be always JSON with the specified format.

                      代理消息提示词

                      根据浏览器上下文的信息构造包含当前页面信息的提示词,帮助模型理全面理解当前页面的信息和可执行的动作。

                      规划提示词

                      分析当前任务进度和完成情况、制定下一步的高级策略、识别潜在的挑战和障碍、提供任务分解和决策支持。但总的来说是可以在运行固定步长后将历史对话信息交给另一个planner_llm 进行一次规划总结,从高层次对agent进行指导。

                      四、思考总结

                      现阶段的 BrowserUse 个人认为它主要是有几个创新点,一个是开创性地构建带标识 Dom 树结构的方式来辅助大模型去理解网页结构和内容,并能通过 index 去精确定位到 clickable 元素,另一个是它串起了 LLM 对于网页内容的理解、next goal 思考、决策路径、action 行动的流程。

                      其本质上还是使用 LLM + Playwright 来实现 AI 操作浏览器,而未来如果基础模型的多模态能力能够有大幅度的提升和完善,那么或许可以直接通过理解复杂的视觉内容来更进一步理解网页内容!







                      欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5