Browser use — 利用 AI 操作浏览器 · 原理篇

前言

Browser-use 是一个强大的浏览器自动化框架,它结合了大语言模型(LLM)的能力与浏览器操作,使AI 能够像人类一样浏览网页、填写表单、点击按钮等。

1. 基础概念系统架构

Browser-use 采用模块化设计,由多个核心组件协同工作。

1.1 核心组件

Browser-Use由四个主要组件组成:

  1. Agent

    • 负责与 LLM 交互,接收任务并分解为具体操作
    • 维护对话历史和状态
    • 处理 LLM 输出,转换为浏览器操作
  2. Controller

    • 注册和管理可执行的浏览器操作(点击、输入等)
    • 提供标准操作接口和自定义扩展能力
  3. Browser

    • 管理浏览器上下文和状态
    • 封装了操作浏览器的方法,比如点击、输入等
  4. DOMService

    • 解析和处理页面的 DOM 结构
    • 提供元素定位和交互能力
    • 维护元素状态和历史记录

1.2 交互流程

下图展示了Browser-Use的基本工作流程:

sequenceDiagram participant User participant Agent participant LLM participant Controller participant Browser participant DOMService User->>Agent: 提供任务 Agent->>Browser: 初始化浏览器 loop 执行循环 Agent->>Browser: 获取当前状态 Browser->>DOMService: 提取 DOM 元素 DOMService->>Browser: 返回可交互元素 Browser->>Agent: 返回浏览器状态 Agent->>LLM: 发送状态和任务 LLM->>Agent: 返回决策和行动 Agent->>Controller: 执行行动 Controller->>Browser: 操作浏览器 Browser->>Agent: 返回操作结果 Agent->>Agent: 检查任务是否完成 end Agent->>User: 返回最终结果

2. 工作流程详解

2.1 初始化阶段

用户提供任务给Agent,例如"在亚马逊上搜索并购买iPhone 13"。Agent初始化浏览器,准备执行任务。

2.2 状态获取阶段

Agent获取当前浏览器状态,包括:

  • 可交互DOM树:通过DOMService提取页面上所有可以点击、输入的元素
  • 页面截图:如果启用视觉功能,会将可交互节点高亮并编号
  • 页面状态:当前URL、标题、打开的标签页等信息
  • 历史操作记录:之前执行过的操作和结果

2.3 决策生成阶段

Agent 将收集到的状态信息发送给 LLM,请求 LLM 分析当前情况并决定下一步操作。LLM 会返回结构化的操作指令,如"点击搜索框"、"输入 iPhone 13"等。

2.4 操作执行阶段

Agent 通过 Controller 执行 LLM 决定的操作,如点击、输入、滚动等。

2.5 状态更新与循环

执行操作后,Agent 会检查操作结果,更新状态,然后重复上述步骤,直到任务完成、达到最大步骤数或出现错误。

3. DOM 元素识别与交互

Browser use 可以识别网页中可交互内容,并且与之进行交互,这是其核心功能之一。

3.1 可交互元素识别

DOMService 通过以下标准判断元素是否可交互

javascript 复制代码
// 详细可以看 buildDomTree.js
const isInteractiveElement = 
    hasAriaProps ||
    hasClickHandler ||
    hasClickListeners ||
    isDraggable ||
    isContentEditable

近期有个 PR 合进 master,新增了一个方法判断元素是否可交互,通过判断 element 的 cursor 是不是可交互属性,例如 pointer、cell、grab 等等,提高了判断元素可交互的准确性,思想还是蛮有意思的。

3.2 元素编号与描述

  • 可交互元素会被分配唯一的编号,会将元素描述和编号一起发给 LLM

  • 结构: [索引]<标签名 属性值1;属性值2>文本内容/>

    css 复制代码
    [1]<button 登录按钮;btn-primary>登录/>
    [2]<input 请输入用户名;text;username/>

3.3 编号到元素定位的机制

