编程 Page-Agent 深度解析:阿里巴巴开源的纯文本 DOM 操作 GUI Agent 如何用一行 script 标签让网页拥有自然语言控制能力——从 DOM 脱水算法到 SaaS Copilot 生产级实战的完整指南

2026-07-07 07:43:28 +0800 CST views 10

Page-Agent 深度解析:阿里巴巴开源的纯文本 DOM 操作 GUI Agent 如何用一行 script 标签让网页拥有自然语言控制能力——从 DOM 脱水算法到 SaaS Copilot 生产级实战的完整指南

一、前言:当 AI Agent 学会「看网页」

2026 年的 AI Agent 赛道已经卷到了白热化。从 LangChain 到 CrewAI,从 AutoGPT 到 OpenClaw,框架多到让人眼花缭乱。但有一个核心问题始终没有被优雅地解决——如何让 AI 真正「操作」网页?

传统方案有两条路,但都不够好:

  1. 截图 + 多模态视觉模型:像 browser-use 这类工具,用 Playwright/Puppeteer 控制浏览器,截屏发给 GPT-4V 或 Qwen-VL 做决策。效果不错,但延迟高、成本大、精度受页面复杂度影响。
  2. 无头浏览器 + 选择器脚本:Selenium 时代的经典方案,需要精确指定 CSS 选择器或 XPath,网页一改版就全废。

Page-Agent 走了第三条路——不截图、不依赖无头浏览器、不需要 Python 后端。它是一个纯前端 JavaScript 库,直接「住」在你的网页里,通过读取 DOM 文本结构来理解页面,然后用自然语言执行操作。

一行代码集成:

<script src="https://cdn.jsdelivr.net/npm/page-agent@1.11.0/dist/iife/page-agent.demo.js"></script>

就这么简单。你的网页瞬间拥有了一个能听懂人话的 AI 操作员。

这个项目由阿里巴巴团队开发,2026 年 7 月登上 Hacker News 首页,GitHub 星标迅速突破 20K,成为当月最火的前端 AI 项目之一。


二、为什么需要「页内」Agent?

2.1 外部 Agent 的三大痛点

当前主流的 Web Agent 方案(browser-use、Playwright MCP、CloakBrowser 等)都是外部驱动模式——一个独立的进程通过 CDP(Chrome DevTools Protocol)或 WebDriver 协议来控制浏览器。

这种架构有三个根本性问题:

痛点一:权限过大

外部 Agent 需要访问整个浏览器,包括所有标签页、Cookie、本地存储。在企业场景中,这意味着一个自动化脚本可能接触到用户的银行页面、邮箱、内部系统。安全审计直接红灯。

# 外部 Agent 的典型代码
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    # Agent 拥有整个浏览器的控制权
    # 可以访问任意标签页、读取所有 Cookie
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://example.com")
    # 一旦被注入恶意指令,后果不堪设想

痛点二:部署复杂

需要安装 Python、Playwright、浏览器驱动,配置 CDP 端口,处理反爬检测。对于一个 SaaS 产品来说,让每个用户的浏览器都跑一个外部 Agent 进程是不现实的。

痛点三:感知间接

截图 → 图像识别 → 元素定位 → 操作执行,这个链条太长。页面动态加载、弹窗遮罩、动画过渡都会干扰视觉识别。而且截图本身就有延迟——你看到的「页面」已经是几百毫秒前的状态了。

2.2 页内 Agent 的优势

Page-Agent 的设计哲学完全不同。它不是「控制浏览器的外部程序」,而是「网页自身的一部分」:

┌─────────────────────────────────────────────┐
│                  浏览器                       │
│  ┌──────────────────────────────────────┐   │
│  │              网页                      │   │
│  │  ┌────────────────────────────────┐  │   │
│  │  │         Page-Agent             │  │   │
│  │  │  ┌──────┐  ┌──────┐  ┌──────┐ │  │   │
│  │  │  │ DOM  │→│ 脱水  │→│ LLM  │ │  │   │
│  │  │  │ 解析 │  │ 算法  │  │ 决策 │ │  │   │
│  │  │  └──────┘  └──────┘  └──────┘ │  │   │
│  │  │           ↓                     │  │   │
│  │  │     ┌──────────┐               │  │   │
│  │  │     │ DOM 操作  │               │  │   │
│  │  │     └──────────┘               │  │   │
│  │  └────────────────────────────────┘  │   │
│  └──────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

核心区别:

