15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

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.txt itself is always allowed.
  • Crawl-delay is 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.robotparser on 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$.

PathMatching rulesResult under RFC 9309
/search/flightsDisallow: /searchDisallowed
/search/aboutDisallow: /search (7 characters), Allow: /search/about (13)Allowed — the longer rule wins
/docs/fares.pdfDisallow: /*.pdf$Disallowed
/docs/fares.pdf?v=2None — $ requires the path to end in .pdfAllowed
/robots.txtImplicitly allowedAllowed

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.txt on each scheme and authority, so shop.example.com and example.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 the Disallow line 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. Passing Mozilla/5.0 (compatible; GPTBot/1.1; …) matched the * group instead of a GPTBot group in our test; passing GPTBot matched correctly.
  • The read() method has its own policy. It calls urlopen without 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.

TokenOperatorPurposeDoes robots.txt apply?
GPTBotOpenAIContent that may be used to train foundation modelsYes
OAI-SearchBotOpenAISurfacing sites in ChatGPT search featuresYes
ChatGPT-UserOpenAIVisits initiated by a user's requestOpenAI says robots.txt may not apply
Google-ExtendedGoogleControl over use for Gemini training and grounding; no separate crawler or User-Agent stringYes; does not affect Google Search
ClaudeBotAnthropicContent that may be used for model trainingYes
Claude-SearchBotAnthropicIndexing for search qualityYes
Claude-UserAnthropicRetrieval when a user asks Claude a questionYes, 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

robots.py — RFC 9309 error handling, 24-hour cache, product-token matching
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 default
Known-answer test: does this Python's parser use RFC 9309 precedence?
from 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. [1]IETF: RFC 9309: Robots Exclusion Protocol
  2. [2]Python documentation: urllib.robotparser — Parser for robots.txt
  3. [3]Google Search Central: How Google interprets the robots.txt specification
  4. [4]OpenAI: Overview of OpenAI crawlers
  5. [5]Google Search Central: Google's common crawlers (Google-Extended)
  6. [6]Anthropic: Does Anthropic crawl data from the web, and how can site owners block the crawler?

Next

Where to go from here

Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.Use caseProxies for AI and LLM training-data collectionCollect public web text, images and product data at corpus scale from residential and ISP IPs, paid per gigabyte with no monthly commitment.Glossaryrobots.txtrobots.txt is a plain-text file at the root of a website, /robots.txt, in which the operator tells automated crawlers which paths they may and may not fetch, grouped by crawler name; the Robots Exclusion Protocol behind it dates from 1994 and was standardised by the IETF as RFC 9309 in September 2022.GlossaryWeb crawlerA web crawler, also called a spider or bot, is a program that automatically discovers and fetches web pages — usually by following links from page to page or reading sitemaps — to build a search index, an archive or a dataset.GlossaryUser-AgentUser-Agent is the HTTP request header in which a client identifies its software — one or more product tokens such as curl/8.5.0, with optional versions and comments — and which RFC 9110 says servers use to diagnose compatibility problems, tailor responses and compile browser and operating-system statistics.GuideCollecting travel data responsiblyRobots.txt, rate limits, public versus logged-in pages, terms of service, GDPR and the EU database right: a practical checklist for collecting travel data.GuideHandling 429 rate limitsWhat HTTP 429 means, how to read Retry-After, and how to retry politely with exponential backoff, jitter and per-host concurrency limits. With code.Use caseProxies for AI and LLM training-data collectionCollect public web text, images and product data at corpus scale from residential and ISP IPs, paid per gigabyte with no monthly commitment.Use caseResidential proxies for AI agentsBring your own residential IPs to browser-use, Playwright, Crawl4AI, n8n and any MCP tool — one sticky session per task, billed per GB.