当 LLM 返回 action: [{click_element: {index: 1}}] 的时候,是怎么找到对应元素的?

  1. 元素映射机制

    Browser use 维护一个 selector_map , 将元素索引映射到元素信息

    ini 复制代码
    SelectorMap = dict[int, DOMElementNode]
  2. 元素定位过程

    当要点击索引为 1 的元素的时候,会执行以下步骤:

    python 复制代码
    @self.registry.action('Click element', param_model=ClickElementAction)
    	def click_element():
    		# 1. 获取 index 对应的 element node
    		element_node = await browser.get_dom_element_by_index(params.index)
    		# 2. 点击元素
    		await browser._click_element_node(element_node)
    		
  3. _click_element_node 过程中,会优先看 element_node 能不能解析出 css_selector, 如果不可以,再使用 xpath (xptah 是构建 dom 树的时候生成)。

3.4 视觉辅助

当启动 use_vision 时,Agent 会将当前页面截图一并发给 LLM。

4. Agent 与 LLM 交互机制

Agent 会向 LLM 发送消息,获取下一步的操作。其中发送的消息结构、返回的格式如下

4.1 消息格式

Agent 向 LLM 发送的消息包含:

  • 当前 URL 和页面标题
  • 打开的标签页信息
  • 可交互元素列表(带索引号)
  • 页面截图(如果启用视觉功能)
  • 滚动信息(页面上下方内容)
  • 上一步操作的结果

4.2 LLM 返回格式

LLM 返回结构化的 json 对象

json 复制代码
{
    "current_state": {
        "evaluation_previous_goal": "成功导航到谷歌首页",
        "memory": "需要搜索最新的AI新闻",
        "next_goal": "在搜索框中输入查询内容"
    },
    "action": [
        {
            "input_text": {"index": 1, "text": "最新人工智能新闻"}
        },
        {
            "click_element": {"index": 2}
        }
    ]
}

4.3 规范 LLM 输出的方法

Browser use 通过以下机制确保 LLM 输出的一致性

  1. System prompt

    • 在初始化的时候,会通过自然语言知道 LLM 返回特定格式
  2. with_structured_output

    • 使用 langchain 中间层机制,在提示中添加额外指导,解析 llm 响应

5. 页面状态变化检测

在执行完 action 之后,需要检测页面的状态变化,如果发生了变化,需要中断原来规划的 action,重新进行规划。

5.1 检测流程

graph TD A[执行操作前] --> B[缓存当前DOM元素哈希] B --> C[执行操作] C --> D[获取新的DOM元素哈希] D --> E{比较哈希值} E -->|有变化| F[中断操作序列] E -->|无变化| G[继续执行] F --> H[重新规划] G --> I[执行下一操作]

5.2 哈希计算机制

Browser use 使用路径哈希值来追踪 DOM 元素状态

  1. 分支路径哈希

    • 分支路径哈希是通过从当前元素回溯到根节点(如 <html>)的路径信息生成的一个唯一标识符。它反映了元素在DOM树中的层级位置。

    • 例子

      html 复制代码
      <!-- DOM结构 -->
      <html>
        <body>
          <div class="container">
            <p>Text</p>      <!-- 路径:html/body/div[0]/p[0] -->
            <button>Click</button>  <!-- 路径:html/body/div[0]/button[0] -->
          </div>
        </body>
      </html>

      <p> 的分支路径哈希可能为 hash("html/body/div[0]/p[0]")。

  2. 代码实现

    python 复制代码
    # 在执行操作前缓存元素哈希
    cached_selector_map = await self.browser_context.get_selector_map()
    cached_path_hashes = set(e.hash.branch_path_hash for e in cached_selector_map.values())
    
    # 执行操作
    for i, action in enumerate(actions):
        # 如果操作需要元素索引且不是第一个操作
        if action.get_index() is not None and i != 0:
            # 获取当前页面状态
            new_state = await self.browser_context.get_state()
            new_path_hashes = set(e.hash.branch_path_hash for e in new_state.selector_map.values())
            
            # 检查是否有新元素出现
            if check_for_new_elements and not new_path_hashes.issubset(cached_path_hashes):
                # 页面发生变化,中断操作序列
                msg = f'Something new appeared after action {i} / {len(actions)}'
                logger.info(msg)
                results.append(ActionResult(extracted_content=msg, include_in_memory=True))
                break
        
        # 执行当前操作
        result = await self.controller.act(action, self.browser_context, ...)
        results.append(result)