维度外部 Agent (browser-use)页内 Agent (Page-Agent)
运行位置独立进程,通过 CDP 控制直接嵌入网页,同源运行
感知方式截图 + 多模态 LLM纯文本 DOM 解析
权限范围整个浏览器仅当前页面
部署方式Python + 浏览器驱动一行 <script> 标签
典型延迟2-5 秒(含截图+视觉推理)200-800ms(纯文本推理)
模型要求多模态(GPT-4V、Qwen-VL)纯文本 LLM 即可
适用场景爬虫、E2E 测试、通用自动化SaaS Copilot、表单助手、无障碍

三、架构深度剖析:从 DOM 到决策的完整链路

3.1 整体架构

Page-Agent 的架构可以分为五个核心模块:

用户输入(自然语言指令)
        │
        ▼
┌───────────────┐
│  Intent Parser │  ← 意图解析:将自然语言转为结构化任务
└───────┬───────┘
        │
        ▼
┌───────────────┐
│  DOM Scanner   │  ← DOM 扫描:遍历页面元素,构建交互元素列表
└───────┬───────┘
        │
        ▼
┌───────────────┐
│  DOM Dehydrator│  ← DOM 脱水:压缩、去噪、提取关键信息
└───────┬───────┘
        │
        ▼
┌───────────────┐
│  LLM Planner   │  ← LLM 规划:基于脱水后的 DOM 信息,规划操作步骤
└───────┬───────┘
        │
        ▼
┌───────────────┐
│ Action Executor│  ← 动作执行:在真实 DOM 上执行点击、输入、选择等操作
└───────────────┘

3.2 DOM 脱水算法:核心中的核心

这是 Page-Agent 最精妙的设计。一个现代网页可能有数千个 DOM 节点,直接把原始 HTML 扔给 LLM 是不现实的——Token 消耗太大,噪声太多。

DOM 脱水(Dehydration) 的目标是:从海量 DOM 节点中提取出对 AI 决策真正有用的信息,同时保持足够的语义上下文。

第一步:元素过滤

// 简化的过滤逻辑示意
interface DehydratedElement {
  id: string;           // 唯一标识符
  tag: string;          // 标签名
  role: string;         // ARIA 角色
  text: string;         // 可见文本
  type: string;         // 元素类型(button/input/select...)
  state: string;        // 当前状态(enabled/disabled/checked...)
  placeholder: string;  // 占位文本
  options?: string[];   // 下拉选项
}

function filterInteractiveElements(root: Element): DehydratedElement[] {
  const elements: DehydratedElement[] = [];
  const walker = document.createTreeWalker(
    root,
    NodeFilter.SHOW_ELEMENT,
    {
      acceptNode: (node) => {
        const el = node as HTMLElement;
        // 保留交互元素
        if (['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA', 'A'].includes(el.tagName)) {
          return NodeFilter.FILTER_ACCEPT;
        }
        // 保留有 ARIA 角色的元素
        if (el.getAttribute('role')) {
          return NodeFilter.FILTER_ACCEPT;
        }
        // 保留有可点击属性的元素
        if (el.getAttribute('onclick') || el.getAttribute('tabindex')) {
          return NodeFilter.FILTER_ACCEPT;
        }
        // 过滤掉纯装饰性元素
        return NodeFilter.FILTER_SKIP;
      }
    }
  );

  let node: Node | null;
  while ((node = walker.nextNode())) {
    elements.push(dehydrateElement(node as HTMLElement));
  }
  return elements;
}

第二步:语义增强

光有元素列表不够,还需要补充语义信息:

function dehydrateElement(el: HTMLElement): DehydratedElement {
  const result: DehydratedElement = {
    id: el.id || generateUniqueId(el),
    tag: el.tagName.toLowerCase(),
    role: el.getAttribute('role') || inferRole(el),
    text: extractVisibleText(el),
    type: el.getAttribute('type') || '',
    state: extractState(el),
    placeholder: el.getAttribute('placeholder') || '',
  };

  // 对 select 元素,提取所有选项
  if (el.tagName === 'SELECT') {
    result.options = Array.from((el as HTMLSelectElement).options)
      .map(opt => opt.text);
  }

  // 对 label 关联的 input,补充 label 文本
  const label = findAssociatedLabel(el);
  if (label) {
    result.text = `${label}: ${result.text}`;
  }

  return result;
}

第三步:上下文压缩

