CrawlCheck について
CrawlCheckの依存関係のないコアは、クローラー方針、ボットの識別、引用可能なコンテンツ、エージェント向けのページ要素を読み取ります。測定をローカルで再現する開発者向けで、スコアリングやクローラーとしての取得、監視、履歴管理はホステッドサービスが担います。
主な機能
- 01robots.txtのクローラールール解析
- 02アクセス可否とルールの遮蔽確認
- 03IP範囲によるボット識別の検証
- 04引用可能なコンテンツ信号の抽出
- 05Markdownとエージェント向け要素の検査
- 06JavaScriptおよびPython SDK
活用シーン
- 01robots.txtが許可するクローラーの監査
- 02アクセスログ上のボットを検証できるか確認
- 03回答エンジンが引用できるページ内容の測定
- 04クローラー向けページの読み取りをローカルで再現
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://crawlcheck.io/mcpclaude mcp add --transport http emmanuelorta-crawlcheck https://crawlcheck.io/mcp{"mcpServers":{"emmanuelorta-crawlcheck":{"type":"http","url":"https://crawlcheck.io/mcp"}}}Cursor で開く{"servers":{"emmanuelorta-crawlcheck":{"type":"http","url":"https://crawlcheck.io/mcp"}}}VS Code で開く{"mcpServers":{"emmanuelorta-crawlcheck":{"type":"http","url":"https://crawlcheck.io/mcp"}}}{"mcpServers":{"emmanuelorta-crawlcheck":{"serverUrl":"https://crawlcheck.io/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
24| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| agent | string | いいえ | クローラーの user-agent トークン。例:GPTBot |
| action | enum | いいえ | これから実行する操作(デフォルトは読み取り)read | cite | connect | transact | administer |
| domain | string | はい | ドメイン名のみ。例:example.com |
| policy | object | いいえ | max_age_hours, on_warn (warn|require_confirmation|block), require_entity, human_approval[], allow[], deny[] |
| template | string | いいえ | safe_citation, safe_data_retrieval, safe_api_connect, safe_mcp_tool, safe_commerce, safe_oauth or support_escalation |
詳細
crawlcheck-core
ウェブエージェント向けの検証レイヤーであるCrawlCheckの、依存関係のないモジュールを本番サービスから抽出したものです。サイトがAIクローラーやエージェントに何を許可し、何を提供しているかについて、署名付きの回答を支えるリーダーです。ビルド手順不要、依存関係なし、ESM、Node 20+。
ホステッドサービスは、12種類のクローラーの識別情報(ブラウザーと2つの制御リクエストも追加)でサイトを取得し、3つの段階にわたって47の項目を測定します。到達(クローラーがアクセスできるか)、読み取り(実際に何を受け取るか)、引用(回答エンジンがコンテンツを引用できるか)です。このリポジトリはオープンコアです。ネットワーク、ストア、キーが不要なリーダーとチェックを公開しているため、測定を再現できます。
現在含まれているもの: robots.txt ポリシーリーダー、クローラー識別情報/ログ行の偽造検証ツール、引用可能なコンテンツのリーダー、画像シグナルのリーダー、Markdown ネゴシエーションおよびエージェントサーフェスのプローブです。サービスに残るもの: スコアリング(各読み取りを評価するしきい値、評価における各項目の重み、数値の設定に使用したコーパスのキャリブレーション)、クローラーの識別情報での取得、経時的な記録、監視、データセット、課金です。ルールはシンプルです。ページの読み取り結果は、確認する権利のある事実であり、キャリブレーションこそが製品です。
このリポジトリには SDK も含まれています
sdk/—@crawlcheck/sdk(npm):CrawlCheck API 用の型付きクライアントであり、署名付き回答、証拠バンドル、レシートを検証する依存関係ゼロのオフライン検証ツールです。npm testで本番環境に対するテストスイートを実行します。sdk-python/—crawlcheck(PyPI):Python 3.9+ 向けの同じクライアントと検証ツールです。依存関係はありません。
どちらも、ディレクトリの内容が公開するパッケージとバイト単位で一致しない限り公開を拒否するワークフローを通じて、非公開のサービスリポジトリから公開されています。そのため、このコピーはパッケージのページが参照するソースであり、内容が乖離する可能性のあるミラーではありません。
インストール
npm install github:emmanuelorta/crawlcheck-core
まだ npm レジストリには公開されていないため、リポジトリからインストールしてください。以下のすべてのサブパスは、そのインストールから解決されます。
robots — robots.txt が実際に許可するクローラー
import { crawlerPolicy } from "crawlcheck-core/robots";
const policy = crawlerPolicy(robotsTxt, /* unreadable */ false, "/wp-admin/");
policy.agents.find(a => a.ua === "GPTBot");
// { ua:"GPTBot", label:"OpenAI training corpus", role:"train",
// allowed:true, allowed_root:true, named:true, rules_shadowed:true }
policy.shadowed;
// [{ uas:["gptbot"], missing:["/wp-admin/","/feed/"], missing_count:2 }]
名前が定義された114種類のエージェント(回答エンジン、検索インデックス、トレーニング用クローラー、SEO ツール、ソーシャル共有プレビュー、地域別エンジン)ごとに、どのユーザーエージェントグループが適用されるか(named)、そのグループのもとで指定されたパスとルートが許可されるか、そしてそのグループが * のルールを上書きするかどうかを報告します。
ルールの上書きによる不具合
クローラーは、最も具体的なグループのルールだけに従います。次のように記述した瞬間に、
User-agent: *
Disallow: /wp-admin/
User-agent: GPTBot
Allow: /
GPTBot には Disallow: /wp-admin/ が適用されなくなります。* グループは完全に適用対象から外れます。AI エージェントの許可リストを追加したサイトでは、依然有効だと思っていたすべてのルールが無効になることがよくあります。shadowedGroups() は、名前付きグループごとに、そのグループによって適用されなくなった * の Disallow ルールを一覧表示します。この問題は、まず私たち自身のサイトで見つかりました。AI クローラーをブロックする方法と、ユーザーエージェントグループの用語集項目をご覧ください。
API
| エクスポート | 説明 |
|---|---|
crawlerPolicy(txt, unreadable, path) | 1つのファイルに対する、エージェントごとの完全なポリシーです。ファイルが空または読み取れない場合は、理由とともに measured:false を返します。ポリシーが存在しない状態をポリシーとして報告することはありません |
parseGroups(txt) | RFC 9309 のグループです。連続する User-agent 行は1つのグループを共有し、Allow/Disallow ルールは記載順に並びます |
groupFor(groups, ua) | エージェントに適用されるグループです。エージェントトークンの最長プレフィックスが一致するものを選び、該当がない場合は * をフォールバックとして使います |
pathAllowed(group, path) / rootAllowed(group) | RFC 9309 に準拠した照合です(* は任意の位置で一致し、末尾の $ は末尾に固定します)。最長のルールが優先され、同じ長さの場合は Allow が Disallow に優先します |
ruleMatches(rule, path) / decide(rules, path) | マッチャー本体と、判定を行ったルールを含む判定結果です |
shadowedGroups(groups) | * による拒否が適用されない名前付きグループです(グループ独自のルール、たとえば Disallow: / で適用されるものを除きます) |
AGENTS | 114エージェントのテーブルです:[token, label, role] |
対応していないこと
Crawl-delay、Sitemap、不明なフィールドは無視され、パーセントエンコーディングは正規化されません(ルールとパスは記述どおりに比較されます)。パスの照合は RFC 9309 に準拠します。* は任意の文字列に一致し、末尾の $ は末尾に固定します。そのため、Disallow: / と Allow: /$ の組み合わせでは、ホームページのみが許可され、それ以外は許可されません。0.6.0 より前のバージョンでは * が削除され、$ は無視されていました。2026-09-30 に本番環境で変更が行われ、0.6.0 ではこのモジュールがサービスの読み取り方式に戻されました。テストでは、* ルールによって判定されるパスを含め、実際の robots.txt ファイルとの一致を確認します。
agents — 誰が取得したのか、そして申告どおりのエージェントなのか
import { identifyBot, loadRanges, verifyLines } from "crawlcheck-core/agents";
identifyBot("Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)"); // "GPTBot"
const ranges = await loadRanges(fetch); // the operators' published IP feeds (cache this for a day)
const r = verifyLines(accessLogText, ranges);
r.tally; // { verified, spoofed, unverifiable, no_bot }
r.forged_rate_pct; // spoofed / (verified + spoofed), or null when nothing is checkable
r.rows[0]; // { line, bot, ip, verdict, why }
判定は3種類で、2種類ではありません。検証済み:アドレスが、申告された運営者の公開する範囲内にあります。偽装:その運営者が公開するすべての範囲の外にあります。検証不能:運営者がフィードを公開していない(Common Crawl、ByteDance、Meta)、またはフィードを読み取れなかった場合です。この場合は偽装ではなく、検証不能として報告されます。偽装率の分母には検証済みと偽装のみを含めます。検証不能なアクセスを偽装として数えると率が過大になり、正当なものとして数えると過小になります。
| エクスポート | 説明 |
|---|---|
identifyBot(ua) | user-agent に含まれる名前付きクローラートークンです。最長一致を優先し、ブラウザーの場合は null を返します |
verifyLines(lines, ranges) | /api/tool/verify の背後にある検証関数です。ログ行の文字列または配列を受け取り、集計、率、行ごとの判定結果を返します |
verifyBotIP(bot, ip, ranges) | 1件のアクセスについて true/false/null を返します |
loadRanges(fetch) | 10個の運営者フィード(OpenAI ×3、Anthropic、Perplexity ×2、Google ×2、Bing、Apple)を、検証関数が読み取る形式で取得します。このパッケージでネットワークを呼び出すのはこれだけです。フィードがない運営者は検証不能になります |
ipInCidr(ip, cidr), ip4ToInt, ip6ToBig | 範囲の照合と、IPv4/IPv6(IPv4 マップドアドレスを含む)の処理です |
BOT_TOKENS, BOT_FEED_OF, BOT_IP_FEEDS, CRAWLERS | トークン、トークンからフィードファミリーへの対応、フィード URL、18行の運営者/役割/目的のテーブルです |
これが存在する理由:CrawlCheck 独自のテレメトリでは、かつて「GPTBot」による85件のアクセスのうち78件を偽装として数えていましたが、これらは自分たちのテストリクエストでした。フィードによって置き換えられた以前の確認方法は逆引き DNS です。user-agent は自己申告であり、それだけでは何も証明できません。
quotable — 回答エンジンがページから引用できる内容
import { quotable } from "crawlcheck-core/quotable";
const { signals } = quotable(html, "Acme Fencing LLC");
signals.lead_defines; // does the first sentence say what this IS
signals.answer_units; // paragraphs of 15-70 words that carry a fact
signals.quotable_share; // % of sentences an engine could attribute unedited
signals.faq_questions; // declared in FAQPage markup
signals.faq_visible; // ...and actually readable on the page
問題は、文章の出来がよいかどうかではありません。回答エンジンがこのページについて事実を述べようとしたとき、そのまま引用できる一文がページ上にあるかどうかです。なければ、エンジンが文を組み立てることになり、回答に使われるのはあなたの言葉ではなく、エンジンの言葉になります。
このモジュールが公開するのは、読み取り結果であり、採点ではありません。本番サービスがページごとに記録するすべての件数と割合はここで生成され、ライブスキャンとバイト単位で再現できます。これを行うのがパリティテストです。このテストは、ページのバイト列がフィクスチャの取得元と一致しない限り、比較を拒否します。サービス側に保持されるのは、読み取り結果を評価するための情報です。しきい値、総合評価におけるセクションの重み、そして数値の調整に使った数百サイト規模のコーパスパスです。
この違いはマーケティング上の言い回しではなく、プロダクトの誠実さを保つためのものです。ページを正しく読み取ったかどうかは誰でも確認でき、数値について、こちらの言い分をそのまま信じる必要はありません。一方、引用できないのは調整情報です。これはコーパスを使って得られた部分だからです。
ここでは、間違えやすい点を2つ明確にしています。FAQ のパリティは、取り除いた本文ではなく、ページ全体の可視コンテンツを対象に確認します。<footer> 内の FAQ アコーディオンも、読者に見えるテキストです。また、件数の集計対象は本文の段落とリスト項目で、ナビゲーション、ヘッダー、フッター、フォームは除外します。ナビゲーションメニューは文章ではなく、それを数えるとページ上のすべての比率が水増しされるためです。
| エクスポート | 機能 |
|---|---|
quotable(html, nameHint) | 本番の呼び出しを1回で実行します。JSON-LD グラフを解析し、{ signals } を返します |
quoteSignals(html, nodes, nameHint) | 読み取り関数です。nodes は ldGraphNodes(html) から取得します。入力に <body> がない場合は null を返します |
読み取り結果の採点(quoteRows、quoteBlock、セクションの重み、スコアのバージョン)は、このパッケージではなくサービス側にあります。無料スキャンで URL を指定すると、各行を確認できます:https://crawlcheck.io/.
rows — すべての読み取り関数が返す、3状態の行
import { tgt, pctScore } from "crawlcheck-core/rows";
行は { k, cur, opt, ok, why } で表されます。チェック項目、このページの現在の状態、あるべき状態、判定、そしてその理由です。ok: null は3つ目の状態で、重要な意味があります。これは「測定済みだが、採点対象ではない」ことを意味します。判定するには信号が少なすぎるもの、この種類のサイトには当てはまらないもの、または純粋に情報を示すものは、読者に表示されますが、意図的に計算から除外されます。pctScore() は採点対象の行だけを分母にし、該当する行がないセクションには 0 ではなく null を返します。未測定のセクションは、不合格ではありません。
このルールがあるため、サービス提供地域を持つ事業者は住所がないことを理由に評価を下げられず、公開したばかりのサイトもフィールドパフォーマンスデータがないことを理由に遅いと報告されません。ほとんどの監査ツールには2つの状態しかなく、測定できない箇所はすべてゼロとして扱われます。
surfaces — Markdown のネゴシエーションと、サイトの評価を F にした Accept ヘッダー
import { agentSurfaces, grab } from "crawlcheck-core/surfaces";
const root = await grab("https://example.com/");
const out = await agentSurfaces("https://example.com", root);
out.found.markdown; // Accept: text/markdown got a text/markdown answer
out.found.agents_md; // an authored /agents.md, not a 200 of HTML
out.found.link_header; // read off the root response — no extra request
out.evidence.markdown; // { content_type: "text/markdown; charset=utf-8" }
スキャナーの Accept ヘッダーはその識別情報の一部であり、これを間違えると、評価全体が誤ったドキュメントを測定することになります。このフェッチャーは 2026-09-01 まで Accept: */* を送信していました。vercel.com はそれに対して 3,025 バイトの text/markdown を返しましたが、名前付きクローラーの各行は、text/html を送信していたため、521 KB のページを受け取りました。その結果、スキャンではモバイルが 0(Markdown ファイルには viewport meta がありません)、エンティティマップの一致度が 0 と採点され、サイトは F と評価されました。これは、クローラーには決して渡されないドキュメントについての評価です。Googlebot、GPTBot、ClaudeBot はすべてブラウザー形式の Accept を送信するため、CRAWLER_ACCEPT も同様です。
したがって、Markdown ネゴシエーションは、意図的に、専用のリクエストで一度だけ測定します。ページを取得する方法の副作用として測定することはありません。「このサイトはエージェント向けに Markdown を提供している」と「フェッチャーが誤ったものを要求した」を混同することが問題であり、このパッケージにはそれを固定するためだけのテストがあります。Accept: text/markdown を伴うリクエストはちょうど 1 つで、それ以外のすべてのリクエストは CRAWLER_ACCEPT を伴います。
もうひとつのルールは、HTML を返す 200 はソフト 404 です。ほとんどのサイトでは /SKILL.md、/agents.md、3 つの .well-known パスは存在せず、キャッチオールルートはそれらすべてに対して SPA シェルを 200 で返します。単純なチェックでは、これらはすべて存在すると報告されてしまいます。このモジュールが表示する 10 行のうち 9 行は情報提供用で、採点対象ではありません。導入はまだ初期段階であり、存在しないことは欠陥ではありません。また、「該当なし:Product、Offer、またはチェックアウトが見つかりません」と示す行は、チェックリストではなくサイトそのものを測定しています。
| エクスポート | 説明 |
|---|---|
agentSurfaces(base, root) | 4 つの .well-known のプローブを同時に実行し、root の Link ヘッダーと Markdown ネゴシエーションリクエストも取得します |
grab(url) | CrawlCheck のフェッチャーです。クローラー UA、ブラウザー形式の Accept、10 秒の中断、本文は最大 60,000 文字まで読み取ります |
skillMd(host) / agentsMd(host) | /SKILL.md とその YAML フロントマター、および /agents.md を取得します。どちらにもソフト 404 のルールを適用します |
agentSurfaceRows(result) | 適用可能性を示す文言を含む 10 行を返します |
looksHtml(body, ctype), UA, CRAWLER_ACCEPT, SURFACE_PATHS | 呼び出し元がプローブを再現できるよう、各要素をエクスポートします |
jsonld — ページが実際に宣言するエンティティ
import { ldGraphNodes } from "crawlcheck-core/jsonld";
ldGraphNodes(html); // root nodes and @graph children, merged by @id
簡単に間違えやすいルールが 2 つあります。別のノードにネストされたノードは 値 です。PostalAddress、Offer、ListItem、Answer などが該当し、ページが主張する主体ではなく、エンティティに適用される基準も適用されません。また、同じ @id が 2 回登場しても、主体は 1 つです。RDF ではステートメントが統合されるため、最初のノードを残して 2 番目を破棄するリーダーでは、型のないフィンガープリントのスタブと、それに続く完全な Organization の両方を宣言しているサイトを、型のないビジネスを宣言していると読み取ってしまいます。この問題は実際のサイトで測定されたため、ここでは統合処理を行っています。
images — エンジンが画像から把握できること
import { imageSignals, imageRows } from "crawlcheck-core/images";
const sig = imageSignals(html);
// { total:9, no_alt:0, empty_alt:0, no_dimensions:0, lazy:7, lazy_above_fold:0,
// modern_format:8, svg:1, og_image:true, ld_image:true, ld_imageobject:true, ... }
imageRows({ images: sig }); // eight rows, five scored
採点対象は 5 行です。alt 属性の欠落、width と height の指定、ファーストビューでの遅延読み込みがないこと、JSON-LD 内のエンティティ画像、og:image です。3 行は報告されますが、意図的に採点されません。このモジュールの要点はそこにあります。
alt="" は alt の欠落ではありません。装飾画像に適したマークアップであり、実際のサイトでは装飾用の alt がほとんどを占めるため、これを採点すると、通常は適切なシグナルを根拠に、ほぼすべてのサイトが不合格になります。数えるのは 属性 の欠落だけです。ファイル形式やヒーロー画像の優先度は、欠陥ではなく好みの問題です。また、画像がないページでは採点対象外の行が 1 つ返されるため、このセクションのスコアは 0 ではなく null になります。
このリーダーには、これらのテストを書く過程で見つかった修正が含まれています。属性のパターンで空白が除外されていたため、引用符で囲まれた値にスペースが含まれると、読み取り時に空文字列として扱われ、alt="a cedar fence" と alt="" を区別できませんでした。実際のホームページ 6 件で測定したところ、実際にはほとんどの画像に意味のある alt テキストが付いていたにもかかわらず、画像をそれぞれ 15 件中 15 件、9 件中 9 件、11 件中 10 件、装飾画像として報告していました。
テスト
npm test # 48 tests, no network (4 skipped)
CC_LIVE=1 npm test # 48: adds the four live parity checks
robots — RFC グループの解析、優先順位、双方向のシャドーイングケース、未測定の入力、テーブルの整合性、RFC 9309 のワイルドカードと終了アンカーのケース、および実際の robots.txt ファイル 2 件での本番サービスとの同等性をテストします。
agents — トークンの照合、IPv4/IPv6 の範囲ロジック、3 状態の判定、および各ログ行が参照する 5 つの運営者フィードの保存済みスナップショット(test/fixtures/ranges.json、日付はファイル内に記載)を使い、実際のログ行 11 件で本番の検証器と同等であることをテストします。
quotable — リーダー(段落の判定、リード文のテスト、疑問形の見出し、FAQ との同等性、引用可能な文の条件)、行(スコア対象となるもの、意図的に対象外とするもの、段落が 5 つ未満の場合の動作)、JSON-LD グラフのルール、および本番環境のライブスキャンとの同等性をテストします。
surfaces — 実際に送信される Accept ヘッダー、ネゴシエーションリクエストが 1 件のみであること、ソフト 404 の処理、および行の文言をテストします。
同等性フィクスチャの仕組み
各 .quotable.json には、crawlcheck.io で実行した実際のスキャン結果が格納されています。サービス独自の数値を、スキャン ID、スコアバージョン、取得元のビルド sha とともに、改変せずにコピーしたものです。ページ自体はコミットしません。代わりにフィクスチャには、スキャンで読み取った正確なレスポンスボディのダイジェスト page_sha256 が保存されています。ライブテストでは同じ user-agent と Accept でページを再取得し、バイト列が一致しない限り比較を行わず、一致した場合にのみ、このモジュールが 21 個すべてのフィールドとセクションスコアを再現することを確認します。
これにより、ドリフトを見逃さずに検出できます。HTML の保存データでは、すでに存在しないページに対していつまでもテストが通ってしまいます。ダイジェストの不一致なら、リーダーではなくサイトが変わったこと、そしてフィクスチャを取り直す必要があることが明確にわかります。
2 つのフィクスチャのうち 1 つは、意図的に再取得対象としていません。emmanuelorta.com は毎時更新されるライブの数値を表示しており、2026-09-03 の測定では、取得から 3 時間後に同じ長さのレスポンスを受け取ったにもかかわらず、ダイジェストが異なっていました。そのため、このフィクスチャには reproducible: false とその理由が記録されています。記録された 2 つの読み取り結果(100 対 67、FAQ の質問 9 件対 0 件、Markdown のネゴシエーションあり対なし)が食い違っていることが、リーダーが一方のサイトを他方に当てはめて平坦化しないことの証明になるため、このフィクスチャは残しています。
images — 上記の 3 つの区別をテストで固定します(alt がない場合と空の場合、画像がないページ、スコア対象外の形式と優先順位)。行の仕様と 3 状態の判定、形式不正の入力、および同じバイト列からサービスが記録したすべてのフィールドを再現する本番環境のライブスキャンとの同等性もテストします。
ロードマップ
次は、マシン可読ファイルのリーダー(llms.txt、エンティティマップ、それらの基盤となるソフト 404 とチャレンジの判定)です。いずれもライブサービスとの同等性テストを備えて初めてリリースします。
スポンサー
crawlcheck-core と SDK は MIT ライセンスで、無料で使用できます。時間の節約に役立った場合は、GitHub で開発をスポンサーしていただけます。スポンサーシップは、修正、実際のサイトを使ったテスト、クローラーやエージェントの動作に合わせてライブラリを最新の状態に保つために活用されます。
Agency スポンサー — まだいません。
支援者 — まだいません。
サポーター — まだいません。
ライセンス
MIT。Emmanuel Orta が開発しました。サービス本体は crawlcheck.io で利用できます。AI クローラーやエージェントに提供される内容を A〜F で無料監査できます。アカウントは不要です。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。