5.3 应用场景

  1. 表单交互反馈

    • 输入验证消息
    • 错误提示
  2. UI 状态变化

    • 下拉菜单展开/折叠
    • modal 打开/关闭

6. 高级功能:记忆机制与任务规划

6.1 记忆机制

记忆机制使AI能够记住之前的操作、观察到的信息和任务目标,保持任务的连贯性。在 Browser use 中,记忆机制主要通过以下方式实现。

  1. 状态:每次 LLM 返回的 current_state.memory 包含要记住的内容, 是 LLM 生成的,意味着记忆的内容由 LLM 的理解和判断决定的。

    json 复制代码
    {
      "current_state": {
        "memory": "已登录账号user123,正在查找iPhone产品,需要选择黑色款式",
        "next_goal": "点击黑色选项按钮"
      }
    }
  2. 历史操作记录:Browser use 会保存所有操作的历史记录, 在分析下次 action 的时候会传给 llm。

6.2 动态规划模式

Browser use 采用动态规划模式,意味着不会在一开始就生成所有步骤和操作,而是逐步规划的方式。

graph TD A[观察当前状态] --> B[思考下一步] B --> C[执行操作] C --> D[观察新状态] D --> B

每次 Agent 执行一个步骤的时候,会向 LLM 发送当前状态,请求 LLM 规划下一步操作:

python 复制代码
async def step(self, step_info: Optional[AgentStepInfo] = None) -> None:
    """执行一个步骤"""
    # 1. 获取当前浏览器状态
    state = await self.browser_context.get_state()
    
    # 2. 添加状态消息到历史
    self._message_manager.add_state_message(state, self.state.last_result, step_info)
    
    # 3. 获取所有消息历史
    input_messages = self._message_manager.get_messages()
    
    # 4. 向 LLM 请求下一步操作
    model_output = await self.get_next_action(input_messages)
    
    # 5. 执行 LLM 规划的操作
    result = await self.multi_act(model_output.action)
    
    # 6. 更新状态
    self.state.last_result = result

LLM 可以在一个操作中,规划多个连续的操作,例如:

json 复制代码
# LLM 返回的操作序列示例
{
  "current_state": {
    "evaluation_previous_goal": "成功导航到登录页面",
    "memory": "需要登录账号",
    "next_goal": "填写登录表单并提交"
  },
  "action": [
    {"input_text": {"index": 2, "text": "username123"}},
    {"input_text": {"index": 3, "text": "password123"}},
    {"click_element": {"index": 1}}
  ]
}

这些 action 会按顺序执行,当执行一个 action 之后,就会判断页面是否发生变化,如果发生变化,将会终止计划。

6.3 高级规划器(Planner)

Planner 是 Browser use 的高级功能,可以从全局视角审视任务,帮助 AI 更有效的完成复杂任务。

Planner 更偏向与顾问的角色, 而非执行者的角色,通过输出消息历史的方式,影响执行 LLM。

6.4.1 使用方法

python 复制代码
from langchain_openai import ChatOpenAI
from browser_use import Agent

# 创建主执行 LLM
main_llm = ChatOpenAI(model="gpt-4o")

# 创建规划器 LLM(可以使用不同模型)
planner_llm = ChatOpenAI(model="gpt-4o")

# 创建 Agent 并配置规划器
agent = Agent(
    task="在亚马逊上搜索并购买最新的iPhone",
    llm=main_llm,
    planner_llm=planner_llm,  # 设置规划器 LLM
    planner_interval=5,       # 每5步触发一次规划
    use_vision=True
)

# 运行 Agent
await agent.run()

6.4.2 触发机制

高级规划器不是每步都会执行,而是特定时机触发,如果 planner_interval 设为 5,那么执行 5 个步骤后会触发高级规划。

6.4.3 规划过程

