Guide · Scraping practice
How to respect robots.txt in your crawler
To respect robots.txt, fetch /robots.txt for each scheme and host before crawling it, pick the group whose user-agent line matches your crawler's product token — or the * group — and allow or disallow each URL by the most specific matching rule, as RFC 9309 defines. Cache the result for no more than 24 hours, treat server errors as “disallow everything”, and honour Crawl-delay where a site sets one even though it is not part of the standard. Python's built-in urllib.robotparser covers the basics but, depending on the Python version, applies rules in file order and ignores wildcards, so test it before relying on it.
Reviewed 30 Oct 2026
The short answer
- The most specific rule — the longest matching path — wins. If an allow and a disallow rule match equally, the allow rule should be used.
*matches any characters and$anchors the end. - Group matching is case-insensitive on the product token. Pass that token, not your full User-Agent header, to a parser.
- Cache for at most 24 hours; 4xx means no restrictions; 5xx or an unreachable server means assume complete disallow; parse at least 500 KiB;
/robots.txtitself is always allowed. Crawl-delayis not in RFC 9309. Google ignores it; Anthropic says its crawlers support it. Treat it as a minimum interval between requests.- In our tests,
urllib.robotparseron Python 3.12.6, 3.13.12 and 3.14.0 applied rules in file order and ignored*and$; Python 3.14.6 followed RFC 9309. Check the version you run. - AI crawlers use separate tokens — GPTBot, OAI-SearchBot, ClaudeBot, Google-Extended and others — so a site can treat training, search and user-initiated fetching differently.
How RFC 9309 matching works
A robots.txt file is a list of groups. Each group starts with one or more User-agent lines and contains Allow and Disallow rules. A crawler finds the group whose user-agent value matches its product token, case-insensitively; if none does, it obeys the * group. Within the group, every rule whose path matches the URL's path — including the query string — is a candidate, and the one with the most characters wins.
Take this file: User-agent: *, Disallow: /search, Allow: /search/about, Disallow: /*.pdf$.
| Path | Matching rules | Result under RFC 9309 |
|---|---|---|
/search/flights | Disallow: /search | Disallowed |
/search/about | Disallow: /search (7 characters), Allow: /search/about (13) | Allowed — the longer rule wins |
/docs/fares.pdf | Disallow: /*.pdf$ | Disallowed |
/docs/fares.pdf?v=2 | None — $ requires the path to end in .pdf | Allowed |
/robots.txt | Implicitly allowed | Allowed |
The order of lines in the file does not matter for the result — which is exactly where older parsers go wrong. For the term itself, see robots.txt.
Fetching, caching and error codes
- One file per origin. The file lives at
/robots.txton each scheme and authority, soshop.example.comandexample.com— or HTTP and HTTPS — each have their own. - Redirects. RFC 9309 says crawlers should follow at least five consecutive redirects, even to another host.
- Status codes. A 4xx such as 404 means the crawler may access any resource; a 5xx, or no answer at all, means it must assume complete disallow.
- Caching. Do not use a cached copy for more than 24 hours unless the file is unreachable. Google says it generally caches for up to 24 hours as well.
- Size. The parsing limit must be at least 500 KiB; Google ignores content beyond 500 KiB.
The broader context — rate limits, terms of service and personal data — is covered in collecting travel data responsibly.
Crawl-delay and other non-standard lines
RFC 9309 lets crawlers interpret records outside the protocol, naming Sitemap as an example, but it does not define Crawl-delay. Support is therefore per crawler: Google's documentation says crawl-delay is not supported, while Anthropic's documentation for its crawlers says it supports the non-standard Crawl-delay extension. Python's parser exposes crawl_delay() and request_rate() for the lines it finds.
A respectful crawler treats a stated delay as a minimum number of seconds between requests to that host, combines it with its own per-host limit and still backs off when the server answers 429 or 503 — see handling 429 rate limits.
Parsing robots.txt in Python
urllib.robotparser.RobotFileParser offers set_url(), read(), parse(), can_fetch(), mtime(), crawl_delay(), request_rate() and site_maps(). Its documentation does not state which precedence rule it applies, and we found that it changed between releases. Running the example file above:
- Python 3.12.6, 3.13.5, 3.13.12 and 3.14.0 returned disallowed for
/search/about, because theDisallowline comes first, and allowed for/docs/fares.pdf, because*and$were not understood. - Python 3.14.6 returned the RFC 9309 answers for every row of the table.
- Product token, not header.
can_fetch()compares only the part of the string before the first slash. PassingMozilla/5.0 (compatible; GPTBot/1.1; …)matched the*group instead of aGPTBotgroup in our test; passingGPTBotmatched correctly. - The read() method has its own policy. It calls
urlopenwithout a timeout, treats 401 and 403 as disallow-all and other 4xx as allow-all, and sends urllib's default User-Agent. Fetching the file yourself, as in the code below, gives you a timeout, your own identity and the RFC's error handling.
The second code sample is a one-line known-answer test. Run it in your deployment image; if it prints False, use a parser that implements RFC 9309 rather than relying on rule order.
AI crawler user agents and opt-outs
AI companies now run several crawlers each, so site owners can allow one purpose and refuse another. The tokens below are the ones their operators document.
| Token | Operator | Purpose | Does robots.txt apply? |
|---|---|---|---|
| GPTBot | OpenAI | Content that may be used to train foundation models | Yes |
| OAI-SearchBot | OpenAI | Surfacing sites in ChatGPT search features | Yes |
| ChatGPT-User | OpenAI | Visits initiated by a user's request | OpenAI says robots.txt may not apply |
| Google-Extended | Control over use for Gemini training and grounding; no separate crawler or User-Agent string | Yes; does not affect Google Search | |
| ClaudeBot | Anthropic | Content that may be used for model training | Yes |
| Claude-SearchBot | Anthropic | Indexing for search quality | Yes |
| Claude-User | Anthropic | Retrieval when a user asks Claude a question | Yes, per Anthropic |
If you run your own crawler, give it a unique product token and a page that explains what it does, so site owners can write a group for it. If you build AI agents that browse for a user, or collect AI training data, decide explicitly which token your traffic answers to and document it. See also web crawler and user agent.
Code
Worked example
import timefrom urllib.parse import urlsplitfrom urllib.robotparser import RobotFileParserimport requestsPRODUCT_TOKEN = "examplebot" # the token your robots.txt groups would nameUSER_AGENT = "examplebot/1.0 (+https://example.com/bot)"CACHE_SECONDS = 24 * 3600 # RFC 9309: do not use a cached copy for longer than 24 h_cache: dict[str, tuple[float, RobotFileParser]] = {}def _load(origin: str) -> RobotFileParser: rp = RobotFileParser() try: r = requests.get(f"{origin}/robots.txt", timeout=(5, 15), headers={"User-Agent": USER_AGENT}) except requests.RequestException: rp.disallow_all = True # unreachable: assume complete disallow return rp if r.status_code >= 500: rp.disallow_all = True # 5xx: assume complete disallow elif r.status_code >= 400: rp.allow_all = True # 4xx (e.g. 404): no restrictions else: rp.parse(r.content[:512_000].decode("utf-8", "replace").splitlines()) return rpdef robots_for(url: str) -> RobotFileParser: parts = urlsplit(url) origin = f"{parts.scheme}://{parts.netloc}" # robots.txt is per scheme, host and port cached = _cache.get(origin) if cached is None or time.time() - cached[0] > CACHE_SECONDS: cached = (time.time(), _load(origin)) _cache[origin] = cached return cached[1]def allowed(url: str) -> bool: # Pass the product token, not the full User-Agent header string. return robots_for(url).can_fetch(PRODUCT_TOKEN, url)def delay_for(url: str, default: float = 1.0) -> float: # Crawl-delay is not part of RFC 9309; honour it as a floor if a site sets one. d = robots_for(url).crawl_delay(PRODUCT_TOKEN) return max(float(d), default) if d else defaultfrom urllib.robotparser import RobotFileParserrp = RobotFileParser()rp.parse("User-agent: *\nDisallow: /search\nAllow: /search/about\n".splitlines())# RFC 9309: the longer Allow rule wins, so this must print True.print(rp.can_fetch("examplebot", "https://example.com/search/about"))FAQ
Questions
Something not covered? Write to contact@arcproxies.io.
Is robots.txt legally binding?
RFC 9309 states that its rules are not a form of access authorisation; they are the site operator's instructions to crawlers. Whether ignoring them has legal consequences depends on the jurisdiction and circumstances. A responsible crawler follows them regardless. This is not legal advice.
Does Google support crawl-delay?
No. Google's robots.txt documentation says fields such as crawl-delay are not supported and are ignored. Other crawlers, including Anthropic's, say they support it as a non-standard extension.
How do I block AI crawlers with robots.txt?
Add a group for each documented token, such as User-agent: GPTBot followed by Disallow: /, and likewise for ClaudeBot or Google-Extended, on every host you want covered. Each operator documents its tokens and what they control; user-initiated agents may be treated differently.
What happens if robots.txt returns a 404?
Under RFC 9309, a status in the 400–499 range means the file is unavailable and the crawler may access any resource on the server. A 5xx error or an unreachable server means the crawler must assume complete disallow.
Does robots.txt apply to subdomains?
No. Each scheme and host has its own /robots.txt, so www.example.com, shop.example.com and example.com are read separately. Anthropic's documentation, for example, tells site owners to add their opt-out to every subdomain.
Which name should my crawler match in robots.txt?
Its product token — a short name of letters, underscores and hyphens, such as examplebot — compared case-insensitively. Publish the token and a description page so site owners can address your crawler directly.
Sources & review
Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.
- [1]IETF: RFC 9309: Robots Exclusion Protocol
- [2]Python documentation: urllib.robotparser — Parser for robots.txt
- [3]Google Search Central: How Google interprets the robots.txt specification
- [4]OpenAI: Overview of OpenAI crawlers
- [5]Google Search Central: Google's common crawlers (Google-Extended)
- [6]Anthropic: Does Anthropic crawl data from the web, and how can site owners block the crawler?
Next
Where to go from here
Guides