1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

robots.txt の User-agent: * は名前を書いた bot に効かない(書き忘れを見つけるスクリプト付き)

1
Posted at

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 を取っていました。

取得を止めたいなら robots.txt です。


直した後の時間単位の動き、ClaudeBot が直す前に止まっていて因果が言えなかったこと、名前を書いていない GoogleOther まで来ていた件は Aulvem 本家にまとめました → Aulvem|robots.txt の User-agent: * は名前を書いた bot に効かない — Googlebot が Disallow のはずのリンクを133回たどっていた

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?