robots.txt で User-agent: * の下に Disallow を書いても、User-agent: Googlebot のように名前を書いたグループがある bot には効きません。bot は自分の名前に一致するグループを1つだけ読み、* は一致するグループが無いときの受け皿だからです。
自分のサイトでは、転送リンク /go/ を * で Disallow したのに、Cloudflare が本物と確認した Googlebot が3日で133回、ClaudeBot が5日で213回、そのリンクを取っていました。ここでは、仕組みと直し方、それから書き忘れを機械的に見つけるスクリプトをまとめます。
効かない書き方
User-agent: Googlebot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: *
Allow: /
Disallow: /go/
AI 検索のクローラーを名前で許可しておき、あとから * に Disallow を足す、という順で書くとこうなりがちです。この robots.txt では、Googlebot と ClaudeBot にとって /go/ は許可されています。
根拠は仕様そのものです。
-
RFC 9309 2.2.1 節: 一致するグループが無いときは
*のグループに従う(MUST) - Google の robots.txt の仕様: 1つのクローラーに有効なグループは1つだけで、ほかのグループは無視される
グループの間でルールは引き継がれません。* は全 bot の共通設定ではありません。
直し方
止めたいパスを、名前を書いた全グループに書きます。
User-agent: Googlebot
Allow: /
Disallow: /go/
User-agent: ClaudeBot
Allow: /
Disallow: /go/
User-agent: *
Allow: /
Disallow: /go/
Allow: / と Disallow: /go/ が同じグループにあっても問題ありません。RFC 9309 2.2.2 節では、一致したパスが最も長いルールが勝つので、/go/amazon/... には Disallow: /go/(4文字)が Allow: /(1文字)より優先されます。
直した後の Googlebot は、robots.txt を取り直した時点から /go/ に来なくなりました。
| 日(UTC) | Googlebot の /go/
|
|---|---|
| 10-26 | 49 |
| 10-27(13:31 に修正) | 52 |
| 10-28〜11-02 | 0 |
直してから止まるまでに4時間ほどかかったのは、Google が robots.txt を最大24時間キャッシュするためです。
書き忘れを見つけるスクリプト
名前を書いたグループが多いと、どれかに書き忘れます。robots.txt から User-agent を全部拾い、止めたいパスを判定するスクリプトを置いておきます。
import re
import sys
from protego import Protego # pip install protego
def named_agents(text):
"""robots.txt に出てくる User-agent の名前(* 以外)"""
names = re.findall(r"(?im)^\s*user-agent\s*:\s*([^\s#]+)", text)
return sorted({n for n in names if n != "*"}, key=str.lower)
def check(text, base, paths):
rp = Protego.parse(text)
agents = named_agents(text) + ["SomeUnnamedBot"] # 最後の1つは * の確認用
leaks = []
for ua in agents:
for p in paths:
ok = rp.can_fetch(base + p, ua)
print(f"{ua:20s} {p:28s} {'allow' if ok else 'DISALLOW'}")
if ok:
leaks.append((ua, p))
return leaks
if __name__ == "__main__":
robots_file, base, *paths = sys.argv[1:]
leaks = check(open(robots_file, encoding="utf-8").read(), base, paths)
if leaks:
print(f"\n{len(leaks)} 件、止めたはずのパスが allow になっています")
sys.exit(1)
python check_groups.py public/robots.txt https://example.com /go/amazon/B0CL6H35DY
直す前の robots.txt にかけると、名前を書いた13グループすべてが allow で、SomeUnnamedBot だけが DISALLOW になりました。ログで起きていたことと同じです。直した後は全行が DISALLOW になり、終了コードは0です。止めたいパスを引数に並べて CI に入れておけば、グループを足したときの書き忘れで落ちます。
Python 標準の urllib.robotparser は使えない
同じ判定を urllib.robotparser でやると、直した後の robots.txt でも全 UA が allow になります。
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.parse(open("robots.txt", encoding="utf-8").read().splitlines())
rp.can_fetch("Googlebot", "/go/amazon/B0CL6H35DY") # True(直した後でも)
標準の実装はグループ内のルールを上から読み、最初に一致した行で決めます。Allow: / を先に書いていると、Disallow: /go/ まで届きません。最長一致で判定する Protego(Scrapy が使っているパーサー)を使ってください。動作は 0.7.0 で確かめています。
nofollow や X-Robots-Tag では止まらない
転送リンクには rel="sponsored nofollow" を付け、応答に X-Robots-Tag: noindex, nofollow も返していましたが、Googlebot は67種類の転送 URL を取っていました。
- nofollow は 2020年3月1日からクロールについてもヒント扱いで、たどるかどうかは Google が決める
-
X-Robots-Tagは取得した応答に付くヘッダーなので、取得そのものは止められない
取得を止めたいなら robots.txt です。
直した後の時間単位の動き、ClaudeBot が直す前に止まっていて因果が言えなかったこと、名前を書いていない GoogleOther まで来ていた件は Aulvem 本家にまとめました → Aulvem|robots.txt の User-agent: * は名前を書いた bot に効かない — Googlebot が Disallow のはずのリンクを133回たどっていた