Web サイト全体のデータを取得する CLI / MCP / Viewer。ヘッドレスブラウザで各ページをレンダリングし、loading=lazy や IntersectionObserver 起因の遅延読み込みを末尾までスクロールして網羅的に取得する。
設計詳細・データフロー・DB スキーマは ARCHITECTURE.md を参照。各コマンドのオプション一覧は --help で確認できる。
- サイトマップと URL 一覧、各ページのメタデータ
- 各ネットワークリクエスト/レスポンスヘッダー
- レンダリング後 DOM の HTML スナップショット
Web サイトをクロールして .nitpicker アーカイブを生成。
npx @nitpicker/cli crawl <URL> [<URL>...]
npx @nitpicker/cli crawl existing.nitpicker --append <URL>
npx @nitpicker/cli crawl existing.nitpicker --retry-failed複数 URL を渡すと、それぞれが「再帰クロールの起点」かつ「スコープエントリ」として扱われ、1 つの .nitpicker に集約される。スコープ判定は (hostname, port, path) のトリプル。同一ホスト異ポート(localhost:3000 と localhost:8080)は別スコープとして隔離される。
--appendの URL 群が新しい再帰起点としてinfo.rootsに追加される- 既存
externalページのうち拡張後のスコープに該当するものはinternalとして再スクレイプされる - クロール開始前に
<archive>.bakを作成。失敗時は自動復元、成功時は.bakを削除 - list-mode archive(
--list/--list-fileで作成)への append は不可 --resume/--diff/--output/--list/--list-file/--singleと同時指定不可
サーバー側で取得した URL リストファイル(1 行 1 URL、空行 / # コメント可)と既存
.nitpicker を突き合わせ、まだアーカイブに無い URL だけを取り込む モード。
クロールでは到達できなかった「孤立 LP」「使われていない置きっぱなしファイル」を
浮かび上がらせる用途。
npx @nitpicker/cli crawl <archive> --inventory <urls.txt>- HTML 応答は puppeteer で描画して再帰クロールに乗せ、新規 page を
'inventory-seed'、 そこから follow したリンク先を'inventory-discovered'のラベルで保存する - 非 HTML 応答(PDF / 画像 / CSS / JS …)は HEAD のみで
resourcesに直接登録、ラベルは'inventory-seed' - 既存
pages/resourcesにすでにある URL は skip(2 回目以降の--inventoryは新規分だけ処理) - スコープ外 URL は警告して skip
- 結果は
query isolated-pages/query unused-resourcesで見るのが想定動線(後述) --append/--retry-failed/--resume/--diff/--output/--list/--list-file/--singleと同時指定不可
前回クロールで失敗したページだけを再取得する。「サーバ側の一時障害やタイムアウトで取りこぼしたページを、フルクロールし直さずに回収する」ためのモード。
- 再取得対象は status が
-1(ネットワークエラー/タイムアウト/ブラウザクラッシュの sentinel)/NULL、content-type がNULL、または status が 5xx のページ。確定応答である 4xx は再取得しても結果が変わらないため対象外 - さらに、最終エラーメッセージが永続失敗 kind(
dns/tls/client-blocked/parse-error/connection-refused)に分類されるページは 対象から除外される。NXDOMAIN や証明書失効、Chromium のブロック判定、HTTP パースエラーなどは再試行しても同じ結果しか返さないため、--retry-failedを繰り返すほどリトライ対象が縮んでいく(収束する)。一過性のネットワーク要因や middlebox 由来のタイムアウトは引き続きリトライ対象 internal/external両方を対象にする(external はメタデータのみ再取得)- 再取得したページが HTML なら新リンクを辿り、未取得の新 URL があればそこから再帰クロールする(デフォルト ON)。
--no-recursiveで「失敗ページの再取得のみ」に限定できる。この recursive 設定はフラグ値が優先され、アーカイブ作成時の recursive 設定は継承しない - スコープ・除外(
--exclude系)・User-Agent などは アーカイブに保存された設定値を流用する。明示的にフラグ指定したものだけ上書きされる - クロール開始前に
<archive>.bakを作成。失敗時は自動復元、成功時は.bakを削除 - list-mode archive への retry は不可
--resume/--append/--diff/--output/--list/--list-file/--singleと同時指定不可
https://USER:PASS@host/ 形式。スコープエントリの資格情報は同じ (hostname, port) 配下にのみ注入される(他ホストへの漏洩防止)。
| code | 意味 |
|---|---|
0 |
成功 |
1 |
致命的(引数不足、内部エラー、スコープ内ページのスクレイプ失敗) |
2 |
警告(外部リンクエラーのみ。--strict で 1 に格上げ) |
CI/CD で外部リンクの一時的障害でビルドを落としたくない場合は || [ $? -eq 2 ] で 2 を許容できる。
robots.txt自動準拠(--ignore-robotsで無効化可能、慎重に)- User-Agent デフォルト
Nitpicker/<version>(--user-agent変更可) --intervalでリクエスト間隔(ms)
v0.10 で archive フォーマットが大きく変わった (#84 で HTML を snapshot-html.zip
から SQLite BLOB に移行、#85 で beholder 3.0.0 の nested Meta を受けて pages テーブルを
リシェイプ + page_tags / page_jsonld 追加)。新 CLI は info.version >= 0.10.0
を要求し、それより古い archive は IncompatibleArchiveError で拒否する。
移行は下記スクリプトを 一度だけ 走らせる:
# リポジトリを clone した状態で
yarn install
yarn build # Step C が crawler の compiled lib/ から meta 派生 helper を import するため必須
node scripts/migrate-to-0.10.mjs <old.nitpicker> [<new.nitpicker>]スクリプトは入力の状態を検知して必要なステップだけ実行する(HTML→BLOB がまだなら
それも、メタスキーマだけ古いならそれだけ)。さらに最後のステップとして、保存済み
HTML BLOB を jsdom でパースして beholder 3.1.0 の extractMetaFromDocument に渡し、
0.10 で追加した meta 列・meta_extras・page_tags・page_jsonld を新規 crawl と
同等の状態に充填する。最後に info.version を 0.10.0 に bump する。出力先を
省略すると <old>.0.10.nitpicker を入力と同じディレクトリに作る (元ファイルは
触らない)。10 万ページ規模で実行時間は 数時間〜十数時間オーダー (jsdom + Wappalyzer
が per-page CPU bound)。スクリプトは @nitpicker/cli の npm パッケージには含まれて
いないので、移行が必要な場合は git clone してリポジトリルートから実行する。
npx @nitpicker/cli analyze <file> [--plugin <name>]... [--all]axe / Lighthouse / markuplint / textlint / main-contents / search プラグインを実行し、結果をアーカイブに書き戻す。
.nitpicker を Google Sheets に出力。
npx @nitpicker/cli report <file> --sheet <URL> [--all] [--dedupe-resources]Google Sheets は 1 ドキュメント 10M セル上限。広告/解析タグ(Google Ads / Facebook Pixel / Yahoo / Bing UET / LINE Tag 等)はページごとに per-request unique なクエリ付き URL を生成するため、数百万件のレコードで上限に達することがある。
--dedupe-resources は Resources シートを canonical URL で集約する:
- canonical 化: パス保持、クエリは値を捨ててキーのみ sort & unique(例:
?auid=XYZ&capi=1→?auid&capi) - 集約キー:
(canonical URL, status, contentType) - 追加列:
Count(集約件数)とQuery Pattern(各キーのユニーク値数key=N、N=1は実質定数、N>1は per-request 変動、上限 100 超はkey=100+) - path 内 ID は保持:
/pagead/viewthroughconversion/10840516367/のような conversion ID は残るので、「Google Ads 入っていますか / どの conversion ID?」がこの 1 シートで答えられる - 値そのものは記録しない(プライバシー / メモリ配慮)
実例: 1.6M raw → 63K 行(96% 削減、9.6M セル → 380K セル)。
raw / dedupe 両モードで URL の自然順(image-2.jpg が image-10.jpg より先、大文字小文字同視)に並ぶ。実装は Martin Pool の strnatcmp の JS 移植で、文字コードを直接比較する on-the-fly 方式。派生文字列を生成しないため大規模アーカイブでもメモリを浪費しない。
1.6M リソース級では getResources() 結果配列だけで約 1〜1.5GB。Node デフォルトヒープ (4GB) では厳しいので NODE_OPTIONS=--max-old-space-size=8192 を指定:
NODE_OPTIONS=--max-old-space-size=8192 npx @nitpicker/cli report ./archive.nitpicker -S ... --dedupe-resources10 万件規模なら 1〜2GB で収まり、デフォルトで動く。
アーカイブからメモリへ一度に読み込むページ数を制御する値。Google Sheets API への 1 リクエストあたりの行数とは別物。各シートの行送信は @d-zero/google-sheets の Sheet.appendRow が内部で 2500 行ごとに自動 flush するため、呼び出し元のメモリ滞留はチャンクサイズ分に抑えられる。
crawl → analyze → report を 1 コマンドで直列実行。--sheet を指定したときのみ report ステップが走る。
npx @nitpicker/cli pipeline <URL> --sheet <URL> --all各ステップに対応するフラグが自動でルーティングされる。終了コードは crawl と同じ体系。
.nitpicker に対してクエリを実行して JSON 出力。MCP サーバーと同等。
npx @nitpicker/cli query <file> <sub-command> [options]サブコマンド: summary / pages / page-detail / html / links / resources / images / violations / duplicates / mismatches / headers / resource-referrers / error-kinds。詳細は --help。
pages のみで使える。指定時は既定の HTML-or-null ベースフィルタを外し、PDF など非 HTML 行も列挙する。カテゴリ判定は @nitpicker/query の classifyContentType ルール表に集約され、Summary チャートと Pages フィルタが同じ行を同じカテゴリに数える。
クロール失敗を原因別に集計する。kind の網羅集合は @nitpicker/crawler の ErrorKind union(packages/@nitpicker/crawler/src/types.ts)が正で、現在は dns / dns-transient / connection-refused / connection-reset / connection-timeout / tls / local-network / parse-error / client-blocked / timeout / protocol / unknown の 12 種。DNS 解決失敗や Chromium のブロック判定、HTTP パースエラーが、ただの「タイムアウト/不明」に埋もれず区別できる。kind 別件数 + ホスト別内訳 + サンプル URL を JSON で返す。
原因の判定(classifyErrorKind)は 保存された値ではなく message から読み取り時に行うため、この機能より前に作成した既存アーカイブもそのまま分類できる。失敗の取得元は 2 系統: スクレイプ経路の失敗は page_errors、DNS/接続/TLS など crawler レベルの失敗は構造化テーブル crawl_errors(無い古いアーカイブでは error.log を読んでフォールバック)。応答しないページが per-property の累積タイムアウトで timeout に倒れる現象は beholder 側の既知課題(getMeta の逐次取得)。
.nitpicker または stub ディレクトリ(crawl 強制停止時の ._nitpicker-*)をローカルブラウザで対話的に閲覧する。Hono バックエンド + React SPA。
npx @nitpicker/cli viewer <file-or-stub-dir> [--port 9000] [--no-open]crawl --resume <stub> と同じパスを viewer <stub> に渡せば、その時点までに集めたデータを read-only オープン(Archive.connect)で閲覧できる。.nitpicker ファイルへの tar 化も tmpDir 削除も info マイグレーションも一切走らないため、その後 crawl --resume を安全に続行できる。Footer に "Live crawl in progress (PID xxx)" / "Interrupted crawl stub" のバッジが出る(<tmpDir>.lock/pid.txt を probe して判定)。
ページ一覧は サーバ側ページネーション(limit/offset)+ TanStack Query infinite query + TanStack Virtual の組み合わせで、10 万行規模をクライアント全件ロードせず一定メモリで表示する。
query error-kinds と同じ集計をブラウザで表示する。kind 別のバーを選ぶと、その原因で失敗したホスト内訳とサンプル URL にドリルダウンできる。サンプル URL は(解決失敗・接続拒否など本来開けない URL なので)リンクではなく診断用のテキストとして表示する。
<iframe sandbox>(allow-same-origin / allow-scripts なし)でレンダリングするためローカルでも安全。ソース表示にも切り替え可。
WCAG 2.1 AA 目標。仮想テーブルは flexbox レイアウトで table セマンティクスがアクセシビリティツリーから剥がれるため、ARIA ロール(table/row/columnheader/cell)と aria-rowcount/aria-colcount を明示付与する設計。web/components/virtual-table.tsx のロール属性を削除すると画面読み上げで「ただのテキストの羅列」に退行するため必須。検証は yarn workspace @nitpicker/viewer test:e2e の a11y 専用テスト群でカバー。
.nitpicker を AI アシスタント(Claude 等)から直接クエリする Model Context Protocol サーバー。
claude_desktop_config.json:
{
"mcpServers": {
"nitpicker": { "command": "npx", "args": ["@nitpicker/mcp-server"] }
}
}ツール一覧(22 種)と引数仕様は @nitpicker/mcp-server の src/tool-definitions.ts の description / JSON Schema を参照。v2 で追加された Wappalyzer / JSON-LD 系(list_pages_by_tag / count_pages_by_tag / list_pages_by_jsonld_type / count_pages_by_jsonld_type / get_tag_inventory / get_page_tags / get_page_jsonld / get_page_jsonld_overview)は MB 級になり得るので、大規模サイトでは事前に count_* で size-check し、本体は CLI nitpicker query <subcommand> + jq 経由を推奨。
LLM が data の鮮度を判定できるよう mode と crawlerPid を常に同梱する。
| field | type | 説明 |
|---|---|---|
archiveId |
string | 以降のツール呼び出し識別子 |
mode |
"archive" | "stub" |
"stub" は point-in-time snapshot |
crawlerPid |
number | null |
stub かつ live crawler 検出時に PID。null なら interrupted stub または finished archive |
重要(LLM 側の判断): mode === "stub" で crawlerPid !== null の場合、list_pages / get_summary の結果は刻々と変わる可能性があるため、ユーザーへの返答でその点を明示すること。
別プロセスが同じ archive を開いている。.lock/pid.txt の PID を ps -p <PID> で確認し、稼働中なら終了を待つ。既に終了していれば次回 open 時に stale 検出で自動回復。それでも残れば rm -rf <path>.lock で手動削除可能。
--list / --list-file で作成された archive(info.fromList=true)は再帰クロールの土台にならないため append / retry 経路は閉じている。新規 archive を作るかフルクロールで作り直すこと。
--append / --retry-failed の失敗時復元自体が失敗した状態。AggregateError がログに残るはず。mv <archive>.bak <archive> で原本を復元できる。
旧版スキーマの archive を初めて開いたときの 正常通知。info.roots 追加と info.scope 削除の冪等 migration。viewer / mcp-server は Archive.connect({readOnly: true}) 固定のため migration は走らず、この行は出ない(user の tmpDir を絶対に書き換えない設計)。
viewer <stub-dir> / MCP open_archive <stub-dir> 時の 正常通知。read-only オープンなので WAL モード SQLite の concurrent read は安全。peekArchiveLockHolder は lock を取りに行かず PID を probe するだけなので crawler を妨げない。PID liveness は one-shot snapshot で、起動後の変化は footer に反映されない(再起動が必要)。
viewer が stub を開いている間に同じ archive で crawl --resume <stub> を完走させると、crawler は tmpDir を .nitpicker tar に変換して tmpDir を削除する。viewer 側は SQLite ハンドルが宙吊りになり後続クエリが I/O エラー。対処: viewer を Ctrl-C で停止 → 生成された .nitpicker で開き直す。viewer は「stub の状態を変更しない」ことしか保証しない設計判断。
.nitpicker 拡張子の symlink が directory を指している場合のエラー。viewer / MCP は 入力パスの拡張子で user intent を判定するため、current.nitpicker -> ._nitpicker-foo/ のような構成は意図的に拒否される(archive を期待しているのに stub が返るのは plugin data の欠落を生むため)。symlink を貼り直すか stub を直接指定する。