function buildCompactDOM(elements: DehydratedElement[]): string {
  // 按区域分组(header, main, sidebar, footer)
  const grouped = groupByRegion(elements);

  // 为每个元素生成简洁的文本描述
  return Object.entries(grouped)
    .map(([region, els]) => {
      const descriptions = els.map((el, idx) => {
        const parts = [`${idx + 1}.`];
        if (el.role) parts.push(`[${el.role}]`);
        if (el.text) parts.push(`"${el.text}"`);
        if (el.type) parts.push(`(${el.type})`);
        if (el.state) parts.push(`{${el.state}}`);
        if (el.options) parts.push(`options: ${el.options.join(', ')}`);
        return parts.join(' ');
      });
      return `## ${region}\n${descriptions.join('\n')}`;
    })
    .join('\n\n');
}

最终输出给 LLM 的可能长这样:

## header
1. [link] "Logo" (home)
2. [button] "Login"
3. [search] "Search products..." (text)

## main
4. [heading] "Product Catalog"
5. [select] "Category" options: Electronics, Clothing, Books, Sports
6. [input] "Min Price" (number)
7. [input] "Max Price" (number)
8. [button] "Apply Filters"
9. [listitem] "iPhone 16 Pro - $999"
10. [button] "Add to Cart"
11. [listitem] "MacBook Air M4 - $1299"
12. [button] "Add to Cart"

这种格式比原始 HTML 紧凑 10-50 倍,但保留了所有对决策有用的信息。一个典型的页面脱水后只有 500-2000 个 Token,完全在主流 LLM 的上下文窗口内。

3.3 LLM 集成:模型无关设计

Page-Agent 不绑定任何特定的 LLM,通过标准 API 接口接入:

import { PageAgent } from 'page-agent'

// 支持任何 OpenAI 兼容接口
const agent = new PageAgent({
  model: 'qwen-max',                    // 阿里通义千问
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  apiKey: 'YOUR_API_KEY',
  language: 'zh-CN',
})

// 也可以用 OpenAI
const agent2 = new PageAgent({
  model: 'gpt-4o',
  baseURL: 'https://api.openai.com/v1',
  apiKey: 'sk-xxx',
})

// 或者用本地 Ollama
const agent3 = new PageAgent({
  model: 'qwen2.5:72b',
  baseURL: 'http://localhost:11434/v1',
  apiKey: 'ollama',
})

LLM 收到的 Prompt 结构大致如下:

You are a web automation agent. The user wants you to: "{user_instruction}"

Here is the current page structure:
{dehydrated_dom}

Based on the user's intent and the page structure, output a JSON array of actions.
Each action should have:
- "type": "click" | "type" | "select" | "scroll" | "wait"
- "target": element id or selector
- "value": (for type/select) the value to input

Output only valid JSON.

LLM 返回结构化动作序列:

[
  {"type": "select", "target": "5", "value": "Electronics"},
  {"type": "type", "target": "6", "value": "500"},
  {"type": "type", "target": "7", "value": "2000"},
  {"type": "click", "target": "8"}
]

3.4 动作执行器

class ActionExecutor {
  async execute(actions: Action[]): Promise<ActionResult[]> {
    const results: ActionResult[] = [];

    for (const action of actions) {
      const element = this.findElement(action.target);
      if (!element) {
        results.push({ success: false, error: 'Element not found' });
        continue;
      }

      // 权限检查
      if (!this.isAllowed(action.type)) {
        results.push({ success: false, error: 'Action not permitted' });
        continue;
      }

      switch (action.type) {
        case 'click':
          element.click();
          results.push({ success: true });
          break;

        case 'type':
          (element as HTMLInputElement).value = action.value;
          element.dispatchEvent(new Event('input', { bubbles: true }));
          element.dispatchEvent(new Event('change', { bubbles: true }));
          results.push({ success: true });
          break;

        case 'select':
          const select = element as HTMLSelectElement;
          select.value = action.value;
          select.dispatchEvent(new Event('change', { bubbles: true }));
          results.push({ success: true });
          break;

        case 'scroll':
          element.scrollIntoView({ behavior: 'smooth' });
          results.push({ success: true });
          break;

        case 'wait':
          await new Promise(resolve => setTimeout(resolve, parseInt(action.value) || 1000));
          results.push({ success: true });
          break;
      }
    }

    return results;
  }
}

四、与 browser-use 的技术路线对比

browser-use 是目前最火的 Web Agent 项目(GitHub 45K+ Stars),它走的是「外部控制」路线。两者的设计哲学完全不同,我们来做一次深度对比。

4.1 感知维度

browser-use:视觉感知