python 复制代码
async def _run_planner(self) -> Optional[str]:
    """运行规划器分析状态并建议下一步"""
    # 如果没有设置规划器,跳过
    if not self.settings.planner_llm:
        return None
    
    # 创建规划器消息历史
    planner_messages = [
        PlannerPrompt(self.controller.registry.get_prompt_description()).get_system_message(),
        *self._message_manager.get_messages()[1:],  # 使用完整消息历史
    ]
    
    # 获取规划器输出
    response = await self.settings.planner_llm.ainvoke(planner_messages)
    plan = str(response.content)
    
    # 处理输出(可能包括移除思考标签等)
    if self.planner_model_name and ('deepseek-r1' in self.planner_model_name or 'deepseek-reasoner' in self.planner_model_name):
        plan = self._remove_think_tags(plan)
    
    # 尝试解析为 JSON 并记录
    try:
        plan_json = json.loads(plan)
        logger.info(f'Planning Analysis:\n{json.dumps(plan_json, indent=4)}')
    except json.JSONDecodeError:
        logger.info(f'Planning Analysis:\n{plan}')
    except Exception as e:
        logger.debug(f'Error parsing planning analysis: {e}')
        logger.info(f'Plan: {plan}')
    
    return plan

6.4.4 Planner 输出示例

swift 复制代码
"
{\"state_analysis\":\"用户已成功登录电商网站并导航到产品搜索页面。已搜索'iPhone 13'并查看了前两个搜索结果。目前正在查看第二个产品的详细信息页面。\",
\"progress_evaluation\":\"任务完成度约40%。已完成登录和产品搜索,但尚未选择具体型号、添加到购物车和完成结账流程。\",
\"challenges\":[\"产品可能有多种颜色和存储选项需要选择\",\"可能需要处理库存不足的情况\",\"结账过程可能需要填写多个表单和确认步骤\"],
\"next_steps\":[\"选择合适的iPhone 13型号和配置(颜色、存储容量)\",\"检查价格和库存情况\",\"将选定产品添加到购物车\"],
\"reasoning\":\"在购买流程中,选择具体产品配置是下一个逻辑步骤。需要先确定具体型号和配置,然后检查库存和价格,再进行添加到购物车操作。这样可以确保用户选择了合适的产品,并且该产品确实可以购买。\"}
"

6.4.5 工作流程图

sequenceDiagram participant User participant Agent participant Planner participant LLM participant Browser User->>Agent: 提供任务 Agent->>Browser: 初始化浏览器 loop 执行循环 Agent->>Browser: 获取当前状态 alt 触发 Planner 条件 (每 N 步) Agent->>Planner: 发送完整历史和状态 Planner->>Agent: 返回分析和建议 Agent->>Agent: 存储规划结果 end Agent->>LLM: 发送状态、历史和规划建议 LLM->>Agent: 返回具体操作 Agent->>Browser: 执行操作 Browser->>Agent: 返回操作结果 Agent->>Agent: 更新状态和历史 end Agent->>User: 返回最终结果

总结

Browser-Use 使得 AI 能够像人类一样浏览和操作网页。它不仅能够识别和交互网页元素,还能理解页面状态变化,记住历史操作,并根据当前情况动态调整策略。

相关推荐
天官赐福_3 分钟前
vue2的scale方式适配大屏
前端·vue.js
江城开朗的豌豆3 分钟前
CSS篇:前端经典布局方案:左侧固定右侧自适应的6种实现方式
前端·css·面试
我儿长柏必定高中5 分钟前
Promise及使用场景
前端
无名友5 分钟前
HTML — 浮动
前端·css·html
0xJohnsoy7 分钟前
React中的this详解
前端
the_one7 分钟前
🚀「v-slide-in」+ 瀑布流实战指南:Vue 高级滑入动画一键实现,页面质感瞬间拉满!
前端·javascript·css
ZL不懂前端7 分钟前
微前端介绍
前端
Lear8 分钟前
uniapp&微信小程序markdown&latex
前端
江城开朗的豌豆8 分钟前
CSS篇:CSS选择器详解与权重计算全指南
前端·css·面试
asing9 分钟前
之家中后台前端解决方案 - 支点2.0
前端·javascript