深入剖析 Browser Use:49.9K+ Star 的 AI 驱动浏览器自动化框架

深入剖析 Browser Use

Browser Use 介绍

Browser Use 是一个AI驱动浏览器自动化开源框架,让我们自然语言操作浏览器,目前在 GitHub 上已经获得了惊人的 49.9k star

下方演示展示了我启动 Browser Use 运行的一个小 demo,通过简单的自然语言指令"打开百度并搜索苹果",Browser Use 自主完成了网页导航、识别搜索框、输入关键词并执行搜索的全过程

Browser Use 工作原理

如图所示,Browser Use 首先捕获了浏览器实时状态,然后整合结构化信息交由 LLM 进行智能决策,随后执行确定的动作,完成后再次获取更新后的浏览器状态,循环往复直至任务完成。

这一自动化执行流程由三大核心模块组成,通过 browser_use/agent 的调度下协同工作形成了一个完整的自动化执行流程:

  1. 获取浏览器状态
    • 浏览器管理模块(browser_use/browser)
    • DOM 处理模块(browser_use/dom)
  2. 消息管理(browser_use/agent/message_manager)
  3. 动作执行(browser_use/controller)

获取浏览器状态

Browser Use 获取浏览器状态,主要函数如图所示,分为两个模块

  1. browser_use/browser 基于 Playwright 实现浏览器的核心控制与管理,负责启动浏览器实例、处理浏览器上下文和页面

  2. browser_use/dom 解析和提取页面元素信息、元素高亮定位

浏览器管理模块(browser_use/browser)

浏览器实例初始化策略

源码路径: browser_use/browser/browser.py

browser-use 提供了四种不同的浏览器初始化策略,以适应不同的使用场景:

  1. _setup_cdp:通过 Chrome DevTools Protocol 连接到正在运行的 Chrome 实例,便于调试
  2. _setup_wss:适用于连接到云端浏览器服务(如 anchorbrowser.iobrowserless.io),实现远程操作
  3. _setup_strandard_browser:使用已安装 Chrome 的用户配置文件,保留登录状态和 Cookie
  4. _setup_browser:默认方法,创建新的浏览器实例,适用于基本场景

browser use 官方文档

这里我分别测试了 1、3、4 三种初始化方式,对于方式 1 可以采用以下命令启动通过 --user-data-dir 来指定用户信息

方式 1、3 的底层逻辑实际相同,都是基于 Chrome DevTools Protocol (CDP) 进行连接,只不过方式 3 在代码中自动执行启动命令

ini 复制代码
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"   --remote-debugging-port=9222 --user-data-dir="存储用户和浏览器插件路径"
浏览器上下文创建与反检测技术

创建上下文的核心逻辑在 BrowserContext.create_context,整体代码比较简单,通过默认方式启动可以分为下四步

  • context = browser.new_context(...) 创建新的上下文(无痕窗口)
    • CDP 连接,且浏览器已有上下文,会使用当前浏览器的上下文
  • context.tracing.start 记录浏览器操作的详细日志,用于调试和问题排查
  • context.add_cookies 加载 cookie
  • context.add_init_script 注入反检测脚本,用来避免网站检测到自动化行为的脚本

源码路径: browser_use/browser/context.py

重点是注入反检测脚本,毕竟我还是第一次学到😂

  • Webdriver 特征隐藏

    • 自动化浏览器通常会将navigator.webdriver设置为true
  • 浏览器语言伪装

    • 覆盖浏览器的语言偏好设置,语言设置是浏览器指纹的重要组成部分
  • 浏览器插件模拟

    • 自动化浏览器通常没有安装插件
  • Chrome 运行时环境模拟

    • 添加 Chrome 特有的浏览器对象,反爬系统会检查 window.chrome 对象结构
  • 权限 API 修改

    • Headless(无头)浏览器处理通知权限的方式与普通浏览器不同,对通知权限查询返回一致的结果
  • Shadow DOM

    • 避免网站使用封闭模式 Shadow DOM 隐藏内容,无论网站指定什么模式,都返回开放模式的 Shadow DOM
    • 还使用立即执行函数表达式(IIFE)封装,避免变量污染,细节
