首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我用 Tarsier 做了个网站自动化探索工具,聊聊 AI 驱动的网页理解

我用 Tarsier 做了个网站自动化探索工具,聊聊 AI 驱动的网页理解

作者头像
tunsuy
发布2026-04-09 10:13:13
发布2026-04-09 10:13:13
1080
举报

❝当 AI Agent 需要"看懂"网页时,会发生什么?❞

最近在研究 AI Agent 领域时,遇到了一个有趣的问题:「如何让 AI 理解并操作网页?」

传统的网页自动化工具(Selenium、Playwright)依赖开发者手动编写选择器,而 AI Agent 的愿景是让模型自己"看懂"页面、理解元素含义、做出操作决策。这中间的桥梁是什么?

答案是:「网页元素的视觉标注」

今天分享我基于 Tarsier 构建网站自动化探索工具的完整实践过程,包括原理分析、代码实现,以及踩过的那些坑。

❝📦 「完整代码」:https://github.com/ai-claw/claweb❞


一、问题的起点:AI 如何"看"网页?

1.1 传统自动化的困境

用过 Selenium 的同学都知道,写一个简单的自动化脚本需要:

代码语言:javascript
复制
# 传统方式:硬编码选择器
driver.find_element(By.CSS_SELECTOR, "#login-btn").click()
driver.find_element(By.XPATH, "//input[@name='username']").send_keys("test")

这种方式有几个明显的问题:

  • 「脆弱性」:页面结构一变,选择器就失效
  • 「维护成本高」:每个页面都要人工分析 DOM
  • 「无法泛化」:换个网站就要重新写

1.2 AI Agent 的新思路

如果让 AI 来做这件事呢?

理想的流程是:

  1. 给 AI 一张网页截图
  2. AI 识别出"登录按钮在哪"、"用户名输入框在哪"
  3. AI 输出操作指令

但这里有个关键问题:「AI 看到的是像素,而操作需要 DOM 元素」

视觉大模型可以说"登录按钮在图片的右上角",但自动化框架需要的是 #login-btn 或具体的 XPath。

「如何在视觉理解和 DOM 操作之间建立映射?」

这就是 Tarsier 要解决的问题。


二、Tarsier:给网页元素贴标签

2.1 Tarsier 是什么?

Tarsier(眼镜猴)是 Reworkd AI 开源的一个网页元素标注工具。它的核心思路非常巧妙:

「在网页截图上,给每个可交互元素添加可视化标签(如 [1]、[2]、[3]),同时维护标签 ID 到 XPath 的映射关系。」

这样,视觉大模型只需要说"点击 [5] 号元素",程序就能通过映射找到对应的 XPath 执行操作。

代码语言:javascript
复制
视觉截图(带标签)  →  大模型理解  →  输出 [ID]  →  查映射表  →  得到 XPath  →  执行操作

2.2 核心原理

Tarsier 的工作流程:

「Step 1:遍历 DOM 树,找出所有可交互元素」

代码语言:javascript
复制
# 可交互元素的判断标准
INTERACTIVE_ELEMENTS = [
    'a', 'button', 'input', 'select', 'textarea',
    '[role="button"]', '[role="link"]', '[onclick]'
]

「Step 2:为每个元素注入可视化标签」

代码语言:javascript
复制
// 在元素旁边插入标签
const tag = document.createElement('div');
tag.textContent = `[${id}]`;
tag.style.cssText = `
    position: absolute;
    background: #FF6B6B;
    color: white;
    font-size: 12px;
    padding: 2px 4px;
    border-radius: 3px;
    z-index: 10000;
`;
element.parentNode.insertBefore(tag, element);

「Step 3:截图并返回映射关系」

代码语言:javascript
复制
# 返回结果
{
    "screenshot": bytes,  # 带标签的截图
    "tag_to_xpath": {
        1: "//button[@id='login']",
        2: "//input[@name='username']",
        3: "//a[@href='/register']",
        # ...
    }
}

2.3 标签分类设计

Tarsier 对不同类型的元素使用不同的标签前缀:

前缀

元素类型

示例