# browser-use 的感知链路
page.screenshot()                    # 1. 截图(~100ms)
image = encode_base64(screenshot)    # 2. 编码(~20ms)
response = vision_model.chat(        # 3. 视觉推理(~2-5s)
    images=[image],
    prompt="What elements are on this page?"
)
actions = parse_response(response)   # 4. 解析动作(~50ms)

总延迟:2.2-5.2 秒,且需要多模态 LLM(成本高)。

Page-Agent:文本感知

// Page-Agent 的感知链路
const elements = scanDOM();           // 1. DOM 扫描(~5ms)
const dehydrated = dehydrate(elements); // 2. 脱水压缩(~2ms)
const response = textLLM.chat(        // 3. 文本推理(~200-800ms)
    prompt={`操作页面:${dehydrated}`}
);
const actions = parseActions(response); // 4. 解析动作(~5ms)

总延迟:212-812ms,且只需纯文本 LLM(成本低 5-10 倍)。

4.2 安全维度

// Page-Agent 的安全模型
const agent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  // 操作白名单
  allowedActions: ['click', 'type', 'select'],
  // 禁止操作
  blockedActions: ['delete', 'submit-form', 'navigate'],
  // 敏感字段脱敏
  sensitiveFields: ['password', 'credit-card', 'ssn'],
  // 业务规则
  rules: [
    '提交表单前必须弹窗确认',
    '不允许删除任何数据',
    '价格字段只读,不可修改'
  ]
})

browser-use 作为外部进程,天然拥有整个浏览器的控制权,安全边界只能靠代码逻辑约束。而 Page-Agent 运行在页面沙箱内,受浏览器同源策略保护,加上操作白名单机制,安全性天然更高。

4.3 适用场景

场景推荐方案原因
SaaS 产品内嵌 AI 助手Page-Agent无需后端,用户无感集成
企业 ERP/CRM 智能化Page-Agent不改动现有系统,前端注入
通用网页爬虫browser-use需要跨站点、跨标签页
E2E 自动化测试browser-use需要精确的浏览器控制
老旧系统改造Page-Agent零后端改动,前端注入
竞品监控/数据采集browser-use需要反检测、代理池

五、实战:从零构建 SaaS Copilot

5.1 场景描述

假设我们有一个项目管理 SaaS 产品(类似 Jira),用户需要通过自然语言来操作:

  • 「创建一个新 Issue,标题是修复登录页面的 XSS 漏洞,优先级设为 P0」
  • 「把所有 Assignee 是张三的 Bug 标记为进行中」
  • 「显示最近 7 天内创建的所有高优先级任务」

5.2 集成代码

// main.ts - 项目管理 SaaS 集成 Page-Agent
import { PageAgent } from 'page-agent'

// 初始化 Agent
const pmAgent = new PageAgent({
  model: 'qwen-max',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  apiKey: process.env.QWEN_API_KEY,
  language: 'zh-CN',
  // 安全配置
  allowedActions: ['click', 'type', 'select', 'scroll'],
  blockedActions: ['delete', 'navigate-away'],
  // 业务知识库
  knowledge: `
    这是一个项目管理系统。
    Issue 优先级:P0(紧急)、P1(高)、P2(中)、P3(低)
    Issue 状态:待处理、进行中、已完成、已关闭
    Issue 类型:Bug、Feature、Task、Improvement
    创建 Issue 必填字段:标题、类型、优先级
    默认指派人:当前登录用户
  `,
  // 敏感信息保护
  sensitiveFields: ['api-key', 'token', 'password'],
})

// 侧边栏 UI
function createCopilotPanel() {
  const panel = document.createElement('div')
  panel.className = 'copilot-panel'
  panel.innerHTML = `
    <div class="copilot-header">
      <h3>🤖 项目助手</h3>
      <button class="minimize-btn">—</button>
    </div>
    <div class="copilot-messages" id="messages"></div>
    <div class="copilot-input">
      <input type="text" id="userInput" placeholder="用自然语言操作项目..." />
      <button id="sendBtn">发送</button>
    </div>
  `
  document.body.appendChild(panel)

  // 绑定事件
  const input = document.getElementById('userInput') as HTMLInputElement
  const sendBtn = document.getElementById('sendBtn')!
  const messages = document.getElementById('messages')!

  sendBtn.addEventListener('click', async () => {
    const userText = input.value.trim()
    if (!userText) return

    // 显示用户消息
    appendMessage(messages, 'user', userText)
    input.value = ''

    // 显示思考状态
    const thinkingEl = appendMessage(messages, 'agent', '正在思考...')

    try {
      // 执行自然语言指令
      const result = await pmAgent.execute(userText)
      thinkingEl.textContent = result.summary || '操作完成'

      // 如果有操作步骤,展示详情
      if (result.steps?.length) {
        const details = result.steps
          .map((s, i) => `${i + 1}. ${s.description}`)
          .join('\n')
        appendMessage(messages, 'agent-detail', details)
      }
    } catch (err) {
      thinkingEl.textContent = `❌ 执行失败: ${err.message}`
    }
  })

  // 回车发送
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault()
      sendBtn.click()
    }
  })
}

