VLM+无头浏览器打造自动化RPA,彻底告别接口依赖
传统企业自动化面临一个残酷的现实:你想要的API根本不存在。
当业务团队要求"自动从供应商门户抓取实时库存"时,工程师的回应往往是"他们没有开放接口"。于是,项目陷入死局——要么等待供应商开发API(通常以季度计),要么部署脆弱的CSS选择器爬虫(一次改版就全线崩溃)。
2024年,这一困局正在被一种全新的技术范式打破:Vision-Language Model(VLM)驱动的智能浏览器Agent。它不再依赖预设的DOM选择器,而是像人类一样"看懂"网页、理解语义、自主决策。这不是渐进式改良,而是自动化领域的范式转移。
一、技术范式的断裂:从Selector到Semantic
传统RPA和Web自动化的核心脆弱性在于Selector依赖。无论是Selenium的XPath、Puppeteer的CSS选择器,还是Playwright的Locator,本质上都是在用硬编码规则匹配DOM树。一旦前端框架升级、A/B测试启动、或动态class名变化,整个自动化链路即刻瘫痪。
数据不会撒谎:根据 UiPath 2023 Annual Report 披露,企业RPA项目的平均维护成本占初始开发的35%-45%,其中超过60%的维护工作源于UI变更导致的选择器失效。
VLM Agent的核心突破在于:它不再"记住"元素位置,而是"理解"页面意图。即便按钮从页面左侧移到右侧、class从.btn-submit变成.primary-action,只要语义标签仍然是"提交订单",Agent就能准确定位。
二、核心技术栈解构:从DOM到Cognition
构建一个生产级VLM Browser Agent,需要四个核心模块的精密协作:
2.1 Accessibility Tree:被忽视的语义金矿
大多数开发者对Accessibility Tree(a11y tree)的认知仅停留在"无障碍支持"。但在VLM Agent语境下,它是连接视觉像素与DOM语义的桥梁。
与完整的DOM树(通常包含数千节点)不同,Accessibility Tree是经过浏览器精简的语义化结构,仅保留对辅助技术有意义的元素——按钮、链接、表单、标题等。这意味着:
- 信息密度提升10-50倍:典型电商页面DOM节点约3000-5000个,Accessibility Tree仅100-300个
- 噪声自动过滤:
<div>嵌套地狱、装饰性元素被自动剔除 - 角色语义明确:每个节点自带
role、name、value等ARIA属性
// Playwright 获取 Accessibility Tree 的标准方法
const accessibilitySnapshot = await page.accessibility.snapshot();
// 输出结构示例:
// {
// role: 'WebArea',
// name: 'Amazon Shopping Cart',
// children: [
// { role: 'button', name: 'Proceed to Checkout', focused: true },
// { role: 'link', name: 'Continue Shopping' }
// ]
// }
2.2 Set-of-Mark (SoM):视觉-语言对齐的Rosetta Stone
微软研究院2023年论文 “Set-of-Mark: Visual Prompting for Large Vision-Language Models” 提出了一种革命性的视觉标注方法:在截图的每个交互元素上叠加数字标记,让VLM能够精确引用。
| 方法 | 定位精度 | 多元素区分 | 成本 | 鲁棒性 |
|---|---|---|---|---|
| 纯文本描述 | 低 | 差 | 低 | 中 |
| Bounding Box坐标 | 高 | 中 | 高 | 低 |
| Set-of-Mark | 极高 | 优 | 中 | 高 |
| 纯VLM视觉定位 | 中 | 差 | 高 | 中 |
SoM的工作流:
2.3 Action Space:从意图到执行
一个设计良好的Action Space需要在表达力与可控性之间取得平衡。参考 Browser Use(2024年最活跃的Web Agent开源项目)的设计:
# Browser Use Action Space 定义(简化版)
class BrowserAction(Enum):
CLICK = "click" # 参数: element_id: int
TYPE = "type" # 参数: element_id: int, text: str
SCROLL = "scroll" # 参数: direction: "up" | "down"
NAVIGATE = "navigate" # 参数: url: str
EXTRACT = "extract" # 参数: element_id: int
WAIT = "wait" # 参数: seconds: int
COMPLETE = "complete" # 任务完成信号
关键设计原则:
- 原子化:每个Action只做一件事,便于VLM精确控制
- 可组合:复杂操作通过Action序列实现
- 可观测:每个Action执行后返回页面状态变化
2.4 状态管理与记忆:LangGraph的状态机范式
生产级Agent不能是无状态的——它需要记住"我在哪"、“我做过什么”、“下一步该做什么”。LangGraph 提供了基于状态图的优雅解决方案:
三、实战架构:构建自动驾驶级RPA引擎
以下是一个生产可用的VLM Browser Agent核心架构:
3.1 核心代码骨架
以下是融合SoM与Accessibility Tree的Agent核心逻辑:
"""
VLM Browser Agent - 核心引擎
参考项目: browser-use, webagent, omne-agentic-workflow
"""
import asyncio
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
from enum import Enum
from playwright.async_api import async_playwright, Page, Browser
from openai import AsyncOpenAI
import json
class ActionType(Enum):
CLICK = "click"
TYPE = "type"
SCROLL = "scroll"
NAVIGATE = "navigate"
EXTRACT = "extract"
WAIT = "wait"
COMPLETE = "complete"
@dataclass
class BrowserAction:
type: ActionType
element_id: Optional[int] = None
text: Optional[str] = None
url: Optional[str] = None
direction: Optional[str] = None
seconds: Optional[int] = None
@dataclass
class ElementInfo:
id: int
role: str
name: str
bounding_box: Dict[str, float]
is_interactive: bool
class SoMAnnotator:
"""Set-of-Mark 标注器"""
def __init__(self):
self.counter = 0
self.elements: Dict[int, ElementInfo] = {}
def build_element_map(self, accessibility_tree: dict) -> Dict[int, ElementInfo]:
"""从Accessibility Tree构建元素映射"""
self.elements.clear()
self.counter = 0
self._traverse_tree(accessibility_tree)
return self.elements
def _traverse_tree(self, node: dict, depth: int = 0):
"""递归遍历Accessibility Tree"""
interactive_roles = {
'button', 'link', 'textbox', 'checkbox', 'radio',
'combobox', 'menuitem', 'tab', 'searchbox'
}
role = node.get('role', '')
name = node.get('name', '')
if role in interactive_roles and name:
self.counter += 1
self.elements[self.counter] = ElementInfo(
id=self.counter,
role=role,
name=name,
bounding_box=node.get('boundingBox', {}),
is_interactive=True
)
for child in node.get('children', []):
self._traverse_tree(child, depth + 1)
def generate_som_screenshot(self, page: Page, output_path: str) -> str:
"""生成带SoM标注的截图"""
# 注入Canvas覆盖层绘制数字标记
js_code = f"""
(elements) => {{
const canvas = document.createElement('canvas');
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.zIndex = '999999';
canvas.style.pointerEvents = 'none';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.font = 'bold 14px Arial';
Object.entries(elements).forEach(([id, el]) => {{
const bbox = el.bounding_box;
if (bbox) {{
ctx.fillStyle = 'rgba(255, 165, 0, 0.8)';
ctx.beginPath();
ctx.arc(bbox.x + 12, bbox.y + 12, 12, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = 'black';
ctx.fillText(id, bbox.x + 6, bbox.y + 17);
}}
}});
return canvas.toDataURL();
}}
"""
# 执行标注(实际实现需要更复杂的坐标映射)
return output_path
class VLMAgent:
"""VLM驱动的浏览器Agent"""
SYSTEM_PROMPT = """你是一个专业的Web自动化Agent。你的任务是根据页面截图和元素信息,选择最合适的操作来完成任务。
可用操作:
- click(element_id): 点击指定元素
- type(element_id, text): 在指定元素中输入文本
- scroll(direction): 滚动页面
- navigate(url): 导航到指定URL
- extract(element_id): 提取元素内容
- wait(seconds): 等待指定秒数
- complete(): 任务完成
响应格式(JSON):
{"action": "click", "element_id": 23, "reasoning": "点击提交按钮完成订单"}
"""
def __init__(
self,
model: str = "gpt-4o",
api_key: Optional[str] = None,
base_url: Optional[str] = None
):
self.model = model
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
self.annotator = SoMAnnotator()
self.max_steps = 30
self.conversation_history: List[Dict] = []
async def run(self, task: str, start_url: str) -> Dict[str, Any]:
"""执行自动化任务"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
page = await browser.new_page()
# 导航到起始页
await page.goto(start_url)
await page.wait_for_load_state('networkidle')
result = {"success": False, "steps": 0, "data": None}
for step in range(self.max_steps):
# 1. 感知:获取页面状态
accessibility_tree = await page.accessibility.snapshot()
element_map = self.annotator.build_element_map(accessibility_tree)
screenshot_path = f"/tmp/step_{step}.png"
await page.screenshot(path=screenshot_path)
# 2. 构建 SoM 增强的 Prompt
element_description = self._format_elements(element_map)
# 3. VLM 推理
action = await self._decide_action(
task=task,
element_description=element_description,
screenshot_path=screenshot_path
)
result["steps"] = step + 1
# 4. 执行动作
if action.type == ActionType.COMPLETE:
result["success"] = True
break
await self._execute_action(page, action, element_map)
await asyncio.sleep(1) # 等待页面响应
await browser.close()
return result
async def _decide_action(
self,
task: str,
element_description: str,
screenshot_path: str
) -> BrowserAction:
"""调用VLM进行决策"""
with open(screenshot_path, "rb") as f:
screenshot_data = f.read()
messages = [
{"role": "system", "content": self.SYSTEM_PROMPT},
{"role": "user", "content": [
{"type": "text", "text": f"任务: {task}\n\n可用元素:\n{element_description}"},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{screenshot_data.hex()}"}}
]}
]
response = await self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=256,
temperature=0.1
)
return self._parse_action(response.choices[0].message.content)
def _format_elements(self, elements: Dict[int, ElementInfo]) -> str:
"""格式化元素列表为文本描述"""
lines = []
for eid, el in elements.items():
lines.append(f"[{eid}] {el.role}: \"{el.name}\"")
return "\n".join(lines)
def _parse_action(self, response: str) -> BrowserAction:
"""解析VLM响应为Action对象"""
try:
data = json.loads(response)
return BrowserAction(
type=ActionType(data.get("action", "wait")),
element_id=data.get("element_id"),
text=data.get("text"),
url=data.get("url"),
direction=data.get("direction"),
seconds=data.get("seconds", 2)
)
except:
return BrowserAction(type=ActionType.WAIT, seconds=2)
async def _execute_action(
self,
page: Page,
action: BrowserAction,
elements: Dict[int, ElementInfo]
):
"""执行浏览器动作"""
if action.type == ActionType.CLICK and action.element_id:
el = elements.get(action.element_id)
if el:
# 使用语义定位而非选择器
await page.get_by_role(el.role, name=el.name).click()
elif action.type == ActionType.TYPE and action.element_id:
el = elements.get(action.element_id)
if el and action.text:
await page.get_by_role(el.role, name=el.name).fill(action.text)
elif action.type == ActionType.SCROLL:
direction = action.direction or "down"
await page.evaluate(f"window.scrollBy(0, {'500' if direction == 'down' else '-500'})")
elif action.type == ActionType.NAVIGATE and action.url:
await page.goto(action.url)
elif action.type == ActionType.WAIT:
await asyncio.sleep(action.seconds or 2)
# 使用示例
async def main():
agent = VLMAgent(model="gpt-4o")
result = await agent.run(
task="在Amazon搜索'wireless earbuds',找到价格最低的商品,加入购物车",
start_url="https://www.amazon.com"
)
print(f"任务完成: {result}")
if __name__ == "__main__":
asyncio.run(main())
四、开源生态与选型指南
2024年,VLM Browser Agent领域迎来了爆发式增长。以下是目前最值得关注的开源项目横向对比:
| 项目 | Stars | 核心特点 | VLM支持 | 生产就绪度 | 维护活跃度 |
|---|---|---|---|---|---|
| browser-use | 45k+ | LangChain集成、多模型支持 | GPT-4o/Claude/Gemini/本地 | 高 | ⭐⭐⭐⭐⭐ |
| Open Interpreter | 58k+ | 自然语言操作系统 | 多模型 | 中 | ⭐⭐⭐⭐ |
| Omne | 3k+ | 多Agent协作框架 | 模块化 | 中 | ⭐⭐⭐ |
| WebVoyager | 2k+ | 学术基准测试 | 研究导向 | 低 | ⭐⭐ |
| Agent-E | 1k+ | 企业级Web Agent | 多模型 | 高 | ⭐⭐⭐⭐ |
选型建议
五、成本与性能的工程权衡
VLM调用的Token消耗是生产部署的核心考量。以下是典型场景的成本分析:
5.1 Token消耗估算
| 场景 | 页面复杂度 | 单步Token | 平均步数 | 单任务成本(GPT-4o) |
|---|---|---|---|---|
| 简单表单填写 | 低 | ~1,500 | 3-5 | $0.03-0.05 |
| 电商搜索+下单 | 中 | ~2,500 | 8-12 | $0.15-0.25 |
| 复杂多页流程 | 高 | ~4,000 | 15-25 | $0.50-0.80 |
5.2 优化策略
- 视觉压缩:将截图缩放至512x512或768x768,减少视觉Token
- 元素预过滤:仅传递可交互元素给VLM,过滤装饰性内容
- 缓存策略:对静态页面元素缓存SoM标注
- 模型分层:简单动作用小模型,复杂决策用大模型
# 成本优化:分层模型策略
class HierarchicalAgent:
def __init__(self):
self.fast_model = "gpt-4o-mini" # 简单决策
self.smart_model = "gpt-4o" # 复杂推理
async def decide(self, context: dict) -> BrowserAction:
complexity_score = self._assess_complexity(context)
if complexity_score < 0.3:
# 简单场景:快速模型
return await self._call_model(self.fast_model, context)
else:
# 复杂场景:智能模型
return await self._call_model(self.smart_model, context)
六、安全与伦理边界
自动驾驶级RPA的威力越大,责任越重。必须在架构层面内嵌安全约束:
核心安全规则
# agent_security_config.yaml
safety_rules:
url_whitelist:
- "https://internal.company.com/*"
- "https://portal.supplier-a.com/*"
action_blacklist:
- type: "click"
target_pattern: "*Delete*"
require_confirmation: true
- type: "type"
target_pattern: "*password*"
log_content: false
rate_limits:
max_actions_per_minute: 30
max_actions_per_session: 500
sandbox:
disable_file_access: true
disable_network_override: true
credential_isolation: true
七、未来展望:从RPA到Agentic Workflow
VLM Browser Agent不是终点,而是Agentic Workflow时代的序章。我们正在见证三个趋势的交汇:
- 多模态统一:GPT-4o、Gemini 1.5等模型已实现文本、图像、视频、音频的原生理解
- 工具使用能力:模型不再只是"聊天",而是主动调用API、操作软件、编排工作流
- 长期记忆:向量数据库与RAG让Agent能够积累经验、持续学习
预测:到2025年底,70%的企业Web自动化场景将通过VLM Agent实现,传统Selector-based RPA将退守至极少数高吞吐、低延迟的专用场景。
核心资源与引用
开源项目:
- Browser Use: https://github.com/browser-use/browser-use
- Open Interpreter: https://github.com/OpenInterpreter/open-interpreter
- Playwright: https://github.com/microsoft/playwright
- LangGraph: https://github.com/langchain-ai/langgraph
关键论文:
- Set-of-Mark Visual Prompting (Microsoft Research, 2023): https://arxiv.org/abs/2310.11441
- WebVoyager: Building an End-to-End Web Agent with Large Multimodal Models: https://arxiv.org/abs/2401.13919
- SeeAct: GPT-4V as Web Navigation Agent: https://arxiv.org/abs/2401.01614
技术规范:
- WAI-ARIA Accessibility Tree: https://www.w3.org/TR/wai-aria/
- Playwright Accessibility API: https://playwright.dev/python/docs/api/class-accessibility
API未死,但它正在变成"编译目标"而非"编程接口"。 未来的开发者不再手写HTTP请求,而是用自然语言描述意图,让VLM Agent自动生成执行路径。这不是科幻,而是正在发生的现实。
如果你正在评估VLM Agent在业务中的落地,建议从低风险、高价值的内部流程开始——供应商门户数据同步、竞品价格监控、报表自动下载——逐步积累经验后再扩展到生产环境。
技术的浪潮已经涌来,你准备好冲浪了吗?
更多推荐

所有评论(0)