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>複数 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と同時指定不可
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)
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。詳細は --help。
pages のみで使える。指定時は既定の HTML-or-null ベースフィルタを外し、PDF など非 HTML 行も列挙する。カテゴリ判定は @nitpicker/query の classifyContentType ルール表に集約され、Summary チャートと Pages フィルタが同じ行を同じカテゴリに数える。
.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 万行規模をクライアント全件ロードせず一定メモリで表示する。
<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"] }
}
}ツール一覧(14 種)と引数仕様は @nitpicker/mcp-server の src/tools/ の JSDoc を参照。
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 経路は閉じている。新規 archive を作るかフルクロールで作り直すこと。
--append の失敗時復元自体が失敗した状態。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 を直接指定する。