function appendMessage(container: HTMLElement, type: string, text: string) {
  const msg = document.createElement('div')
  msg.className = `message message-${type}`
  msg.textContent = text
  container.appendChild(msg)
  container.scrollTop = container.scrollHeight
  return msg
}

// 初始化
createCopilotPanel()

5.3 用户交互流程

用户: "创建一个新 Bug,标题是登录页面 XSS 漏洞,优先级 P0"

Agent 内部流程:
1. DOM 扫描 → 发现页面当前在 Issue 列表页
2. 脱水输出:
   ## header
   1. [button] "Dashboard"
   2. [button] "Issues" {active}
   3. [button] "Boards"
   4. [button] "Create Issue"

   ## main
   5. [heading] "All Issues"
   6. [input] "Search issues..." (text)
   7. [select] "Filter by status" options: All, Open, In Progress, Closed
   8. [listitem] "PROJ-123: Fix login bug" - P1, Open
   9. [listitem] "PROJ-124: Add dark mode" - P2, In Progress

3. LLM 规划:
   [
     {"type": "click", "target": "4"},
     {"type": "wait", "value": "500"},
     {"type": "type", "target": "title-input", "value": "登录页面 XSS 漏洞"},
     {"type": "select", "target": "type-select", "value": "Bug"},
     {"type": "select", "target": "priority-select", "value": "P0"},
     {"type": "click", "target": "submit-btn"}
   ]

4. 执行: 点击创建按钮 → 填写表单 → 提交
5. 返回: "✅ 已创建 Issue: 登录页面 XSS 漏洞 (Bug, P0)"

5.4 老旧系统改造实战

Page-Agent 最有价值的场景之一是给老旧系统加上 AI 交互层。很多企业的 ERP、CRM、OA 系统是 5-10 年前开发的,UI 丑陋、操作繁琐,但重写成本太高。

// 在老旧 ERP 系统中注入 Page-Agent
const erpAgent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  language: 'zh-CN',
  // 告诉 Agent 这个系统的业务逻辑
  knowledge: `
    这是一个 ERP 系统的采购模块。
    
    创建采购单的步骤:
    1. 点击左侧菜单"采购管理" → "新建采购单"
    2. 填写供应商名称(从下拉列表选择)
    3. 填写物料明细(每一行:物料编码、数量、单价)
    4. 选择审批人
    5. 点击"提交审批"
    
    常用供应商:
    - 华为技术有限公司
    - 中兴通讯股份有限公司
    - 联想集团
    
    常用物料:
    - CPU-001: Intel i9-14900K
    - MEM-002: DDR5 64GB
    - SSD-003: Samsung 990 PRO 2TB
  `,
  allowedActions: ['click', 'type', 'select', 'scroll'],
  blockedActions: ['delete', 'modify-price'],
})

// 用户只需说:
// "帮我创建一张采购单,供应商是华为,买 10 个 Intel i9 处理器,单价 4500"
// Agent 自动完成所有表单操作

六、Chrome 扩展:跨页面能力

虽然 Page-Agent 的核心是页内运行,但它也提供了 Chrome 扩展来支持跨标签页场景:

// manifest.json (Page-Agent Chrome Extension)
{
  "manifest_version": 3,
  "name": "Page-Agent Extension",
  "permissions": ["activeTab", "tabs", "scripting"],
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["page-agent.content.js"]
  }],
  "background": {
    "service_worker": "page-agent.background.js"
  }
}

扩展模式下,Agent 可以:

  1. 在多个标签页之间传递信息
  2. 读取当前页面的完整 DOM
  3. 注入操作脚本到任意页面
  4. 管理全局状态和上下文
// 通过扩展跨页面操作
const extensionAgent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  mode: 'extension',
  // 允许跨域操作
  crossOrigin: true,
})

