Skyvern 全景:这是什么 / 怎么转 / 从哪读
30 秒导读: Skyvern 是一个用「大语言模型 + 计算机视觉」自动化浏览器工作流的开源引擎。 它最核心的一招:不写死 XPath/CSS 选择器——而是给页面上每个可交互元素临时打一个编号
unique_id,把「带编号的元素清单 + 截图」喂给 LLM,LLM 只回「点 id=42、往 id=17 里输入 'hello'」,Skyvern 再把编号翻译回真实的 Playwright 定位器去操作。页面改版、换皮肤都不影响, 因为编号是每次现抓的。本章只做导航与心智模型,不钻任何单一机制的实现(细节留给各章)。
1. 这是什么(零基础也能懂)
一句话定义: Skyvern 让你用自然语言描述一个网页任务(「登录后把发票下载下来」),它自己 看页面、决定点哪、填什么,一步步把任务做完。
它解决谁的什么问题。 传统的网页自动化(Selenium / Playwright 脚本)有个老毛病:你得为
每个按钮、每个输入框写死一条选择器(//div[@id='login-btn']) 。网站一改版,选择器全失效,脚本
集体崩。Skyvern 把「找元素」这件事交给 LLM 的视觉与语义理解,于是:
| 传统脚本 | Skyvern |
|---|---|
| 人工为每个元素写死 XPath/CSS | 运行时现抓元素、现打编号 |
| 页面改版 → 选择器失效 → 脚本崩 | 页面改版 → 重新抓一遍就行 |
| 只能跑「见过的」固定页面 | 没见过的页面也能试着操作 |
| 逻辑写在代码里 | 逻辑写在自然语言目标里 |
给谁用。 想批量填表、抓数据、跑重复网页流程,又不想维护一堆脆弱选择器的人;需要「网页 Agent」能力嵌进自己产品的开发者。
用起来什么样。 公共入口是 from skyvern import Skyvern(见 skyvern/__init__.py:15
的 lazy import),两个主方法 run_task(一次性任务)和 run_workflow(编排好的多步流程):
# 示意,非源码。真实签名见 skyvern/library/skyvern.py:294 run_task / :351 run_workflow
from skyvern import Skyvern
skyvern = Skyvern(api_key="...") # 或 Skyvern.local() 连本地引擎
run = await skyvern.run_task( # 给一句话目标 + 起始网址
prompt="登录并把最近一张发票下载下来",
url="https://example.com/login",
)
print(run.status, run.output) # 跑完拿状态和产出
一句话直觉。 把它想成一个「只会看屏幕、不会读源码」的实习生:你把网页截图和一份 「屏幕上有哪些可点的东西(每个都编了号)」的清单递给他,他指着说「点 3 号、往 8 号里打字」, 你替他真去点。他永远不需要知道那个按钮的 HTML 长什么样。
本节不出现底层代码细节;目标只是让完全没接触过的人知道「这东西是干嘛的」。
2. 顶层全景(它大概怎么转)
2.1 三层主干 + 三个横切子系统
Skyvern 的骨架是一条感知 → 决策 → 执行的流水线,外面套三个横切子系统。先看这张图 (从上往下是一次「思考-行动」的数据流向):
┌─────────────────────── 横切子系统 ───────────────────────┐
│ │
│ ①多引擎 ②工作流引擎 ③规划器 │
│ RunEngine workflow blocks task_v2 │
│ (选哪种大脑) (Block 编排多步) (一句话→自主拆解) │
│ │
└────────────┬──────────────┬───────────────┬─────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 真实网页 │─抓─▶ │ 感知层 │─喂─▶ │ 决策层 │─出─▶ │ 执行层 │
│ (Playwright │ │ webeye/ │ 元素 │ forge/ │ 动作 │ webeye/ │
│ 控制的浏览器)│ ◀─操作───────────────────────────────────────── │ actions/ │
└──────────────┘ │ scraper │ 清单 │ agent.py │ JSON │ handler.py │
▲ │ +截图 │ +截图│ (Agent 循环) │ │ (动作分发) │
└──────────────┴──────────────┴──────┴── ────────────┴──────┴──────────────┘
操作真实浏览器(点/填/滚)后回到「抓」,循环
怎么读这张图: 中间那条横带(网页 → 感知 → 决策 → 执行 →(操作)→ 网页)是主血管,
一次 step 就是走一遍;上方三个横切子系统决定「用哪种大脑」「要不要编排成多步」「要不要先自动
拆任务」。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪(文件 : 符号) |
|---|---|---|
| 感知层 · 抓取器 | 打 unique_id、抽可交互元素树、截图 | skyvern/webeye/scraper/scraper.py:190 scrape_website |
| 感知层 · 页面模型 | 装抓取结果:id→元素/CSS/frame 的映射表 | skyvern/webeye/scraper/scraped_page.py:170 ScrapedPage |
| 决策层 · Agent | 中央循环:建 prompt → 调 LLM → 拿动作 → 逐个执行 | skyvern/forge/agent.py:355 ForgeAgent |
| 决策层 · 单步 | 一次 step 的完整逻辑 | skyvern/forge/agent.py:1316 agent_step |
| 执行层 · 分发器 | 按动作类型查表,调对应 handler | skyvern/webeye/actions/handler.py:1231 ActionHandler |
| 执行层 · id→定位器 | 把 unique_id 翻译回真实 Playwright 定位器 | skyvern/webeye/utils/dom.py:52 resolve_locator |
| ①多引擎 | 六种「大脑」的枚举与分岔 | skyvern/schemas/run_enums.py:14 RunEngine |
| ②工作流引擎 | 30 种 Block 类型编排多步 | skyvern/schemas/workflows.py:417 BlockType |
| ③规划器 | Skyvern 2.0:一句话目标自主拆成子任务 | skyvern/services/task_v2_service.py:481 run_task_v2 |
| 公共入口 | from skyvern import Skyvern 的 SDK 门面 | skyvern/library/skyvern.py Skyvern.run_task / run_workflow |
2.3 主线走一遍(高层,不进代码)
一次 step(Agent 的一个「思考-行动」回合)端到端是这样的——这条线是理解整个项目的钥匙,
每一环的实现细节分别落在 01/02/03 章:
①抓取页面 ②喂给 LLM ③拿到动作 JSON ④落到真实元素 ⑤判断是否完成
───────── ───────── ───────────── ───────────── ─────────────
给每个可交互元素 把「元素树 + 截图」 LLM 回一段 JSON: 按 element_id 查 问 LLM/看信号:
打 unique_id, 连同任务目标塞进 [{action:click, id_to_css_dict, 目标达成了吗?
抽成精简元素树, prompt,发给大模型 element_id:42}, resolve_locator 没完 → 回①抓新页
顺手截一张图 {input,id:17,...}] 翻成 Playwright 面,进下一 step
定位器,真去点/填
│ │ │ │ │
scrape_website build_and_record parse_actions ActionHandler check_user_goal
(scraper.py) _step_prompt + (parse_actions.py) .handle_action _complete /
LLM 调用(agent.py agent.py:1606 (handler.py) complete_verify
:1514) (agent.py)
一句话概括这条线:看(抓页面)→ 想(问 LLM)→ 做(执行动作)→ 查(是否完成),不完成就再看一遍。 所谓「不写死选择器」的魔法,全压在 ①的「打编号」和 ④的「按编号翻译回定位器」这一对上。
3. 阅读地图(从哪读、怎么读)
建议由浅入深按 01 → 06 顺序读。每章一句话导览:
| 顺序 | 章节 | 一句话导览 | 何时读它 |
|---|---|---|---|
| 1 | 01-perception-scraper.md | 感知层:domUtils.js 怎么给元素打 unique_id、scrape_website 怎么把一页网页压成「元素树 + 三张映射表 + 截图」 | 想懂「LLM 到底看到了什么」 |
| 2 | 02-agent-loop-planning.md | 决策层:execute_step / agent_step 的单步循环——建 prompt、调 LLM、解析动作、逐个执行、验证完成 | 想懂「Agent 一个回合怎么转」 |
| 3 | 03-action-execution.md | 执行层:ActionHandler 分发表、resolve_locator 把 id 翻回定位器、点击/输入/下拉的容错技巧 | 想懂「模型说的话怎么精确落地」 |
| 4 | 04-engines-cua.md | 引擎家族:DOM 规划器(skyvern-1.0)vs 计算机使用 CUA(OpenAI/Anthropic/UI-TARS)——同一循环里 agent_step 如何按 RunEngine 分岔 | 想懂「除了看 DOM 还能怎么驱动」 |
| 5 | 05-workflow-blocks.md | 工作流引擎:30 种 BlockType(导航/提取/循环/条件/发邮件…)如何拼成多步流程、参数怎么在 Block 间流转 | 想编排复杂的多步任务 |
| 6 | 06-skyvern2-planner.md | 2.0 规划器:task_v2 如何把一句话大目标自主拆成一串导航/循环/提取子任务并自动编排 | 想懂「一句话怎么变成一整套流程」 |
只想快速抓重点? 读完本章 §2 的三张图 + §4 的巧妙之处,已经能讲清「Skyvern 是什么、 核心原理是什么」。要动手改代码再按上表下钻。
4. 巧妙之处(读者该带走的精华)
这几条是 Skyvern 区别于「又一个 Playwright 封装」的设计决策,每条给出可核对的锚点:
-
用临时
unique_id当「模型 ↔ 真实 DOM」的握手协议。 抓取时domUtils.js给每个元素element.setAttribute("unique_id", ...)(skyvern/webeye/scraper/domUtils.js:1660-1662);常量定义SKYVERN_ID_ATTR = "unique_id"(skyvern/constants.py:5)。LLM 全程只跟这个编号打交道, 执行时resolve_locator再用[unique_id='42']选择器把它翻回 Playwright 定位器 (skyvern/webeye/utils/dom.py:52)。这是「不写死 XPath」的技术底座。 -
动作分发用「注册表」而非巨型 if-else。
ActionHandler维护一张_handled_action_types字典,靠register_action_type把 30 种 动作(点击/输入/下拉/上传/滚动/切标签…)挂进去 (skyvern/webeye/actions/handler.py:4015-4040)。加新动作 = 加一行注册,不动分发主干。 -
一套
agent_step循环喂多种「大脑」。 同一个agent_step内按RunEngine分岔:DOM 规划走 LLM 出 JSON 动作,CUA 引擎 (OpenAI/Anthropic/UI-TARS/Yutori)走各自的原生「计算机使用」协议 (skyvern/forge/agent.py:1449-1485;引擎集合CUA_ENGINES见skyvern/schemas/run_enums.py:23)。换模型家族不用重写循环。 -
CUA 任务自动关掉「完成验证」以省钱提速。 一个不显然的细节:
execute_step里若engine in CUA_ENGINES就强制complete_verification = False——因为 CUA 每步只出一个动作、极少幻觉「假装完成」,再跑一次 验证纯属浪费(skyvern/forge/agent.py:656-657)。 -
两层任务模型:一步循环(task_v1) + 会自主拆解的规划器(task_v2)。 task_v2 不直接操作页面,而是像项目经理:把大目标拆成导航/循环/提取等子任务再交给底层循环去跑 (
_generate_navigation_task/_generate_loop_task见skyvern/services/task_v2_service.py:1725/:1411)。这就是「Skyvern 2.0」自主编排的由来。
5. 边界与心智模型提醒
- 它「看」的是抽出来的元素树 + 截图,不是完整 HTML。 抓取阶段会裁剪(
trim_element_tree)、 只留可交互与可见元素(skyvern/webeye/scraper/scraper.py)。所以模型看不到被过滤掉的东西—— 这是能力也是盲区,细节见 01 章。 unique_id是每次抓取现打的、易变的。 正因为易变,它对页面改版鲁棒;但也意味着编号不能跨 step 复用——每个 step 都要重抓。- 不是所有引擎都「读 DOM」。 CUA 系(计算机使用)更接近「纯看截图点坐标」,DOM 规划器才依赖
元素树。选哪种由
RunEngine决定,取舍见 04 章。
本章到此为止只给全景与心智模型;任何一环「具体怎么实现」都请翻对应章节。
6. 总代码地图(agent 的跳转表)
覆盖全项目关键文件与真实符号名,按「感知 → 决策 → 执行 → 横切 → 入口」排;行号会漂,优先用 符号名 grep 定位。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 抓页面 · 主入口 | skyvern/webeye/scraper/scraper.py | scrape_website / scrape_web_unsafe |
| 抓页面 · 元素树构建 | skyvern/webeye/scraper/scraper.py | get_interactable_element_tree / trim_element_tree |
| 打 unique_id(浏览器内 JS) | skyvern/webeye/scraper/domUtils.js | buildElementTree 区(setAttribute("unique_id", …)) |
| id 属性常量 | skyvern/constants.py | SKYVERN_ID_ATTR |
| 页面模型 · 映射表 | skyvern/webeye/scraper/scraped_page.py | ScrapedPage(id_to_element_dict / id_to_css_dict / id_to_frame_dict) |
| 决策 · Agent 类 | skyvern/forge/agent.py | ForgeAgent |
| 决策 · 外层执行 | skyvern/forge/agent.py | execute_step |
| 决策 · 单步循环 | skyvern/forge/agent.py | agent_step |
| 决策 · 建 prompt + 截图 | skyvern/forge/agent.py | build_and_record_step_prompt |
| 决策 · 完成判断 | skyvern/forge/agent.py | check_user_goal_complete / complete_verify |
| 动作解析(JSON→Action) | skyvern/webeye/actions/parse_actions.py | parse_actions / parse_cua_actions / parse_anthropic_actions |
| 执行 · 分发器 | skyvern/webeye/actions/handler.py | ActionHandler.handle_action / register_action_type |
| 执行 · 典型 handler | skyvern/webeye/actions/handler.py | handle_click_action / handle_input_text_action / handle_select_option_action |
| 执行 · id→定位器 | skyvern/webeye/utils/dom.py | resolve_locator / SkyvernElement |
| 动作类型枚举 | skyvern/webeye/actions/action_types.py | ActionType |
| ①多引擎枚举 | skyvern/schemas/run_enums.py | RunEngine / CUA_ENGINES |
| ②工作流 Block 类型 | skyvern/schemas/workflows.py | BlockType |
| ②工作流 Block 实现 | skyvern/forge/sdk/workflow/models/block.py | Block 各子类 |
| ③2.0 规划器 | skyvern/services/task_v2_service.py | run_task_v2 / run_task_v2_helper |
| ③规划器 · 子任务生成 | skyvern/services/task_v2_service.py | _generate_navigation_task / _generate_loop_task / _generate_extraction_task |
| 公共 SDK 入口 | skyvern/library/skyvern.py | Skyvern.run_task / Skyvern.run_workflow / Skyvern.local |
| 浏览器驱动 · 状态 | skyvern/webeye/browser_state.py | BrowserState |
| 浏览器驱动 · 工厂 | skyvern/webeye/browser_factory.py | BrowserFactory |