extract について
extractは、ウェブサイトから構造化データを取得するスキルで、繰り返し実行できる抽出処理を必要とする場面に向いています。単独で動くPlaywrightスクリプトとデータファイルを作成し、遅延読み込み、スクロール、ページ送りにも対応します。
主な機能
- 01単独で動作するPlaywrightスクリプトの生成
- 02JSON、CSVなどへの出力
- 03遅延読み込みとハイドレーションへの対応
- 04仮想リストと無限スクロールへの対応
- 05ページ送りと出力検証
活用シーン
- 01商品価格や評価の収集
- 02検索結果から著者名やタイトルを抽出
- 03求人一覧やテーブルデータの収集
- 04複数ページやスクロール読み込み分の取得
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ actionbook/ actionbook --skill extractインストール後、Claude Code で/extractを入力して読み込まれたか確認します。
詳細
このスキルを使用するタイミング
ユーザーがウェブサイトからデータを取得したい場合に有効化します。
- 「このページからすべての商品価格を抽出してください」
- 「...の検索結果テーブルをスクレイピングしてください」
- 「arXiv の検索結果から著者名とタイトルの一覧を取得してください」
- 「このページからすべての求人情報を収集してください」
- 「このダッシュボードのテーブルからデータを取得してください」
- 「...からレビューのスコアを収集してください」
- 「...からすべてのリンク/画像/カードをダウンロードしてください」
成果物は常に2つです。
- 実行可能な Playwright スクリプト — Actionbook を実行時に使用せずに抽出を再現する、単独で実行可能な
.cjsファイル。 - 抽出データ — 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
セレクターの取得元の優先順位:
| 優先度 | 取得元 | 使用する状況 |
|---|---|---|
| 1 | actionbook get | サイトがインデックス済みで、ヘルススコアが70%以上 |
| 2 | actionbook browser snapshot | インデックスされていない、またはセレクターが古い |
| 3 | スクリーンショットとスナップショットによる DOM の調査 | 複雑な SPA/動的コンテンツ |
譲れないルール: search + get ですでに必要なフィールドに使用できるセレクターが得られている場合は、get のセレクターから開始し、デフォルトで完全なフォールバック(snapshot/screenshot)に移行しないでください。例外として、実行時の挙動がスクリプトの正確性に影響する可能性がある場合は、軽量な仕組みの確認(ハイドレーション/仮想化/ページネーションの確認)が許可されます。セレクターの不足や不安定さがプローブ/サンプルの検証で示された場合に限り、snapshot/screenshot に進んでください。
仕組みを考慮したスクリプト戦略
ウェブサイトでは、単純なスクレイピングを妨げるパターンが使われています。生成する Playwright スクリプトでは、次の点を必ず考慮してください。
ストリーミング/SSR/RSC のハイドレーション
ページでは、まずシェルが描画され、その後にコンテンツがストリーミングまたはハイドレーションされることがあります。
// 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]');
});
検出の手がかり: data-reactroot を持つ React ルート、Next.js の __NEXT_DATA__、JavaScript の実行後に内容が追加される空のコンテナなどです。actionbook browser text "<selector>" が空を返す一方、スクリーンショットにはコンテンツが表示されている場合は、ハイドレーションが完了していません。
仮想化リスト/仮想 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;
}
検出の手がかり: ページの JavaScript に 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のセレクターがサンプル実行で空の値、または不安定な値を返す。- 実行時の動作が想定される仕組みと一致しない(例:仮想化コンテナー、遅延ハイドレーション)。
ステップ 4:Playwright スクリプトを生成する
以下の処理を行う、単体で実行可能な Playwright スクリプト(extract_<domain>_<slug>.cjs)を作成します。
- 対象 URL に移動します。
- 適切な準備完了シグナルを待ちます(
loadだけでは不十分です。上記の仕組みを参照してください)。 - 検出した仕組み(仮想スクロール、ページネーションなど)に対応します。
- データを構造化オブジェクトに抽出します。
- 出力をディスクに書き込みます(
JSON.stringify/CSV)。 - ブラウザーを閉じます。
- 無限ループを避けるため、ガードレール(
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 | 少なくとも 1 件の項目が抽出されている |
| null/空のフィールドがない | 宣言されたすべてのフィールドに、レコードの 90%以上で値がある |
| データがページと一致する | actionbook browser text を使って、最初と最後のレコードを抜き取り確認する |
検証に失敗した場合は、出力を調べ、セレクターまたは待機方法を調整して再実行します。
ステップ 6:提供する
ユーザーに以下を提示します。
- スクリプトのパス — いつでも再実行できる
.cjsファイル。 - データのパス — 出力 JSON/CSV ファイル。
- レコード数 — 抽出された項目の数。
- 注記 — 仕組みに固有の注意点(例:「このサイトは無限スクロールを使用しており、スクリプトはデフォルトで最大 50 ページ分スクロールします」)。
出力仕様
extract を実行するたびに、以下が生成されます。
| 成果物 | パス | 形式 |
|---|---|---|
| Playwright スクリプト | ./extract_<domain>_<slug>.cjs | playwright を使用する、単体で実行可能な Node.js スクリプト |
| 抽出データ | ./output.json(デフォルト)またはユーザー指定のパス | オブジェクトの JSON 配列(デフォルト)、CSV、またはユーザー指定の形式 |
スクリプトは再実行可能でなければなりません。実行環境に Node.js と Playwright があれば、Actionbook をインストールしていない状態でも、ユーザーが後から実行できる必要があります。
セレクターの優先順位
actionbook get で複数の種類のセレクターが利用できる場合:
| 優先順位 | 種類 | 理由 |
|---|---|---|
| 1 | data-testid | 安定性が高く、テスト向けで、変更されることがほとんどない |
| 2 | aria-label | アクセシビリティに基づき、意味が明確 |
| 3 | CSS セレクター | 構造に依存するため、デザイン変更で動作しなくなる可能性がある |
| 4 | XPath | 最終手段であり、最も壊れやすい |
エラー処理
| エラー | 対応 |
|---|---|
actionbook search で結果が返されない | snapshot + screenshot にフォールバックする |
| セレクターが 0 件の要素を返す | 再度スナップショットを取得し、スクリーンショットと比較してセレクターを更新する |
| スクリプトがタイムアウトする | waitForTimeout を長くし、ボット対策を確認する |
| データが不完全(一部フィールドが空) | コンテンツが遅延読み込みかどうかを確認し、スクロール/待機を追加する |
| ボット対策/CAPTCHA | ユーザーに知らせ、headless: false での実行、または actionbook setup の拡張機能モードを使ってユーザー自身のブラウザーセッションを利用するよう提案する |