获取标签页

检查是否存在已打开的页面,如果存在,返回最后一个页面;如果不存在,创建新页面

DOM 处理模块(browser_use/dom)

DOM 处理模块是 browser-use 的核心,解析页面获取 DOM 节点的信息,提供精确的元素定位和交互能力,帮助 LLM 更准确的决策

以百度为例,介绍一些前端的概念,网页展示基于 HTML 文档,该文档由各种标签元素(如<div>, <a>, <img>等)组成层级结构。浏览器解析这些 HTML 元素后,将其转换为 DOM (文档对象模型) 树,其中每个 HTML 元素、文本和属性都变成了 DOM 树中的节点。JavaScript 可以通过这个 DOM 树动态操作和修改网页内容,实现交互功能。

我在百度页面执行了 browser_use/dom/buildDomTree.js 实现效果如图, 我们可以看到页面很多元素被高亮并打上索引,还返回了页面元素的结构化信息

这里我们可以想到两个问题,什么元素需要被高亮并打上了索引?需要返回元素的哪些关键信息来辅助AI决策?

让我们带着问题一起看看 buildDomTree 的实现逻辑吧

buildDomTree 函数深度优先递归遍历方式,将原生 DOM 树(n 叉树)转换为结构化对象模型,提取并组织每个节点的关键属性、层级关系和交互特性

  • DOM 节点处理:

    • DOM 规范中定义了多种节点类型(DOCUMENT_NODE、COMMENT_NODE、ATTRIBUTE_NODE等),builDomTree 只会处理元素节点(ELEMENT_NODE)文本节点(TEXT_NODE)这两种最能代表页面内容和结构的两种核心节点类型
    • 还会对这两种节点进行过滤,排除对LLM决策无价值的元素类型,例如空文本节点、script、svg 等对内容理解和交互决策无实质贡献的节点
  • xpath:为每个节点生成唯一的XPath标识,便于定位与追踪

  • isVisible:判断元素是否正常显示

  • isInViewport:确定元素是否在当前可视区域内

  • isTopElement:检测元素是否是其位置上的顶层元素

  • isInteractive:识别可交互元素,如链接、按钮等

  • highlightIndex:高亮元素并添加索引

xpath 每个节点生成唯一的XPath标识
javascript 复制代码
  function getXPathTree(element, stopAtBoundary = true) {
    const segments = [];
    let currentElement = element;

    while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
      // 遇到Shadow DOM或iframe边界时停止
      if (
        stopAtBoundary &&
        (currentElement.parentNode instanceof ShadowRoot ||
          currentElement.parentNode instanceof HTMLIFrameElement)
      ) {
        break;
      }

      // 计算同名兄弟节点中的索引
      let index = 0;
      let sibling = currentElement.previousSibling;
      while (sibling) {
        if (
          sibling.nodeType === Node.ELEMENT_NODE &&
          sibling.nodeName === currentElement.nodeName
        ) {
          index++;
        }
        sibling = sibling.previousSibling;
      }

      // 构建XPath片段
      const tagName = currentElement.nodeName.toLowerCase();
      const xpathIndex = index > 0 ? `[${index + 1}]` : "";
      segments.unshift(`${tagName}${xpathIndex}`);

      currentElement = currentElement.parentNode;
    }

    return segments.join("/");
  }

getXPathTree 的逻辑还是比较简单,主要是对于有同级的重名节点,会获取对应索引进行拼接,剩下的就是不断的获取自己的父节点,判断是否有同级的重名节点,到根节点为止

vbnet 复制代码
xpath: "html/body/div/div/div[3]/div/a"
isVisible 判断元素是否正常显示
isTopElement 检测元素是否是其位置上的顶层元素

