mcpField
E
extract

extract

github.com/actionbook数据与数据库1.6k2.3k 次安装1 周前更新
代码仓库

关于 extract

extract 是一项用于从网站提取结构化数据的技能,面向需要可重复采集流程的用户。每次任务会生成可独立运行的 Playwright 脚本和 JSON、CSV 等数据文件,并根据页面机制处理加载、滚动或分页。

主要功能

  1. 01生成独立运行的 Playwright 脚本
  2. 02导出 JSON、CSV 等数据
  3. 03处理延迟加载与页面水合
  4. 04处理虚拟列表和无限滚动
  5. 05支持分页采集与结果校验

适用场景

  1. 01采集商品价格或评分
  2. 02提取搜索结果中的作者和标题
  3. 03收集职位列表或表格数据
  4. 04抓取多页或滚动加载的数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/actionbook/actionbook --skill extract

安装后在 Claude Code 中输入/extract确认已加载。

详细介绍

何时使用此技能

当用户希望从网站获取数据时启用:

  • “提取此页面上的所有产品价格”
  • “抓取……中的结果表格”
  • “从 arXiv 搜索结果中获取作者和标题列表”
  • “收集此页面上的所有职位信息”
  • “获取此仪表盘表格中的数据”
  • “收集……中的评论评分”
  • “下载……中的所有链接/图片/卡片”

交付物始终包含两项内容:

  1. 可执行的 Playwright 脚本 — 独立的 .cjs 文件,无需在运行时使用 Actionbook 即可重现数据提取。
  2. 提取的数据 — 写入磁盘的 JSON(默认格式)、CSV 或用户指定的格式。

决策策略

将 Actionbook 用作有条件的加速工具,而非必经步骤。目标是以最短路径找到可靠的选择器。

User request
  │
  ├─► actionbook search "<site> <intent>"
  │     ├─ Results with Health Score ≥ 70%  ──► actionbook get "<ID>" ──► use selectors
  │     └─ No results / low score  ──► Fallback
  │
  └─► Fallback: actionbook browser open <url>
        ├─ actionbook browser snapshot   (accessibility tree → find selectors)
        ├─ actionbook browser screenshot (visual confirmation)
        └─ manual selector discovery via DOM inspection

选择器来源的优先顺序:

优先级来源适用情况
1actionbook get网站已建立索引,健康度评分 ≥ 70%
2actionbook browser snapshot网站未建立索引或选择器已过时
3通过截图 + 快照检查 DOM复杂 SPA/动态内容

**不可妥协的规则:**如果 search + get 已为所需字段提供可用的选择器,就从 get 选择器开始,默认不要直接转向完整回退方案(snapshot/screenshot)。例外:当运行时行为可能影响脚本正确性时,允许进行轻量机制探测(用于检查 hydration/虚拟化/分页)。只有当探测/样本验证显示选择器存在缺口或不稳定时,才升级到 snapshot/screenshot。

根据机制调整脚本策略

网站会使用一些会导致朴素抓取失效的模式。生成的 Playwright 脚本必须考虑以下情况:

流式传输/SSR/RSC hydration

页面可能先渲染外壳,然后再流式传输或 hydration 内容。

// Wait for hydration to complete — not just DOMContentLoaded
await page.waitForSelector('[data-item]', { state: 'attached' });
await page.waitForFunction(() => {
  const items = document.querySelectorAll('[data-item]');
  return items.length > 0 && !document.querySelector('[data-pending]');
});

**检测线索:**React 根节点带有 data-reactroot、Next.js __NEXT_DATA__,或空容器在 JS 运行后填充。如果 actionbook browser text "<selector>" 返回空内容,但截图显示了内容,则 hydration 尚未完成。

虚拟化列表/虚拟 DOM

DOM 中只存在可见行。滚动会渲染新行并销毁旧行。

// Scroll-and-collect loop for virtualized lists (scroll container aware)
const allItems = [];
const maxScrolls = 50;
let scrolls = 0;

const container = await page.$('<scroll-container-selector>');
if (!container) throw new Error('Scroll container not found');

let previousTop = await container.evaluate(el => el.scrollTop);
while (scrolls < maxScrolls) {
  const items = await page.$$eval('[data-row]', rows =>
    rows.map(r => ({ text: r.textContent.trim() }))
  );
  for (const item of items) {
    if (!allItems.find(i => i.text === item.text)) allItems.push(item);
  }

  await container.evaluate(el => el.scrollBy(0, 600));
  await page.waitForTimeout(300);

  const currentTop = await container.evaluate(el => el.scrollTop);
  if (currentTop === previousTop) break;

  previousTop = currentTop;
  scrolls += 1;
}