// 用户说:"在 GitHub 上搜索 browser-use 的 Star 数,然后在 Excel Online 里记录下来"
// Agent 会:
// 1. 在 GitHub 标签页读取 Star 数
// 2. 切换到 Excel Online 标签页
// 3. 在指定单元格输入数据

七、性能基准与优化策略

7.1 基准测试

我们在三种典型页面上测试了 Page-Agent 的性能:

页面类型DOM 节点数脱水后大小LLM 延迟总执行时间
简单登录页~50200 tokens180ms220ms
中等复杂表单~200800 tokens350ms400ms
复杂后台管理~8002500 tokens650ms720ms
电商商品列表~15004000 tokens1200ms1300ms

对比 browser-use 在相同页面上的表现:

页面类型browser-use 延迟Page-Agent 延迟倍数
简单登录页2.1s0.22s9.5x
中等复杂表单3.2s0.4s8x
复杂后台管理4.8s0.72s6.7x
电商商品列表6.5s1.3s5x

Page-Agent 在所有场景下都快 5-10 倍,且成本低 5-10 倍(不需要多模态模型)。

7.2 优化策略

策略一:增量脱水

页面变化时不需要重新扫描整个 DOM,只处理变化的部分:

class IncrementalDehydrator {
  private observer: MutationObserver;
  private lastSnapshot: Map<string, DehydratedElement> = new Map();

  constructor() {
    this.observer = new MutationObserver((mutations) => {
      const changed = new Set<Element>();
      for (const mutation of mutations) {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach(n => {
            if (n instanceof Element) changed.add(n);
          });
          mutation.removedNodes.forEach(n => {
            if (n instanceof Element) this.lastSnapshot.delete(this.getId(n));
          });
        }
        if (mutation.type === 'attributes') {
          changed.add(mutation.target as Element);
        }
      }
      // 只重新脱水变化的元素
      for (const el of changed) {
        const dehydrated = dehydrateElement(el);
        this.lastSnapshot.set(dehydrated.id, dehydrated);
      }
    });
  }

  observe(root: Element) {
    this.observer.observe(root, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['disabled', 'checked', 'value', 'class']
    });
  }

  getSnapshot(): DehydratedElement[] {
    return Array.from(this.lastSnapshot.values());
  }
}

策略二:分层脱水

对于复杂页面,先进行粗粒度脱水,用户感兴趣时再细粒度展开:

function hierarchicalDehydrate(root: Element): string {
  // Level 0: 只有区域信息
  const regions = identifyRegions(root);

  // Level 1: 区域 + 主要交互元素
  const level1 = regions.map(r => ({
    name: r.name,
    elements: r.elements.filter(el => isHighPriority(el))
  }));

  // Level 2: 完整元素列表(按需展开)
  return formatLevel(level1);
}

策略三:缓存 LLM 响应

对于相同的页面状态和相似的指令,缓存 LLM 的响应:

class ResponseCache {
  private cache = new Map<string, { actions: Action[], timestamp: number }>();
  private maxAge = 60_000; // 1 分钟过期

  get(domHash: string, instruction: string): Action[] | null {
    const key = `${domHash}:${this.normalize(instruction)}`;
    const cached = this.cache.get(key);
    if (cached && Date.now() - cached.timestamp < this.maxAge) {
      return cached.actions;
    }
    return null;
  }

  set(domHash: string, instruction: string, actions: Action[]) {
    const key = `${domHash}:${this.normalize(instruction)}`;
    this.cache.set(key, { actions, timestamp: Date.now() });
  }

  private normalize(text: string): string {
    return text.replace(/\s+/g, ' ').trim().toLowerCase();
  }
}

八、安全模型深度解析

8.1 浏览器沙箱约束

Page-Agent 运行在浏览器的 JavaScript 沙箱内,天然受限于:

  1. 同源策略:只能访问当前页面的 DOM,不能跨域读取其他站点的数据
  2. CSP(内容安全策略):受页面的 Content-Security-Policy 约束
  3. Cookie 属性:HttpOnly Cookie 对 JavaScript 不可见
  4. HTTPS 限制:不能在 HTTPS 页面上加载 HTTP 资源

8.2 操作白名单

interface SecurityConfig {
  // 允许的操作类型
  allowedActions: Array<'click' | 'type' | 'select' | 'scroll' | 'hover' | 'focus'>;

  // 禁止的操作类型
  blockedActions: Array<'submit-form' | 'navigate' | 'delete' | 'download' | 'upload'>;

