先に結果
Disallow: /api/* と書かれた robots.txt を、Python 標準の urllib.robotparser に読ませると、/api/v2/users を「取ってよい」と答えます。
9 個のパスで、標準の urllib.robotparser と、RFC 9309 のとおりに読む小さな関数を比べたところ、4 個で答えが違いました。違ったのは全部、「標準は取ってよいと言うが、RFC 9309 では取ってはいけない」の向きです。
気付いたきっかけ
自分で作っている取得の道具は、取る前に必ず urllib.robotparser で robots.txt を確かめていました。ある日、あるサイトの robots.txt を目で読むと Disallow: /api/* があり、道具はそのサイトの /api/ 以下を「取ってよい」と判断していたことが分かりました。道具の「許可」を信じきって、自分の目で確かめていなかったのが原因です。
試したこと
robots.txt(試しのために作ったもの):
User-agent: *
Disallow: /api/*
Disallow: /*?sort=
Disallow: /private$
Allow: /api/public/
比べたコード(Python 3.14.4):
import urllib.robotparser
rp = urllib.robotparser.RobotFileParser()
rp.parse(ROBOTS.splitlines())
rp.can_fetch("mybot", path)
数えた結果
| パス | urllib.robotparser | RFC 9309 の読み方 |
|---|---|---|
/api/v2/users |
取る | 取らない |
/api/ |
取る | 取らない |
/api |
取る | 取る |
/api/public/info |
取る | 取る |
/items?sort=new |
取る | 取らない |
/items |
取る | 取る |
/private |
取る | 取らない |
/private/page |
取る | 取る |
/apix |
取る | 取る |
9 個中 4 個で違いました。標準のほうは、* と $ を「ただの文字」として前方一致で比べるので、/api/* も /private$ も、どのパスにも当たりません。この試しでは、標準は 9 個すべてに「取る」と答えました。
RFC 9309 には何と書いてあるか
- 2.2.3 Special Characters:
*は「0 文字以上の任意の文字」、$は「パターンの終わり」。クローラーはこの 2 つを扱わなければならない(MUST)。 - 2.2.2:当たったルールが複数あるときは、いちばん長い(オクテット数が多い)ものを使う。allow と disallow が同じ長さなら allow を使うのがよい(SHOULD)。
直し方(RFC 9309 のとおりに読む小さな関数)
import re
def rule_regex(pattern):
anchored = pattern.endswith("$")
body = re.escape(pattern[:-1] if anchored else pattern).replace(r"\*", ".*")
return re.compile(body + ("$" if anchored else ""))
def allowed(rules, path):
"""rules: [(allow: bool, pattern), ...] 自分の User-agent に当たるグループのルール"""
best = None # (長さ, allow):いちばん長いものが勝つ。同じ長さなら allow が勝つ
for allow, pat in rules:
if rule_regex(pat).match(path):
cand = (len(pat), allow)
if best is None or cand > best:
best = cand
return True if best is None else best[1]
上の表の「RFC 9309 の読み方」の列は、この関数(と、User-agent ごとにルールを分ける部分)で出したものです。
分かったこと
- 標準ライブラリの
urllib.robotparserだけで robots.txt を守っているつもりだと、*や$を使ったルールを素通りします。大きなサイトの robots.txt には、/api/*や/*?のような書き方がよくあります。 - 道具が「取ってよい」と言っても、取る前に一度は robots.txt を自分の目で読む。今回はそれで気付きました。
- 直したあとは、同じ 9 個で上の表の右の列どおりに答えることを確かめてから、取得の道具に入れました。
試していないこと
-
urllib.robotparser以外の Python のライブラリ(Protego など)との比べ。 - 文字のエンコード(
%2Fなど)が混ざるパスの扱い。この関数は、パーセントエンコードの違いをそろえていません。