**检测线索:**容器具有固定高度并设置了 overflow: auto/scroll;DOM 中的行数远少于标明的总数;行具有 transform: translateY(...) 或 position: absolute; top: ...px。

无限滚动/延迟加载

用户滚动到页面底部附近时会追加新内容。

// Scroll to bottom until no new content loads (with no-growth tolerance)
let itemCount = 0;
let noGrowthStreak = 0;
const maxScrolls = 80;
let scrolls = 0;

while (scrolls < maxScrolls && noGrowthStreak < 3) {
  await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
  await page.waitForTimeout(1200);

  const newCount = await page.$$eval('.item', els => els.length);
  if (newCount > itemCount) {
    itemCount = newCount;
    noGrowthStreak = 0;
  } else {
    noGrowthStreak += 1;
  }

  scrolls += 1;
}

**检测线索:**页面 JS 中存在 Intersection Observer、“加载更多”按钮、底部的哨兵元素,或滚动时触发网络请求。

分页

多页结果通过“下一页”按钮或编号页面展示。

// Click-through pagination (navigation-aware, SPA-safe)
const allData = [];
const maxPages = 50;
let pageIndex = 0;
while (pageIndex < maxPages) {
  const pageData = await page.$$eval('.result-item', items =>
    items.map(el => ({ title: el.querySelector('h3')?.textContent?.trim() }))
  );
  allData.push(...pageData);

  const nextBtn = await page.$('a.next-page:not([disabled])');
  if (!nextBtn) break;

  const previousUrl = page.url();
  const previousFirstItem = await page
    .$eval('.result-item', el => el.textContent?.trim() || '')
    .catch(() => '');

  await nextBtn.click();

  // Post-click detection only: advance must be caused by this click
  const advanced = await Promise.any([
    page
      .waitForURL(url => url.toString() !== previousUrl, { timeout: 5000 })
      .then(() => true),
    page
      .waitForFunction(
        prev => {
          const first = document.querySelector('.result-item');
          return !!first && (first.textContent || '').trim() !== prev;
        },
        previousFirstItem,
        { timeout: 5000 }
      )
      .then(() => true),
  ]).catch(() => false);

  if (!advanced) break;

  await page.waitForLoadState('networkidle').catch(() => {});
  pageIndex += 1;
}

执行流程

第 1 步:了解目标

从用户请求中确定:

  • URL — 要提取内容的页面
  • 数据结构 — 所需字段/列
  • 范围 — 单页、分页、无限滚动或多页抓取
  • 输出格式 — JSON(默认格式)、CSV 或其他格式
第 2 步:获取选择器并选择执行路径
# Try Actionbook index first
actionbook search "<site> <data-description>" --domain <domain>

# If good results (health ≥ 70%), get full selectors
actionbook get "<ID>"

严格按照以下路径执行:

  • **路径 A(get 质量良好时的默认路径):**所需字段均由 get 选择器覆盖,且质量可接受。

    • 从 get 选择器开始,尽快起草脚本。
    • 在确定脚本策略前,可以进行轻量机制探测(browser text、快速滚动检查)。
    • 除非探测/样本验证显示不匹配,否则在首次起草前不要运行完整回退方案(snapshot/screenshot)。
    • 字段映射默认使用 get 选择器,并将来源标记为 actionbook_get。
  • **路径 B(部分覆盖/不稳定):**存在 get,但缺少所需字段、选择器匹配到 0 个元素,或验证失败。

    • 只针对失败的字段/步骤运行定向回退。
  • **路径 C(无可用覆盖):**search/get 没有可用结果。

    • 运行完整回退发现流程。
步骤 3:探测页面机制,仅在必要时使用备用方案

路径 A 的机制检测时机:

  • 在最终脚本草稿完成之前或样本验证期间运行最小化探测。
  • 运行任何探测命令之前,确保已打开正确的页面上下文:
    • actionbook browser open "<url>"(如果当前标签页上下文未知或已过期)
  • 如果探测或样本运行结果表明存在不匹配(缺少行、选择器不稳定、分页行为不正确),则升级到路径 B,进行有针对性的备用处理。

按路径进行备用发现:

路径 B:有针对性的备用处理(仅处理失败的字段/步骤):

actionbook browser open "<url>"     # if not already open
actionbook browser snapshot          # focus on failed field/container mapping
# actionbook browser screenshot      # optional visual confirmation for failed area

