如何让 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 早期阶段:脚本化和人工编码

  • 技术手段
  • 对于实时单次数据获取,通常依赖开发者手动编写 Python 脚本(如 requests + BeautifulSoup),需要精确解析 HTML 结构;
  • 对于离线批量数据,可以使用 Scrapy 开源框架,通过定义 Spider 规则批量抓取网页,存储为结构化数据,需要利用 Xpath/CSS 选择器来手动配置字段提取规则;
  • 对于自动化测试场景,使用 Selenium,通过代码模拟点击、输入等操作,验证网页能力,虽然支持录制用户操作生成脚本,但仅支持简单流程;
  • 局限性
  • 针对静态页面,无法处理 JS 动态渲染内容,每次页面结构调整需要人工修改爬取逻辑的代码,维护成本高;

  • 对动态内容(如单页 SPA )支持有限,需要额外集成 Selenium 等无头浏览器,反爬机制需人工绕过,自动化程度低;

  • 缺乏智能决策能力,无法自动化处理分支逻辑。

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

  • 技术手段
  • 基于 UI 元素识别(如按钮、输入框的坐标或属性)和预设流程,通过模拟鼠标键盘操作实现自动化,代表工具有 UiPath、Automation Anywhere、Blue Prism;
  • 局限性
  • 依赖固定 UI 元素定位,网页布局变动易导致流程中断;

  • 缺乏语义理解,无法处理需要逻辑推理的任务(比如根据页面内容选择下一步操作);

  • 维护成本高,企业通常需要投入大量资源更新流程脚本以适应系统变更迭代。

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

  • 技术手段
  • 无头浏览器普及,Selenium + Chrome Headless 成为动态网页抓取标配,但资源消耗大;轻量级的工具,像 Puppeteer(Node.js)提供更轻量级控制,但仍需硬编码操作步骤;

  • 反爬攻防战,网站采用验证码、IP 限流、动态 Token 等机制,迫使爬虫开发者引入打码平台或代理池。

  • 局限性
  • 资源消耗和性能瓶颈,浏览器实例占用内存高,难以大规模并行抓取,其对 CPU 和内存的消耗显著高于传统 HTTP 请求;

  • 浏览器兼容性限制,Puppeteer 仅原生支持Chromium内核浏览器,而Selenium虽然支持多浏览器,但不同浏览器驱动的API差异导致跨平台脚本维护成本增加;

  • 反爬对抗的复杂性升级。

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

2.4 AI 驱动的范式跃迁

  • 需求驱动因素

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

  • 技术成熟条件

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

2.5 内容小结

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

三 核心技术解析

Browser-Use 项目中:

service.pyviews.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 模块概览

  • agent
  • gif.py:用于将 AI Agent 的执行历史转换成可视化的动态 GIF 动画,展示整个任务执行过程的,每一步的屏幕截图、任务目标和步骤信息、执行进度和状态;

  • message_manager 模块:管理大模型交互过程中所有通信内容,包括系统提示词、用户输入、模型输出、工具输出等;

  • memory 模块:记忆管理模块(基于 Mem0 的向量存储),专门用于优化长期任务执行中的上下文窗口使用,核心是解决 Token 限制问题(长期任务会产生大量对话历史),智能记忆压缩(对上面的 message 总结&压缩,被压缩的信息不涵盖系统提示词和memory相关的信息)。

  • browser:核心基础设施,负责管理和控制浏览器实例,为 AI Agent 提供与真实浏览器交互的能力,本质上是对 Playwright 进行了一层封装;

  • controller:整个框架的动作执行引擎&Action注册管理,负责将 AI Agent 的决策转换为具体的浏览器操作;

  • dom:整个框架的感知引擎,负责理解和处理网页结构,将复杂的 HTML DOM 转换为 Agent 可以理解和操作的结构化数据;

  • telemetry:追踪 Browser-Use 产品本身使用情况,用于收集用户使用情况,性能指标和错误信息;比如像那个模型成功率更高,哪种任务耗时过长,vision 功能使用频率,常见失败原因,最常用的自定义功能等等;

  • 事件发送:将遥测服务发送到分析服务;

  • 隐私保护:匿名化敏感数据;

  • 配置管理:控制遥测开关和参数;

3.1.1 Dom 树解析

Dom 层核心功能

  • Dom 结构解析与抽象

  • 智能元素识别与索引

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

  • 通过递归方式对DOM树进行深度遍历,确保每一个节点都能被准确访问与处理,为标注提供全面的元素信息