[#ID]

输入框

文本框、密码框、搜索框

[@ID]

链接

超链接、锚点

[$ID]

按钮/交互

按钮、下拉菜单、可点击元素

[%ID]

图片

可交互的图片元素

这种分类让大模型更容易理解元素的用途。


三、动手实践:从基础使用到完整应用

3.1 环境准备

代码语言:javascript
复制
pip install tarsier playwright openai
playwright install chromium

3.2 基础使用:标注页面元素

代码语言:javascript
复制
import asyncio
from playwright.async_api import async_playwright
from tarsier import Tarsier, GoogleVisionOCRService

asyncdef basic_demo():
    asyncwith async_playwright() as p:
        browser = await p.chromium.launch(headless=False)
        page = await browser.new_page()
        await page.goto("https://example.com")
        
        # 标注页面
        tarsier = Tarsier(GoogleVisionOCRService(api_key="..."))
        screenshot, tag_to_xpath = await tarsier.page_to_image(page)
        
        print(f"发现 {len(tag_to_xpath)} 个可交互元素")
        await browser.close()

asyncio.run(basic_demo())

运行后,你会得到一张带有 [1][2][3] 等标签的截图,以及对应的 XPath 映射。

3.3 进阶:结合视觉大模型

有了标注后的截图,就可以让大模型"看图操作"了:

代码语言:javascript
复制
from openai import OpenAI

class VisionLLMClient:
    """视觉大模型客户端"""
    
    SYSTEM_PROMPT = """你是一个网页自动化助手。
页面上的可交互元素已被标记:[#ID] 输入框、[@ID] 链接、[$ID] 按钮
输出操作:CLICK [ID] / TYPE [ID] "文本" / SCROLL UP|DOWN / DONE
每次只输出一个操作。"""

    def __init__(self, api_base, api_key, model):
        self.client = OpenAI(base_url=api_base, api_key=api_key)
        self.model = model
    
    asyncdef analyze(self, screenshot: bytes, instruction: str) -> str:
        """分析截图,返回操作指令"""
        # 完整实现见 GitHub 仓库
        ...

3.4 完整的 Web Agent 执行流程

把上面的组件串起来,就是一个完整的 AI Web Agent:

代码语言:javascript
复制
class WebAgent:
    """AI 驱动的网页自动化代理"""
    
    def __init__(self, llm_client, browser_manager, page_tagger):
        self.llm = llm_client
        self.browser = browser_manager
        self.tagger = page_tagger
    
    asyncdef execute_task(self, url: str, task: str):
        """执行用户任务"""
        await self.browser.goto(url)
        
        for step in range(20):  # 最多 20 步
            # 1. 标注页面元素
            screenshot, tag_to_xpath = await self.tagger.tag_page(self.browser.page)
            
            # 2. 让大模型分析并决策
            action = await self.llm.analyze(screenshot, task)
            
            # 3. 解析并执行动作(CLICK/TYPE/DONE 等)
            if action == "DONE":
                break
            await self._execute_action(action, tag_to_xpath)
            
            # 4. 清理标签,等待页面更新
            await self.tagger.cleanup(self.browser.page)

3.5 LLM 交互原理详解

为了更清晰地理解 Tarsier 驱动的 Web Agent 工作机制,本节详细介绍发送给 LLM 的请求结构、响应格式以及核心 Prompt 模板。

3.5.1 请求结构

使用 OpenAI 兼容的多模态 API 格式:

代码语言:javascript
复制
{
  "model": "gpt-4o",
"messages": [
    {"role": "system", "content": "【系统提示词】"},
    {"role": "user", "content": [
      {"type": "text", "text": "【用户指令 + 页面信息】"},
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
    ]},
    {"role": "assistant", "content": "CLICK [$5]"},
    {"role": "user", "content": [...]},
    ...
  ],
"max_tokens": 500,
"temperature": 0.1
}

「关键点」

  • messages 包含完整对话历史,实现多轮交互
  • 每轮用户消息都包含最新截图和页面元素信息
  • temperature 设置较低(0.1)以获得更确定性的输出
3.5.2 核心 Prompt 模板

「系统提示词(System Prompt)」

代码语言:javascript
复制
SYSTEM_PROMPT = """你是一个专业的 Web 自动化助手。

页面上的可交互元素已被标记:
- [#ID]:文本输入框
- [@ID]:超链接  
- [$ID]:按钮或其他可交互元素
- [%ID]:图片元素

可用的操作格式:
- CLICK [ID] - 点击指定元素
- TYPE [ID] "文本内容" - 在输入框中输入文本
- SCROLL UP/DOWN - 向上或向下滚动页面
- GOTO "URL" - 导航到指定URL
- WAIT 秒数 - 等待指定秒数
- PAUSE - 暂停执行(用于验证码等人工介入场景)
- DONE - 任务完成

重要规则:每次只输出一个操作命令。"""
3.5.3 响应结构与解析

LLM 返回的是简洁的「单行命令」,解析规则如下:

代码语言:javascript
复制
# 命令解析正则表达式
CLICK_PATTERN = re.compile(r"CLICK\s*\[[@#$%]?(\d+)\]", re.IGNORECASE)
TYPE_PATTERN = re.compile(r'TYPE\s*\[[@#$%]?(\d+)\]\s*["\'](.+?)["\']', re.IGNORECASE)
SCROLL_PATTERN = re.compile(r"SCROLL\s+(UP|DOWN)", re.IGNORECASE)
# ... 更多模式见 action_executor.py

「响应示例」

代码语言:javascript
复制
点击 ID 为 5 的按钮
3.5.4 页面分析 Prompt(探索模式)

除了执行任务,Web Agent 还可以自动探索网站。这需要更复杂的分析 Prompt:

代码语言:javascript
复制
ANALYZE_PAGE_PROMPT = """分析这个网页截图,返回以下 JSON 格式的信息:
{
    "page_type": "login/home/list/detail/form/dashboard/unknown",
    "page_description": "一句话描述这个页面的功能",
    "has_sidebar_nav": true/false,
    "sidebar_nav_items": ["侧边栏导航菜单项名称列表"],
    "suggested_explorations": ["建议探索的操作"]
}
重要:只返回 JSON,不要有其他文字。"""
3.5.5 任务规划 Prompt(基于记忆)

当系统积累了网站记忆后,可以智能规划任务路径:

代码语言:javascript
复制
PLAN_PROMPT = """你是一个网站操作专家。根据用户的任务和网站记忆,规划操作步骤。

## 网站信息
域名: {domain}
已知页面: {pages}
已知操作路径: {actions}

## 用户任务
{task}

请返回 JSON 格式的操作计划:
{{"can_plan": true/false, "confidence": 0.0-1.0, "plan": [...]}}"""
3.5.6 完整交互流程示例

以下是一个"登录网站"任务的完整交互流程:

轮次

页面状态

LLM 响应

1

登录页面,显示邮箱/密码输入框

TYPE [#1] "admin@test.com"

2

邮箱已填写

TYPE [#2] "password123"

3

密码已填写

CLICK [$3]

4

跳转到 Dashboard 页面

DONE

3.5.7 与 Skyvern 的对比

特性

Tarsier Web Agent

Skyvern

「输出格式」

单行命令 (CLICK [5])

结构化 JSON

「批量操作」

每次一个动作

支持多个动作批量返回

「推理过程」

隐式(不输出)

显式(包含 reasoning 字段)

「元素标识」

可视化标签 [#ID][$ID]

元素列表 + element_id

「操作类型」

7 种基础命令

20+ 种细分 ActionType

「适用场景」

简单任务执行

复杂企业级自动化


四、更进一步:网站自动化探索

基础的"执行指令"模式需要人工告诉 AI 做什么。能不能让 AI 自己探索网站、发现功能、生成测试用例?

这就是我尝试的下一步:「网站自动化探索器」

4.1 设计思路

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────┐
│                    网站自动化探索流程                      │
├─────────────────────────────────────────────────────────┤
│  1. 访问首页,截图分析页面类型和结构                        │
│                    ↓                                     │
│  2. 识别导航菜单、CRUD 按钮等可探索元素                     │
│                    ↓                                     │
│  3. 按优先级依次点击,记录页面变化                          │
│                    ↓                                     │
│  4. 分析新页面,发现更多可探索元素                          │
│                    ↓                                     │
│  5. 重复 3-4,直到覆盖所有功能                             │
│                    ↓                                     │
│  6. 输出:页面结构图 + 操作记录 + 测试用例                   │
└─────────────────────────────────────────────────────────┘

4.2 页面分析器

首先需要让 AI 理解页面的类型和结构:

代码语言:javascript
复制
class PageAnalyzer:
    """页面语义分析器"""
    
    async def analyze_page(self, screenshot: bytes) -> Dict:
        """分析页面类型和结构,返回 page_type/description/nav_items 等"""
        ...
    
    async def analyze_elements(self, screenshot: bytes, context: str) -> List:
        """分析页面元素,返回 tag_id/semantic_name/explore_priority 等"""
        ...

4.3 探索器核心逻辑

代码语言:javascript
复制
class SiteExplorer:
    """网站自动化探索器 - 广度优先探索网站功能"""
    
    def __init__(self, config, db):
        self.browser_manager = BrowserManager(config.browser)
        self.page_tagger = PageTagger()
        self.page_analyzer = PageAnalyzer(VisionLLMClient(config.llm))
        self.db = db
        
        self.visited_urls = set()
        self.pending_items = []  # 待探索队列
    
    asyncdef explore_site(self, start_url: str) -> Site:
        """探索整个网站"""
        await self.browser_manager.goto(start_url)
        
        # 第一阶段:分析首页,收集导航菜单
        await self._analyze_and_collect_items()
        
        # 第二阶段:广度优先探索
        await self._explore_all_items()
        
        return self.site

4.4 数据持久化设计

探索的结果需要持久化存储,便于后续生成测试用例:

代码语言:javascript
复制
# 数据模型设计
class Site:
    """网站"""
    id: int
    domain: str
    name: str

class Page:
    """页面"""
    id: int
    site_id: int
    url_pattern: str
    page_type: str  # login/list/detail/form/dashboard
    semantic_description: str
    key_features: str  # JSON

class Element:
    """页面元素"""
    id: int
    page_id: int
    element_type: str  # button/link/input/nav_item
    semantic_name: str
    css_selector_hint: str

class Action:
    """操作记录"""
    id: int
    source_page_id: int
    element_id: int
    action_type: str  # click/type/scroll
    target_page_id: int
    notes: str

五、实践中的问题与思考

5.1 LLM 响应的不确定性

这是最大的挑战。同一个页面,大模型可能返回完全不同的分析结果。

「应对策略」:重试机制 + 结果校验 + 降级方案

代码语言:javascript
复制
async def analyze_with_retry(self, screenshot, prompt, max_retries=3):
    for i in range(max_retries):
        result = await self.llm.analyze(screenshot, prompt)
        if self._is_valid_response(result):
            return result
    return {"elements": [], "page_type": "unknown"}  # 降级

5.2 元素标签与 XPath 的映射问题

Tarsier 返回的 tag_id 类型可能不一致:

代码语言:javascript
复制
# 兼容整数和字符串类型的 tag_id
def get_xpath(tag_to_xpath, tag_id):
    return tag_to_xpath.get(tag_id) or tag_to_xpath.get(str(tag_id))

5.3 页面状态管理

探索过程中页面状态不断变化,需要记录"来源页面"以便回退:

代码语言:javascript
复制
首页 → 点击菜单A → 列表页A → 点击新建 → 弹窗 → 关闭 → 列表页A

5.4 弹窗/模态框的处理

很多 CRUD 操作会打开弹窗而不是跳转页面,需要检测并处理:

代码语言:javascript
复制
# 支持多种 UI 框架的弹窗检测
modal_selectors = [".ant-modal", ".el-dialog", ".t-dialog", "[role='dialog']"]

5.5 探索的终止条件

  • 「固定上限」max_pages = 50, max_actions = 200
  • 「覆盖率判断」:待探索队列为空时停止
  • 「时间限制」:超时自动终止

❝完整的问题处理方案请参考项目源码❞

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 有文化的技术人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、问题的起点:AI 如何"看"网页?
    • 1.1 传统自动化的困境
    • 1.2 AI Agent 的新思路
  • 二、Tarsier:给网页元素贴标签
    • 2.1 Tarsier 是什么?
    • 2.2 核心原理
    • 2.3 标签分类设计
  • 三、动手实践:从基础使用到完整应用
    • 3.1 环境准备
    • 3.2 基础使用:标注页面元素
    • 3.3 进阶:结合视觉大模型
    • 3.4 完整的 Web Agent 执行流程
    • 3.5 LLM 交互原理详解
      • 3.5.1 请求结构
      • 3.5.2 核心 Prompt 模板
      • 3.5.3 响应结构与解析
      • 3.5.4 页面分析 Prompt(探索模式)
      • 3.5.5 任务规划 Prompt(基于记忆)
      • 3.5.6 完整交互流程示例
      • 3.5.7 与 Skyvern 的对比
  • 四、更进一步:网站自动化探索
    • 4.1 设计思路
    • 4.2 页面分析器
    • 4.3 探索器核心逻辑
    • 4.4 数据持久化设计
  • 五、实践中的问题与思考
    • 5.1 LLM 响应的不确定性
    • 5.2 元素标签与 XPath 的映射问题
    • 5.3 页面状态管理
    • 5.4 弹窗/模态框的处理
    • 5.5 探索的终止条件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档