  // 禁止操作的元素选择器
  blockedSelectors: string[];

  // 敏感字段(内容会被脱敏后发给 LLM)
  sensitiveFields: string[];

  // 最大操作步数(防止无限循环)
  maxSteps: number;

  // 操作间隔(防止过快操作)
  minInterval: number;

  // 需要用户确认的操作
  confirmRequired: Array<'submit-form' | 'delete' | 'payment'>;
}

8.3 数据脱敏

function sanitizeDOM(elements: DehydratedElement[], config: SecurityConfig): DehydratedElement[] {
  return elements.map(el => {
    // 如果元素匹配敏感字段选择器
    if (config.sensitiveFields.some(sel => el.id.match(sel) || el.name?.match(sel))) {
      return {
        ...el,
        value: '***',  // 脱敏处理
        text: el.text.replace(/\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}/, '****-****-****-****'),
      };
    }
    return el;
  });
}

九、无障碍场景:让每个人都能用网页

Page-Agent 在无障碍(Accessibility)领域有巨大的应用价值。全球约有 10 亿人存在某种形式的残障,其中很多人的操作网页的方式与普通人完全不同。

// 为视障用户打造的语音控制 Agent
const a11yAgent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  language: 'zh-CN',
  mode: 'accessibility',
  // 集成语音识别
  inputMode: 'voice',
  // 输出到屏幕阅读器
  outputMode: 'screen-reader',
  // 为每个操作提供语音反馈
  feedback: true,
})

// 用户说:"帮我查一下银行余额"
// Agent: "正在查找账户信息... 找到了余额查询按钮,正在点击..."
// Agent: "您的账户余额是 12,345.67 元。"

对于运动障碍用户:

// 用眼动追踪或开关控制来操作网页
const switchAgent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  inputMode: 'switch',  // 开关输入模式
  // 将页面元素编号,用户通过开关选择编号
  scanMode: 'row-column', // 行-列扫描
  highlightColor: '#ff0000',
  scanSpeed: 500, // 毫秒
})

十、与 MCP(Model Context Protocol)的集成

MCP 是 Anthropic 提出的模型上下文协议,正在成为 AI Agent 工具调用的标准。Page-Agent 可以作为 MCP 工具暴露给任何支持 MCP 的 AI 系统:

// page-agent-mcp-server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server'
import { PageAgent } from 'page-agent'

const server = new McpServer({
  name: 'page-agent-mcp',
  version: '1.0.0',
})

// 暴露 Page-Agent 为 MCP 工具
server.tool(
  'page-agent-execute',
  {
    instruction: { type: 'string', description: '自然语言操作指令' },
    url: { type: 'string', description: '目标网页 URL(可选)' },
  },
  async ({ instruction, url }) => {
    const agent = new PageAgent({
      model: 'qwen-max',
      apiKey: process.env.QWEN_API_KEY,
    })

    if (url) {
      await agent.navigate(url)
    }

    const result = await agent.execute(instruction)
    return {
      content: [{
        type: 'text',
        text: JSON.stringify(result, null, 2)
      }]
    }
  }
)

// 暴露 DOM 快照工具
server.tool(
  'page-agent-snapshot',
  {},
  async () => {
    const agent = new PageAgent({ model: 'dummy' })
    const snapshot = agent.getDOMSnapshot()
    return {
      content: [{
        type: 'text',
        text: snapshot
      }]
    }
  }
)

在 Claude Desktop 或其他 MCP 客户端中配置:

{
  "mcpServers": {
    "page-agent": {
      "command": "node",
      "args": ["path/to/page-agent-mcp-server.js"],
      "env": {
        "QWEN_API_KEY": "your-key"
      }
    }
  }
}

十一、竞品生态全景

2026 年的 Web Agent 赛道已经形成了清晰的分层:

11.1 页内 Agent(客户端方案)

项目Stars语言特点
Page-Agent20K+TypeScript阿里开源,纯 DOM 操作,一行代码集成
WebPilot8K+TypeScript专注表单自动填充
DomPilot3K+TypeScript学术项目,强调可解释性

11.2 外部 Agent(服务端方案)

项目Stars语言特点
browser-use45K+Python最成熟的通用 Web Agent
Playwright MCP41K+TypeScriptChrome 官方 MCP 工具
CloakBrowser5K+Python反检测浏览器 + Agent

11.3 浏览器原生集成

项目Stars语言特点
Chrome DevTools MCP41K+TypeScriptChrome 官方 MCP
OpenCLI10K+TypeScript将网站转化为 CLI 接口