路径 C:完整备用处理(没有可用覆盖):

actionbook browser open "<url>"
actionbook browser snapshot
actionbook browser screenshot

机制探测(脚本策略需要确认时运行):

# Hydration / streaming check
actionbook browser text "<container-selector>"

# Infinite scroll quick signal (explicit before/after decision)
actionbook browser eval "document.querySelectorAll('<item-selector>').length"   # before
actionbook browser click "<scroll-container-selector-or-body>"                    # focus scroll context
actionbook browser eval "const c=document.querySelector('<scroll-container-selector>') || document.scrollingElement; c.scrollBy(0, c.clientHeight || window.innerHeight);"
actionbook browser eval "document.querySelectorAll('<item-selector>').length"   # after
# If count increases, treat page as lazy-load/infinite-scroll.

备用方案触发条件:

  • actionbook get 无法映射所有必需字段。
  • actionbook get 选择器在样本运行中返回空值或不稳定的值。
  • 运行时行为与预期机制冲突(例如虚拟化容器、延迟 hydration)。
步骤 4:生成 Playwright 脚本

编写一个独立的 Playwright 脚本(extract_<domain>_<slug>.cjs),该脚本需要:

  1. 导航到目标 URL。
  2. 等待正确的就绪信号(而不只是 load——参见上文机制说明)。
  3. 处理检测到的机制(虚拟滚动、分页等)。
  4. 将数据提取为结构化对象。
  5. 将输出写入磁盘(JSON.stringify / CSV)。
  6. 关闭浏览器。
  7. 设置防护措施(maxPages、maxScrolls、超时预算),避免无限循环。

脚本模板:

// extract_<domain>_<slug>.cjs
// Generated by Actionbook extract skill
// Usage: node extract_<domain>_<slug>.cjs

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto('<URL>', { waitUntil: 'domcontentloaded' });

  // -- wait for readiness --
  await page.waitForSelector('<container>', { state: 'visible' });

  // -- extract --
  const data = await page.$$eval('<item-selector>', items =>
    items.map(el => ({
      // fields mapped from user request
    }))
  );

  // -- output --
  const fs = require('fs');
  fs.writeFileSync('output.json', JSON.stringify(data, null, 2));
  console.log(`Extracted ${data.length} items → output.json`);

  await browser.close();
})();
步骤 5:执行并验证

运行脚本,确认其正常工作:

node extract_<domain>_<slug>.cjs

验证规则:

检查项通过条件
脚本以 0 退出没有运行时错误
输出文件存在已写入非空文件
记录数 > 0至少提取到一项
没有 null/空字段每个已声明字段在 ≥ 90% 的记录中都有值
数据与页面一致使用 actionbook browser text 抽查第一条和最后一条记录

如果验证失败,请检查输出、调整选择器或等待策略,然后重新运行。

步骤 6:交付

向用户提供:

  1. 脚本路径——可随时重新运行的 .cjs 文件。
  2. 数据路径——输出 JSON/CSV 文件。
  3. 记录数——提取到的条目数量。
  4. 说明——与机制相关的注意事项(例如,“此网站使用无限滚动;脚本默认最多滚动 50 页”)。

输出约定

每次 extract 调用都会生成:

产物路径格式
Playwright 脚本./extract_<domain>_<slug>.cjs使用 playwright 的独立 Node.js 脚本
提取的数据./output.json(默认)或用户指定的路径对象数组(默认 JSON 格式)、CSV,或用户指定的格式

脚本必须可重复运行——只要运行环境中有 Node.js + Playwright,用户之后就应能在未安装 Actionbook 的情况下执行脚本。

选择器优先级

当 actionbook get 提供多种选择器类型时:

优先级类型原因
1data-testid稳定,面向测试,很少变动
2aria-label以无障碍为导向,语义明确
3CSS 选择器结构性选择器,重新设计后可能失效
4XPath最后的选择,最脆弱

错误处理

错误操作
actionbook search 未返回结果使用 snapshot + screenshot 作为备用方案
选择器返回 0 个元素重新生成快照,与截图对比并更新选择器
脚本超时增加 waitForTimeout 时长,检查是否存在反机器人措施
数据不完整(某些字段为空)检查内容是否延迟加载;添加滚动/等待
反机器人措施 / CAPTCHA告知用户;建议使用 headless: false 运行,或通过 actionbook setup 扩展模式使用用户自己的浏览器会话

评论

支持 Markdown