robots.txtのAllowとDisallowの優先順位は*も長さに数える
robots.txt で Disallow: /admin/ と Allow: /admin/help/ を両方書いたとき、/admin/help/ がクロールされるかどうかは「書いた順番」では決まらない。2022年9月に標準化された RFC 9309(Robots Exclusion Protocol)は、URL に一致したルールのうち最も長く一致したものを使い、Allow と Disallow が同じ長さなら Allow を使うよう定めている(2.2.2 節)。Google の検索セントラルの説明も同じで、ルールが衝突したら制限の緩い方、つまり Allow を使うと書いている。
この記事で確かめるのはその先の細かい点である。「長さ」に * や $ を含めるかどうかで、同じ robots.txt でも判定が変わる。また、Python 標準の urllib.robotparser はこの最長一致を実装していない。判定ロジックを自作するときや、手元のスクリプトでクロール可否を確かめるときに効いてくる差を、Node.js と Python で実測した。
ブラウザで robots.txt を組み立てて判定まで試すなら robots.txt 作成(ハシトシステム) がある。この記事の後半で、このツールの判定と Google の実装との違いにも触れる。
RFC 9309 と Google が決めていること
一次情報で確認できた規則を並べる。
| 項目 | 内容 | 出典 |
|---|---|---|
| 一致の仕方 | パスの先頭からの前方一致。* は0文字以上の任意の並び、$ はパターンの終わりを表す |
RFC 9309 2.2.3 |
| 優先順位 | 最も具体的な一致(octet 数が最も多いもの)を使う。Allow と Disallow が同等なら Allow を使う(SHOULD) | RFC 9309 2.2.2 |
| 大文字小文字 | 区別する(SHOULD) | RFC 9309 2.2.2、Google |
| サイズ上限 | パースの上限は最低 500KiB(RFC)。Google は 500KiB を超えた部分を無視する | RFC 9309 2.5、Google |
| 4xx | RFC は全リソースへのアクセスを許可(MAY)。Google は 429 を除く 4xx を「robots.txt が無い」と同じに扱う | RFC 9309 2.3.1.3、Google |
| 5xx | RFC は全面的な Disallow とみなす(MUST)。Google は最初の12時間クロールを止め、その後は最大30日キャッシュを使う | RFC 9309 2.3.1.4、Google |
「長さ」について、RFC は「octet 数が多い一致」としか書いていない。Google の説明は「ルールのパスの長さ」に基づくとしており、Google が公開している C++ パーサー google/robotstxt の robots.cc を読むと、LongestMatchRobotsMatchStrategy は一致したときにパターン文字列の長さ(pattern.length())を優先度として返している。* も $ も1文字として数えられる。同じ優先度なら Disallow が勝たない(disallow の優先度が厳密に大きいときだけ Disallow)ので、Allow が勝つ。
実際に試す
前提は Node.js(ここでは Windows 11 上の v24.11.1)。外部パッケージは使わない。次のファイルを robots-match.mjs として保存する。パターンを正規表現に変換し、長さの数え方だけを2通りに差し替えて判定を比べる。
1 | |
1 | |
出力:
1 | |
上の6行は Google の robots.txt 仕様ページにある優先順位の例と同じ URL・ルールである。full(パターン全体の長さ)の列は、6例すべてで Google のページに書かれた結論と一致した。
/page.htmにallow: /page(5文字)とdisallow: /*.htm(6文字)。Google のページは「ルールのパスが長い」としてdisallowを適用すると書いている。*を数えないliteralでは/.htmの5文字になって Allow と同点になり、結論が逆転する。/page.php5にallow: /page(5文字)とdisallow: /*.ph(5文字)。同点なので Allow。allow: /$は/そのものにしか一致しない。/page.htmにはdisallow: /だけが一致するので Disallow になる。- 最後の行は大文字小文字の区別である。
/Admin/はdisallow: /admin/に一致しないので、ブロックされない。
* を最初の一致で決め打ちすると $ と噛み合わない
自作の判定でもう1つ起きやすいのが、* の後ろの文字列を indexOf で最初に見つかった位置に決め打ちする実装である。$ で終わるパターンと組み合わせると、後ろにもう一度出てくる一致を見逃す。
1 | |
1 | |
/a.pdf.pdf は .pdf で終わるので Disallow: /*.pdf$ に一致するはずだが、greedyFirst は最初の .pdf の位置で止まり、その後ろに文字が残るので「一致しない」と判定する。正規表現の .* はバックトラックして後ろの .pdf を試すので一致する。google/robotstxt の Matches 関数は正規表現を使わず、パス上で一致し得る位置の集合を1文字ずつ更新していく方式で、同じく後ろの一致を取りこぼさない。/a.pdf?dl=1 のようにクエリが付いた URL は、$ の位置に ?dl=1 が残るのでどちらの実装でも一致しない。
Python の urllib.robotparser は書いた順で決まる
手元のスクリプトでクロール可否を見るとき、Python 標準ライブラリの urllib.robotparser を使うことがある。これは最長一致ではない。
1 | |
1 | |
1 | |
- 同じ2行でも、
Disallow: /を先に書くと/pageは disallow、Allow: /pを先に書くと allow になる。RFC 9309 と Google の規則ではどちらも allow である。 *と$は特別扱いされず、Disallow: /*.pdf$は/a.pdfに一致しない。
理由はソースを見ると分かる。Python 3.11.9 の Entry.allowance はルール行を書いた順に調べて最初に当たったものを返し、RuleLine.applies_to は filename.startswith(self.path) で前方一致を見るだけである(inspect.getsource で確認した)。Google のクローラーと同じ判定が欲しいなら、urllib.robotparser の結果をそのまま信じない方がよい。
ハシトシステムのツールの判定について
robots.txt 作成(ハシトシステム) の判定も最長一致で、同じ長さなら Allow が勝つ。ただし執筆時点のコードは、長さを * と末尾の $ を除いた文字数で比べている(上の表の literal と同じ)。そのため allow: /page と disallow: /*.htm の組み合わせでは、/page.htm が Google の結論とは逆に「クロールできます」と出る。* を含むルールを Allow と競わせるときは、上の full の数え方で確かめてほしい。* を含まない Disallow: /admin/ と Allow: /admin/help/ のような典型的な書き方では、どちらの数え方でも結論は同じになる。
まとめ
- robots.txt は書いた順ではなく最長一致で判定する。同じ長さなら Allow が勝つ(RFC 9309 2.2.2、Google)。
- Google の実装はパターン文字列そのものの長さで比べ、
*と$も1文字に数える。/page.htmにallow: /pageとdisallow: /*.htmなら Disallow になる。 *を最初の一致位置で決め打ちすると、/*.pdf$が/a.pdf.pdfに一致しなくなる。バックトラックするか、一致し得る位置の集合を持つ。- パスの大文字小文字は区別される。
/Admin/はDisallow: /admin/でブロックされない。 - Python 3.11.9 の
urllib.robotparserは先に書いた行が勝ち、*と$を解釈しない。
robots.txt はクロールを止めるだけで、検索結果から消す手段ではない。サイト内の構造側の検査については 孤立ページを構造的に作らない:sitemapに書く前に一覧から辿れるかを検証する も書いている。
参照した一次情報:
- RFC 9309: Robots Exclusion Protocol(2022年9月、Standards Track)
- robots.txt の仕様(Google 検索セントラル)
- google/robotstxt の robots.cc
ツール: robots.txt 作成(ハシトシステム)。User-agent・Disallow・Allow・Sitemap を入力して robots.txt を組み立て、指定したパスがクロールされるかの判定と、よくある書き間違いの指摘まで出す。処理はブラウザ内で完結する。