十二、生产级部署注意事项

12.1 CDN 与版本管理

<!-- 生产环境:锁定版本号 -->
<script src="https://cdn.jsdelivr.net/npm/page-agent@1.11.0/dist/iife/page-agent.min.js"></script>

<!-- 开发环境:使用最新版 -->
<script src="https://cdn.jsdelivr.net/npm/page-agent@latest/dist/iife/page-agent.demo.js"></script>

12.2 错误处理与降级

const agent = new PageAgent({
  model: 'qwen-max',
  apiKey: 'xxx',
  // 降级策略
  fallback: {
    // 主模型失败时切换到备选模型
    models: ['qwen-plus', 'gpt-4o-mini'],
    // 所有模型都失败时的降级行为
    onAllFailed: (error) => {
      console.error('Page-Agent 所有模型调用失败:', error)
      // 回退到传统表单提交
      showFallbackUI()
    }
  },
  // 重试策略
  retry: {
    maxAttempts: 3,
    backoff: 'exponential', // 指数退避
    initialDelay: 1000,
  },
  // 超时控制
  timeout: 30000, // 30 秒
})

12.3 监控与日志

// 生产环境监控
agent.on('action:start', (action) => {
  console.log(`[PageAgent] 开始执行: ${action.type} -> ${action.target}`)
  metrics.increment('page_agent.action.started')
})

agent.on('action:success', (action, result) => {
  metrics.histogram('page_agent.action.duration', result.duration)
  metrics.increment('page_agent.action.success')
})

agent.on('action:error', (action, error) => {
  console.error(`[PageAgent] 执行失败: ${action.type}`, error)
  metrics.increment('page_agent.action.error')
})

agent.on('llm:call', (prompt, response) => {
  metrics.histogram('page_agent.llm.latency', response.latency)
  metrics.increment('page_agent.llm.tokens', response.usage.totalTokens)
})

十三、未来展望

13.1 多模态融合

虽然 Page-Agent 当前走的是纯文本路线,但未来可能会融合轻量级视觉能力:

  • Canvas 截图:只截取特定 DOM 区域,而非整个页面
  • OCR 增强:对于图表、图片中的文字,用 OCR 补充 DOM 信息
  • 布局理解:用轻量级布局分析模型理解页面的空间关系

13.2 跨框架标准化

目前各 Web Agent 的实现各不相同,缺乏统一标准。MCP 协议可能成为统一的工具调用层,而 Page-Agent 的 DOM 脱水格式也可能成为行业标准。

13.3 端侧模型

随着 Gemma 4、Phi-4 等小型高性能模型的出现,完全在浏览器内运行的 AI Agent 成为可能:

// 未来:完全本地化的 Page-Agent
const localAgent = new PageAgent({
  model: 'gemma-4-e4b',  // 4B 参数的端侧模型
  runtime: 'webllm',      // WebLLM 运行时
  // 模型在浏览器内运行,零网络请求
  offline: true,
})

十四、总结

Page-Agent 代表了 Web Agent 技术的一个重要分支——嵌入式、纯文本、模型无关的页内智能体。它的核心创新在于:

  1. DOM 脱水算法:用文本替代截图,成本低 5-10 倍,速度快 5-10 倍
  2. 页内运行架构:天然安全,无需外部进程,一行代码集成
  3. 模型无关设计:支持任何 OpenAI 兼容 LLM,不被厂商绑定
  4. 操作白名单:精细的安全控制,适合企业场景

它不是 browser-use 的替代品,而是互补方案。browser-use 擅长「通用自动化」,Page-Agent 擅长「产品内嵌」。选择哪个,取决于你的场景:

  • 如果你在构建产品(SaaS、企业应用)→ Page-Agent
  • 如果你在做自动化(爬虫、测试、RPA)→ browser-use
  • 如果你两者都需要→ 组合使用

当一行 <script> 标签就能让网页听懂人话,前端工程师距离「AI 原生应用」的距离,就差一个 npm install page-agent 了。

推荐文章

Vue中如何处理异步更新DOM?
2024-11-18 22:38:53 +0800 CST
MySQL 主从同步一致性详解
2024-11-19 02:49:19 +0800 CST
php客服服务管理系统
2024-11-19 06:48:35 +0800 CST
Claude:审美炸裂的网页生成工具
2024-11-19 09:38:41 +0800 CST
html文本加载动画
2024-11-19 06:24:21 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
程序员茄子在线接单