在我省略的代码中有一个值得注意的点

  • 如果元素在iframe中,默认认为它是顶层元素
    • 我写个 demo 测试了下,在当前文档下调用 document.elementFromPoint 只能获取到 iframe,获取不到 iframe 的子节点,
    • 通过iframe.contentDocument 继续调用会受到浏览器同源策略的限制
isInteractive 识别可交互元素

isInteractiveElement 函数采用多层次的判断策略,从明确的标签属性到隐含的行为特征,一层层的判断元素的交互性,代码看着倒是都很简单,但是判断非常的多...

  1. Cookie 横幅特殊处理,登录国外网站好像见的多一点,比如 stackOverflow
javascript 复制代码
const isCookieBannerElement = (typeof element.closest === 'function') && (
  element.closest('[id*="onetrust"]') ||
  element.closest('[class*="onetrust"]') ||
  element.closest('[data-nosnippet="true"]') ||
  element.closest('[aria-label*="cookie"]')
);

函数首先检查元素是否位于 Cookie 横幅内,并进一步判断它是否为接受或拒绝按钮。这种特殊处理反映了现代网页浏览中常见的用户交互模式。

  1. 标准交互元素识别

函数检查元素是否属于公认的交互式 HTML 元素或具有交互角色:

javascript 复制代码
const interactiveElements = new Set([
  "a", "button", "details", "embed", "input", "menu", "menuitem",
  "object", "select", "textarea", "canvas", "summary", "dialog",
  "banner"
]);

const interactiveRoles = new Set(['button-icon', 'dialog', /* ... 其他角色 ... */]);
  1. 基于属性的交互性判断
javascript 复制代码
const hasInteractiveRole =
  hasAddressInputClass ||
  interactiveElements.has(tagName) ||
  interactiveRoles.has(role) ||
  interactiveRoles.has(ariaRole) ||
  (tabIndex !== null && tabIndex !== "-1" && element.parentElement?.tagName.toLowerCase() !== "body") ||
  element.getAttribute("data-action") === "a-dropdown-select" ||
  element.getAttribute("data-action") === "a-dropdown-button";
  1. cookie 横幅和同意界面
javascript 复制代码
Apply to buildDomTree...

const isCookieBanner =

  element.id?.toLowerCase().includes('cookie') ||

  element.id?.toLowerCase().includes('consent') ||

  /* ... 其他条件 ... */;
  1. 事件监听器和行为特征

最后,函数检查元素的行为特征,包括事件监听器和 ARIA 属性:

javascript 复制代码
function getEventListeners(el) {
      try {
        return window.getEventListeners?.(el) || {};
      } catch (e) {
        // ...降级策略
      }
}

// 检查点击相关事件
const listeners = getEventListeners(element);

const hasClickListeners = listeners && (/* ... 条件 ... */);

// 检查 ARIA 属性
const hasAriaProps = element.hasAttribute("aria-expanded") || /* ... 其他属性 ... */;

// 可编辑内容
const isContentEditable = element.getAttribute("contenteditable") === "true" ||
  element.isContentEditable ||
  element.id === "tinymce" ||
  /* ... 其他条件 ... */;

// 元素是否可拖拽
const isDraggable =
      element.draggable || element.getAttribute("draggable") === "true";