复制代码
  // 函数入口
  function buildDomTree(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++;
    return null;  // 终止当前分支的递归
  }

  // 根节点特殊处理
  if (node === document.body) {
    const nodeData = {
      tagName: 'body',
      attributes: {},
      xpath: '/body',
      children: [],
    };

    // 核心递归点1:处理 body 的所有子节点
    for (const child of node.childNodes) {
      const domElement = buildDomTree(child, parentIframe, false); // 🔄 递归调用
      if (domElement) nodeData.children.push(domElement);
    }

    const id = `${ID.current++}`;
    DOM_HASH_MAP[id] = nodeData;
    return id;
  }

  // 核心递归点2:根据节点类型进行不同的递归处理
  if (node.tagName) {
    const tagName = node.tagName.toLowerCase();

    // 场景1:iframe 递归处理
    if (tagName === "iframe") {
      try {
        const iframeDoc = node.contentDocument || node.contentWindow?.document;
        if (iframeDoc) {
          for (const child of iframeDoc.childNodes) {
            const domElement = 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 (const child of node.childNodes) {
        const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted); // 🔄 递归
        if (domElement) nodeData.children.push(domElement);
      }
    }
    // 场景3:常规元素递归处理
    else {
      // Shadow DOM 处理
      if (node.shadowRoot) {
        nodeData.shadowRoot = true;
        for (const child of node.shadowRoot.childNodes) {
          const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted); // 🔄 Shadow DOM 递归
          if (domElement) nodeData.children.push(domElement);
        }
      }
      
      // 最重要的递归处理:常规子节点
      for (const child of node.childNodes) {
        // 关键:高亮状态的递归传递
        const passHighlightStatusToChild = nodeWasHighlighted || isParentHighlighted;
        const domElement = buildDomTree(child, parentIframe, passHighlightStatusToChild); // 🔄 递归调用
        if (domElement) nodeData.children.push(domElement);
      }
    }
  }
  # service.py - _construct_dom_tree 方法
  @time_execution_async('--construct_dom_tree')
  async def _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 = {}

      # 🔄 第一轮遍历:创建所有节点
      for id, node_data in js_node_map.items():
          node, children_ids = self._parse_node(node_data)
          if node is None:
              continue

          node_map[id] = node

          # 建立可交互元素的索引映射
          if isinstance(node, DOMElementNode) and node.highlight_index is not None:
              selector_map[node.highlight_index] = node

      # 🔄 第二轮遍历:建立父子关系(递归结构)
      for id, node_data in js_node_map.items():
          node = node_map.get(id)
          if isinstance(node, DOMElementNode):
              # 关键:递归建立父子关系
              for child_id in node_data.get('children', []):
                  if child_id in node_map:
                      child_node = node_map[child_id]
                      child_node.parent = node      # 设置父节点
                      node.children.append(child_node)  # 添加子节点

      return node_map[str(js_root_id)], selector_map

对元素的可交互性和可见性进行精准判断,确保标注仅应用于符合条件的目标元素,提升标注的准确性

复制代码
classClickableElementProcessor:
    """可点击元素处理器"""
    
    @staticmethod
    def get_clickable_elements_hashes(dom_element: DOMElementNode) -> set[str]:
        """获取所有可点击元素的哈希值集合"""
        clickable_elements = ClickableElementProcessor.get_clickable_elements(dom_element)
        return {ClickableElementProcessor.hash_dom_element(element) for element in clickable_elements}
    
    @staticmethod
    def hash_dom_element(dom_element: DOMElementNode) -> 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. 组合哈希
        return ClickableElementProcessor._hash_string(f'{branch_path_hash}-{attributes_hash}-{xpath_hash}')
视觉标注实现(高亮系统)

// 元素高亮 - 为 AI 提供视觉索引
function highlightElement(element, index, parentIframe = null){
  // 1. 创建高亮容器
  let container = document.getElementById(HIGHLIGHT_CONTAINER_ID);
  if (!container) {
    container = document.createElement("div");
    container.id = HIGHLIGHT_CONTAINER_ID;
    container.style.zIndex = "2147483640";  // 最高层级
  }

  // 2. 为每个元素创建彩色边框和数字标签
  const colors = ["#FF0000", "#00FF00", "#0000FF", "#FFA500"];
  const baseColor = colors[index % colors.length];

  // 3. 多矩形支持 (处理复杂布局)
  const rects = element.getClientRects();
  for (const rect of rects) {
    const overlay = document.createElement("div");
    overlay.style.border = `2px solid ${baseColor}`;
    overlay.style.backgroundColor = baseColor + "1A"; // 10% 透明度
    // 设置位置和尺寸...
  }
}
Dom 树格式化输出

[1]<header class='app-header' >
  [2]<div class='logo' >
    公司 LOGO
  [3]<nav class='main-nav' >
    [4]<a href='/dashboard' >控制台 />
    [5]<a href='/projects' >项目管理 />
    [6]<div class='user-menu' >
      [7]<button class='user-avatar' >
        [8]<img alt='用户头像' />
      [9]<div class='dropdown-menu' >
        [10]<a href='/profile' >个人资料 />
        [11]<a href='/settings' >账户设置 />
        [12]<button >退出登录 />
[13]<main class='app-content' >
  [14]<aside class='sidebar' >
    [15]<ul class='nav-list' >
      [16]<li >
        [17]<a href='/tasks' >任务列表 />
      [18]<li >
        [19]<a href='/calendar' >日历视图 />
  [20]<section class='content-area' >
    [21]<div class='toolbar' >
      [22]<button class='btn-primary' >新建任务 />
      [23]<input type='search' placeholder='搜索任务' />
      [24]<select name='filter' >
        [25]<option value='all' >全部任务 />
        [26]<option value='pending' >待处理 />
    [27]<div class='task-list' >
      任务列表内容
      [28]<div class='task-item' >
        [29]<input type='checkbox' />
        完成网站设计
        [30]<button class='edit-btn' >编辑 />
        [31]<button class='delete-btn' >删除 />
        *[32]*<button >新出现的按钮 />  # 用 * 标记新元素

# views.py - clickable_elements_to_string 方法
def clickable_elements_to_string(self, include_attributes: list[str] | None = None) -> str:
    """将 DOM 树递归转换为 LLM 可理解的文本格式"""
    formatted_text = []
    
    def process_node(node: DOMBaseNode, depth: int) -> None:
        """📍 递归处理函数 - 深度优先遍历和格式化"""
        next_depth = int(depth)
        depth_str = depth * '\t'  # 缩进表示层级
        
        if isinstance(node, DOMElementNode):
            # 处理可交互元素
            if node.highlight_index is not None:
                next_depth += 1
                # 格式化当前节点信息
                text = node.get_all_text_till_next_clickable_element()
                # ... 属性处理和格式化逻辑
                formatted_text.append(formatted_line)
            
            # ⭐ 递归处理所有子节点
            for child in node.children:
                process_node(child, next_depth)  # 递归调用
        
        elif isinstance(node, DOMTextNode):
            # 处理文本节点
            if (not node.has_parent_with_highlight_index() and
                node.parent and node.parent.is_visible):
                formatted_text.append(f'{depth_str}{node.text}')
    
    process_node(self, 0)  # 从根节点开始递归
    return '\n'.join(formatted_text)

下一章继续解读

相关推荐
爱学习的张大1 小时前
具身智能论文问答(四):pi0
人工智能·算法
AI让世界更懂你1 小时前
从降本增效到能力重构:AI快速发展之后,我们该如何理解工程应用与科研AI?
人工智能·重构
ASKED_20191 小时前
KDD Cup 2026 腾讯算法广告大赛:UNI-REC-PCVRHyFormer 源码深度解读
人工智能
硅谷茶馆1 小时前
免费!开源!AI 全自动短视频工具,Comfyui本地接入0帧起手!
人工智能
qcx231 小时前
拆解 Warp AI Agent(五):跨生态联邦——10 种 Skill + MCP + 多 Harness 互操作设计
人工智能·rust·ai agent·skill·warp·mcp·harness
生成论实验室1 小时前
《事件关系阴阳博弈动力学:识势应势之道》第五篇:安全关键关系——故障、障碍与冲突
运维·服务器·人工智能·安全·架构
weixin_446260851 小时前
应用实战篇:利用 DeepSeek V4 构建生产级 AI 应用的全流程与最佳实践
大数据·linux·人工智能
AI科技星1 小时前
全域数学视角下N维广义数系的推广与本源恒等式构建【乖乖数学】
人工智能·机器学习·数学建模·数据挖掘
qcx231 小时前
拆解 Warp AI Agent(二):风险分级执行——Agent 如何做到安全并行、危险排队
人工智能·安全·ai·agent·源码解析·warp