ARIA 我还是第一次听说,MDN 的解释是 无障碍富互联网应用(Accessible Rich Internet Applications,ARIA是一组角色属性,用于定义使残障人士更易于访问 web 内容和 web 应用程序(尤其是使用 JavaScript 开发的应用程序)的方法

highlightElement 高亮元素

doHighlightElements 默认值是 true, focusHighlightIndex 默认值是 -1, 默认情况下,经过上面的判断的元素才会进行高亮操作

通过控制台可以看到,highlightElement 创建一个高亮容器添加到 body 下,获取每个元素的位置,通过定位的覆盖在元素上,核心代码如下

javascript 复制代码
// 创建或获取高亮容器
let container = document.getElementById(HIGHLIGHT_CONTAINER_ID);
if (!container) {
  container = document.createElement("div");
  container.id = HIGHLIGHT_CONTAINER_ID;
  // 设置容器的基本样式
  container.style.position = "fixed";
  container.style.pointerEvents = "none"; // 确保不会干扰用户交互
  container.style.top = "0";
  container.style.left = "0";
  container.style.width = "100%";
  container.style.height = "100%";
  container.style.zIndex = "2147483647"; // 最高层级
  document.body.appendChild(container);
}

const rect = measureDomOperation(
  () => element.getBoundingClientRect(),
  "getBoundingClientRect"
);

// 计算最终位置
const top = rect.top + iframeOffset.y;
const left = rect.left + iframeOffset.x;

// 设置覆盖层位置和尺寸
overlay.style.top = `${top}px`;
overlay.style.left = `${left}px`;
overlay.style.width = `${rect.width}px`;
overlay.style.height = `${rect.height}px`;

// 将覆盖层和标签添加到容器中
container.appendChild(overlay);

源码路径:browser_use/dom/buildDomTree.js#L193

JavaScript 到 Python 的数据转换

JavaScript 获取 DOM 信息:buildDomTree.js 函数遍历浏览器DOM,构建一个包含节点关系和属性的扁平化 Map 结构:

javascript 复制代码
{
  map: {
    21: {
      type: "TEXT_NODE",
      text: "hao123",
      isVisible: true,
    },
    22: {
      tagName: "a",
      attributes: {},
      xpath: "html/body/div/div/div[3]/a[2]",
      children: ["21"],
      isVisible: true,
      isTopElement: true,
      isInteractive: true,
      isInViewport: true,
      highlightIndex: 3,
    },
    468: {
      tagName: "body",
      attributes: {},
      xpath: "/body",
      children: ["4", "6", "466", "467"],
    },
  },
  rootId: 468,
};

Python 解析转换:_construct_dom_tree 方法接收这个 Map 数据,针对不同的节点类型,系统创建相应的 Python 类实例,元素节点转换为 DOMElementNode 实例,文本节点转换为 DOMTextNode 实例,将 JavaScript 的简单数据结构转换为丰富的 Python 对象网络。

不仅如此 _construct_dom_tree, 返回两个关键数据结构

  • html_to_dict:从根节点开始的完整DOM树,包含所有节点及其关系
    • 在后续的消息管理的当前页面信息中的可交互元素就是通过 html_to_dict 获取的
  • selector_map:高亮索引到节点的直接映射
    • 在后续的动作执行中会用到

消息管理

系统提示词 (System Prompt)

定义了 AI 代理的角色、输入格式、响应规则, 我就不翻译一遍放在这了。

源码路径 browser_use/agent/system_prompt.md

定义任务

明确 AI 代理的最终任务目标

译:"你的最终任务是:"""打开百度,搜索苹果""". 如果你已经完成了最终任务,停止所有操作并在下一步使用 done 动作来完成任务。如果没有完成,则照常继续。"

javascript 复制代码
[
    {
        "content": "Your ultimate task is: \"\"\"打开百度,搜索苹果\"\"\". If you achieved your ultimate task, stop everything and use the done action in the next step to complete the task. If not, continue as usual.",
        "role": "user"
    },
]

isInteractiveElement

输出示例

javascript 复制代码
[
  // ...省略
  { content: "Example output:", role: "user" },
  {
    content: null,
    role: "assistant",
    tool_calls: [
      {
        function: {
          arguments: {
            current_state: {
              // 评估上一步操作的结果
              evaluation_previous_goal: "Success - I opend the first page",
              // 记录任务的累积进度和关键信息
              memory: "Starting with the new task. I have completed 1/10 steps",
              // 定义即将执行的具体目标
              next_goal: "Click on company a",
            },
            //  AI 代理计划执行的一个或多个操作
            action: [
              {
                // 具体动作
                click_element: {
                  // index 对应页面上高亮元素的序号
                  index: 0,
                },
              },
            ],
          },
          name: "AgentOutput",
        },
        id: "1",
        type: "function",
      },
    ],
  },
  // 
  { content: "Browser started", role: "tool", tool_call_id: "1" },
  // ...省略
];

系统提示词 中已经定义了响应规则,为什么还要在对话消息中再给输出示例那,区别在哪?

  • 系统提示词响应规则:相当于给 LLM 提供了详细的"理论教材

    • 定义了数据结构和字段含义
    • 说明了各个参数的使用规则
    • 提供了全面的格式指南和约束
  • 对话中的示例:相当于进行了生动的"实际演示

    • 展示了完整的工具调用格式和流程
    • 提供了具体场景中的应用方式
    • 呈现了理想输出的真实样貌

到此理论结合实践,我有一种大师我悟了的感觉😲

决策执行记录

下面的提示词,是将 LLM 返回的决策和决策执行的结果,添加到上下文中,记录思考过程和反馈执行结果,帮助LLM避免重复操作、根据历史经验调整策略、理解当前状态与任务起点的关系,从而保持行动的连贯性和目标一致性。

"[Your task history memory starts here]"提供了明确的历史记录起点,避免与前面的示例输出混淆,确保LLM能够清晰区分示范内容和实际执行历史。

javascript 复制代码
[
  // ...省略
  // 任务历史开始标记
  { content: "[Your task history memory starts here]", role: "user" },
  // LLM 返回的决策动作
  {
    content: null,
    role: "assistant",
    tool_calls: [
      {
        function: {
          arguments: {
            current_state: {
              // 评估上一步操作的结果
              evaluation_previous_goal:
                "Unknown - The page is currently blank, so no previous actions can be evaluated.",
              // 记录任务的累积进度和关键信息
              memory:
                "The task is to open Baidu and search for '苹果'. Currently on a blank page.",
              // 定义即将执行的具体目标
              next_goal: "Open Baidu's homepage.",
            },
            action: [
              {
                go_to_url: {
                  url: "https://www.baidu.com",
                },
              },
            ],
          },
          name: "AgentOutput",
        },
        id: "2",
        type: "function",
      },
    ],
  },
  { content: "", role: "tool", tool_call_id: "2" },
  {
    content: "Action result: 🔗  Navigated to https://www.baidu.com",
    role: "user",
  },
  
  // ...省略
];

当前页面信息

看到 Browser Use 它将复杂的网页结构转化为 LLM 能理解的简明描述,让 LLM 通过截图获得视觉布局,结构化文本理解元素关系,使用简单的数字索引(如[12]、[13])精确定位元素

javascript 复制代码
[
  // ...省略
  
  {
    content: [
      {
        text: `
            [Task history memory ends] - 表示历史记录已结束
            [Current state starts here] - 表示当前状态信息开始
            // 译:以下是一次性信息-如果你需要记住它,请将其写入内存
            The following is one-time information - if you need to remember it write it to memory:

            Current url: https://www.baidu.com/

            Available tabs:
            [TabInfo(page_id=0, url='https://www.baidu.com/', title='百度一下,你就知道')]

            // 译:视口内当前页面顶层的交互元素:
            Interactive elements from top layer of the current page inside the viewport:
            [Start of page]
                [0]<a 新闻/>
                [1]<a hao123/>
                [2]<a 地图/>
                ...省略
                [31]<a 京公网安备11000002000001号/>
                [32]<a 京ICP证030173号/>
                互联网新闻信息服务许可证11220180008

                [33]<img />
            [End of page]

            Current step: 2/100
            Current date and time: 2025-03-20 17:24
        `,
        type: "text",
      },
      {
        // 页面截图
        image_url: {
            "url": "data:image/png;base64,"
        },
        type: "image_url",
      },
    ],
    role: "user",
  },
];

,提示词中特意强调"The following is one-time information"(这是一次性信息),这是因为 browser-use 对上下文做了优化,在每次请求结束后,都会删除历史中页面信息,避免base64图像和大量页面元素描述占用过多令牌空间

Tools / Function Calling

Tools/Function Calling是现代AI模型与外部系统交互的关键机制,它通过结构化接口让AI能够调用特定功能并执行具体操作。现在爆火的 MCP(Multimodal Conversational Platform)正是这种机制的一种实现形式。

Tools 的组成

Tools 机制主要由两个关键部分组成:

  • tool_choice:指定默认或强制使用的工具,每个工具通常包含以下核心元素
    • description:工具的功能描述,如"具有自定义动作的AgentOutput模型"
    • name:工具的唯一标识符,如"AgentOutput"
    • parameters:工具接受的参数结构定义
  • tools数组:定义可用工具及其参数规范
参数结构

在 Browser Use 框架中,AgentOutput工具需要两个主要参数:

  1. current_state 参数

这部分定义AI代理的状态管理系统,包含三个关键字段:

  • evaluation_previous_goal:对上一步操作结果的评估

  • memory:任务进度和关键信息的存储

  • next_goal:即将执行的下一步目标

这种状态设计使AI能够保持上下文感知,实现连续性决策。

  1. action参数

action参数定义了AI可以执行的具体操作,它是一个操作数组,每个操作项只能包含一种操作类型,例如:

  • click_element:点击页面元素
  • done:标记任务完成
JSON Schema实现参数验证和结构约束
  • properties:定义对象的各个属性
  • anyOf:支持多种可能的类型或结构
  • required:指定必须提供的字段
  • type:约束值的数据类型
  • default:提供默认值
  • min_items:控制数组的最小长度

DeepSeek 的限制

在将上下文传递给语言模型之前,browser-use 框架针对 DeepSeek 系列模型(deepseek-reasoner和deepseek-r1)做了专门的处理,

  • DeepSeek 模型(包括 deepseek-reasoner 和 deepseek-r1)不支持函数调用(- Tools / Function Calling)功能,需要将某些特殊消息类型(如 ToolMessage)转换为基本的 HumanMessage 格式,将 AI 消息中的 tool_calls 转换为普通的 JSON 字符串,使模型能够正确处理

  • DeepSeek 模型不允许连续出现多个相同角色(人类或 AI)的消息,必须将连续的人类消息或 AI 消息合并成单个消息

动作执行

动作执行流程

以"打开百度,搜索苹果"任务为例,执行流程如下:

  1. LLM分析当前状态并返回结构化决策(如导航到百度)
  2. 决策被解析为具体动作(如go_to_url)
  3. Agent将动作传递给Controller
  4. Controller通过Registry查找并执行对应的处理函数

动作注册机制

Browser Use 通过 @registry.action 装饰器实现动作注册系统,除了内置的核心动作外,又提供了灵活的扩展机制,支持添加自定义动作

总结

Browser Use 原理表面看似简单,但实际包含复杂精妙的设计,有着大量操作细节和技术深度

相关推荐
喝拿铁写前端2 小时前
前端与 AI 结合的 10 个可能路径图谱
前端·人工智能
codingandsleeping2 小时前
浏览器的缓存机制
前端·后端
灵感__idea4 小时前
JavaScript高级程序设计(第5版):扎实的基本功是唯一捷径
前端·javascript·程序员
摇滚侠4 小时前
Vue3 其它API toRow和markRow
前端·javascript
難釋懷4 小时前
JavaScript基础-history 对象
开发语言·前端·javascript
beibeibeiooo4 小时前
【CSS3】04-标准流 + 浮动 + flex布局
前端·html·css3
拉不动的猪4 小时前
刷刷题47(react常规面试题2)
前端·javascript·面试
浪遏4 小时前
场景题:大文件上传 ?| 过总字节一面😱
前端·javascript·面试
Bigger4 小时前
Tauri(十八)——如何开发 Tauri 插件
前端·rust·app
355984268550555 小时前
医保服务平台 Webpack逆向
前端·webpack·node.js