ai-crawler-robots 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pathwren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The crawler table shipped in data.json is dedicated to the public domain under
26
+ CC0-1.0. Robots tokens, user-agent strings and documentation URLs are taken
27
+ from each operator's own published documentation; the categories and the prose
28
+ are the index's own.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-crawler-robots
3
+ Version: 1.0.0
4
+ Summary: Lint a robots.txt and audit it against 56 AI crawlers: which ones it really blocks, which rules do nothing, and which fetchers robots.txt cannot stop at all. RFC 9309, zero dependencies, no network.
5
+ Author: Pathwren
6
+ License: MIT
7
+ Project-URL: Homepage, https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/
8
+ Project-URL: Documentation, https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/
9
+ Project-URL: Data source, https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/data.json
10
+ Project-URL: Companion package, https://pypi.org/project/ai-crawler-index/
11
+ Keywords: robots.txt,robots,rfc9309,lint,linter,crawler,ai,gptbot,claudebot,seo,crawl,user-agent,offline
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Internet :: WWW/HTTP
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Classifier: Topic :: Text Processing :: Linguistic
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # ai-crawler-robots
30
+
31
+ **A robots.txt that looks right and blocks nothing is the normal failure.**
32
+ This package reads the file you already have and tells you which of the
33
+ 56 AI crawlers it actually stops.
34
+
35
+ ```python
36
+ from ai_crawler_robots import lint, audit, is_allowed
37
+
38
+ lint(open("robots.txt").read())
39
+ # [{'level': 'error', 'line': 3, 'code': 'ua-is-not-a-token',
40
+ # 'message': 'User-agent takes a product token, not a full user-agent string. '
41
+ # 'This group matches no crawler.',
42
+ # 'found': 'Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)',
43
+ # 'fix': 'User-agent: GPTBot'}]
44
+
45
+ is_allowed(text, "GPTBot", "/blog/post")
46
+ # {'allowed': False, 'rule': 'Disallow: /blog', 'line': 8, 'group': 'GPTBot', ...}
47
+
48
+ audit(text)["blocked"] # ['anthropic-claudebot', 'openai-gptbot', ...]
49
+ audit(text)["unenforceable"] # crawlers that will not obey it whatever it says
50
+ ```
51
+
52
+ ## Install
53
+
54
+ ```sh
55
+ pip install ai-crawler-robots
56
+ ```
57
+
58
+ Python >= 3.8. **Zero dependencies, standard library only.** No network at
59
+ import: 56 crawler records and 8 ready-made stances ship
60
+ inside the wheel (68.1 KB), snapshot taken 2026-09-01.
61
+
62
+ **Nothing in this package fetches a URL.** There is no argument anywhere that
63
+ takes one. `refresh()` is the only call that touches the network, you have to
64
+ name it, and it fetches exactly one document: the same
65
+ [data.json](https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/data.json) that ships in the wheel.
66
+
67
+ ## The faults it finds
68
+
69
+ The most common robots.txt failure is not a syntax error — it is a file that a
70
+ person reads as correct and a parser reads as empty. 23 checks, of
71
+ which these are the ones that cost real traffic:
72
+
73
+ | code | what it means |
74
+ | --- | --- |
75
+ | `user_agent_is_a_ua_string` | `User-agent:` carrying a whole browser-style string. RFC 9309 matches a **product token**; that group matches nothing. |
76
+ | `unknown-token` | a token no crawler in the table sends — usually a typo, `ChatGPT-user` for `ChatGPT-User`, or a name somebody invented |
77
+ | `duplicate_group` | a second group for the same token; only the first one applies, the rest is decoration |
78
+ | `noindex_in_robots` | `Noindex:` in robots.txt. Google stopped honouring it on 1 September 2019 and it never worked anywhere else. |
79
+ | `unenforceable` | the rule is correct and the operator states robots.txt does not apply to that fetcher. You need an edge rule, and this says so. |
80
+ | `crawl_delay` | `Crawl-delay:` is not in RFC 9309 and Google, OpenAI and Anthropic all ignore it |
81
+ | `path_without_slash` | `Disallow: admin` does not match `/admin` |
82
+ | `html_response` / `empty` / `bom` | the file never parsed at all |
83
+
84
+ Every finding carries a line number, what was found, and a `fix` string you can
85
+ paste.
86
+
87
+ ## Command line
88
+
89
+ ```sh
90
+ ai-crawler-robots lint robots.txt # the faults, worst first
91
+ ai-crawler-robots audit robots.txt # every AI crawler: blocked / allowed / unenforceable
92
+ ai-crawler-robots check robots.txt --agent GPTBot --path /blog/
93
+ ai-crawler-robots diff old.txt new.txt # what changed by effect, not by text
94
+ ai-crawler-robots policy block-ai-training # print a ready-made stance
95
+ ai-crawler-robots policy block-ai-training --merge robots.txt
96
+ ai-crawler-robots explain GPTBot # who it is, what blocking costs
97
+ ```
98
+
99
+ `lint` exits `1` when it finds an error, `0` otherwise, so it works as a CI
100
+ gate:
101
+
102
+ ```sh
103
+ ai-crawler-robots lint robots.txt --strict || exit 1
104
+ ```
105
+
106
+ ## `audit` — the question people actually have
107
+
108
+ ```sh
109
+ $ ai-crawler-robots audit robots.txt
110
+ robots.txt: 56 crawlers, 12 blocked, 39 allowed, 5 unenforceable
111
+
112
+ BLOCKED (12)
113
+ openai-gptbot GPTBot OpenAI ai-training
114
+ anthropic-claudebot ClaudeBot Anthropic ai-training
115
+ ...
116
+ ALLOWED, AND YOU PROBABLY MEANT TO BLOCK (7)
117
+ google-extended Google-Extended Google ai-training
118
+ ...
119
+ UNENFORCEABLE BY robots.txt (5)
120
+ These fetch on behalf of a user in a live session. Their operators state
121
+ robots.txt does not govern them. Blocking needs an edge rule.
122
+ ```
123
+
124
+ The third group is the one that surprises people. A user-triggered fetcher is
125
+ not a crawler, most operators say so in their own documentation, and a
126
+ `Disallow` aimed at one is a line that will never fire.
127
+
128
+ ## `diff` — did that edit do anything?
129
+
130
+ ```sh
131
+ $ ai-crawler-robots diff robots.old robots.new
132
+ 2 crawler(s) changed verdict for /
133
+ google-extended allowed -> blocked
134
+ meta-externalagent allowed -> blocked
135
+ 44 unchanged. Formatting-only changes are not reported.
136
+ ```
137
+
138
+ Compares by **effect**. Reordering groups, changing whitespace and rewriting
139
+ comments produce an empty diff, because none of them change what a crawler does.
140
+
141
+ ## Ready-made stances
142
+
143
+ 8 of them, the same bodies published at the index:
144
+
145
+ - `allow-all` — Every crawler on this index is named and allowed. Use when you want maximum reach into search and assistants and have nothing to withhold
146
+ - `block-ai-training` — Refuse the crawlers that feed model training. Keep the ones that put you in ChatGPT, Claude, Perplexity and Gemini answers
147
+ - `block-all-ai` — Training, AI search, user-triggered fetches and corpus builders, all refused. Classic search engines still allowed
148
+ - `block-datasets` — Refuse the crawlers whose output is a dataset other people train on: Common Crawl, AI2, Webz.io, Diffbot, ImagesiftBot
149
+ - `allow-ai-search-only` — Be findable and citable in assistants without contributing to training corpora
150
+ - `block-seo-tools` — Ahrefs, Semrush and friends. No user-facing consequence, and often the largest single slice of your bot traffic
151
+ - `block-disputed` — The ones repeatedly reported as ignoring robots.txt. Included for completeness — expect to enforce this at the edge instead
152
+ - `maximum-ai-visibility` — Allow every AI crawler and every search engine; refuse only SEO scrapers. For sites whose goal is to be found and cited by machines
153
+
154
+ `--merge` adds a stance to an existing file **without touching the groups
155
+ already in it**, so your Googlebot rules survive.
156
+
157
+ ## Where the data comes from
158
+
159
+ The [AI Crawler Index](https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/) — an independent, non-commercial public
160
+ reference. Every crawler record links to the operator's own documentation, and
161
+ `explain` prints that link. Presence in the table means the operator documents
162
+ that crawler; it is not an opinion about whether you should allow it.
163
+
164
+ `refresh()` fetches today's table and caches it six hours, which is how often
165
+ the index behind it is rebuilt:
166
+
167
+ ```python
168
+ from ai_crawler_robots import refresh
169
+ refresh() # {'crawlers': 56, 'generated_at': '...'}
170
+ ```
171
+
172
+ ## Companion packages
173
+
174
+ Same table, different questions:
175
+
176
+ - [`ai-crawler-index`](https://pypi.org/project/ai-crawler-index/) — what does
177
+ this user-agent claim to be?
178
+ - [`ai-crawler-verify`](https://pypi.org/project/ai-crawler-verify/) — is that
179
+ claim true? Checks the address against the operator's published ranges.
180
+ - [`ai-crawler-logs`](https://pypi.org/project/ai-crawler-logs/) — who was
181
+ actually in your access log, and what to paste to act on it.
182
+
183
+ ## Licence
184
+
185
+ Code MIT. Bundled data CC0-1.0. Independent and non-commercial; not affiliated
186
+ with, endorsed by or speaking for any crawler operator named in the table.
@@ -0,0 +1,158 @@
1
+ # ai-crawler-robots
2
+
3
+ **A robots.txt that looks right and blocks nothing is the normal failure.**
4
+ This package reads the file you already have and tells you which of the
5
+ 56 AI crawlers it actually stops.
6
+
7
+ ```python
8
+ from ai_crawler_robots import lint, audit, is_allowed
9
+
10
+ lint(open("robots.txt").read())
11
+ # [{'level': 'error', 'line': 3, 'code': 'ua-is-not-a-token',
12
+ # 'message': 'User-agent takes a product token, not a full user-agent string. '
13
+ # 'This group matches no crawler.',
14
+ # 'found': 'Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)',
15
+ # 'fix': 'User-agent: GPTBot'}]
16
+
17
+ is_allowed(text, "GPTBot", "/blog/post")
18
+ # {'allowed': False, 'rule': 'Disallow: /blog', 'line': 8, 'group': 'GPTBot', ...}
19
+
20
+ audit(text)["blocked"] # ['anthropic-claudebot', 'openai-gptbot', ...]
21
+ audit(text)["unenforceable"] # crawlers that will not obey it whatever it says
22
+ ```
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ pip install ai-crawler-robots
28
+ ```
29
+
30
+ Python >= 3.8. **Zero dependencies, standard library only.** No network at
31
+ import: 56 crawler records and 8 ready-made stances ship
32
+ inside the wheel (68.1 KB), snapshot taken 2026-09-01.
33
+
34
+ **Nothing in this package fetches a URL.** There is no argument anywhere that
35
+ takes one. `refresh()` is the only call that touches the network, you have to
36
+ name it, and it fetches exactly one document: the same
37
+ [data.json](https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/data.json) that ships in the wheel.
38
+
39
+ ## The faults it finds
40
+
41
+ The most common robots.txt failure is not a syntax error — it is a file that a
42
+ person reads as correct and a parser reads as empty. 23 checks, of
43
+ which these are the ones that cost real traffic:
44
+
45
+ | code | what it means |
46
+ | --- | --- |
47
+ | `user_agent_is_a_ua_string` | `User-agent:` carrying a whole browser-style string. RFC 9309 matches a **product token**; that group matches nothing. |
48
+ | `unknown-token` | a token no crawler in the table sends — usually a typo, `ChatGPT-user` for `ChatGPT-User`, or a name somebody invented |
49
+ | `duplicate_group` | a second group for the same token; only the first one applies, the rest is decoration |
50
+ | `noindex_in_robots` | `Noindex:` in robots.txt. Google stopped honouring it on 1 September 2019 and it never worked anywhere else. |
51
+ | `unenforceable` | the rule is correct and the operator states robots.txt does not apply to that fetcher. You need an edge rule, and this says so. |
52
+ | `crawl_delay` | `Crawl-delay:` is not in RFC 9309 and Google, OpenAI and Anthropic all ignore it |
53
+ | `path_without_slash` | `Disallow: admin` does not match `/admin` |
54
+ | `html_response` / `empty` / `bom` | the file never parsed at all |
55
+
56
+ Every finding carries a line number, what was found, and a `fix` string you can
57
+ paste.
58
+
59
+ ## Command line
60
+
61
+ ```sh
62
+ ai-crawler-robots lint robots.txt # the faults, worst first
63
+ ai-crawler-robots audit robots.txt # every AI crawler: blocked / allowed / unenforceable
64
+ ai-crawler-robots check robots.txt --agent GPTBot --path /blog/
65
+ ai-crawler-robots diff old.txt new.txt # what changed by effect, not by text
66
+ ai-crawler-robots policy block-ai-training # print a ready-made stance
67
+ ai-crawler-robots policy block-ai-training --merge robots.txt
68
+ ai-crawler-robots explain GPTBot # who it is, what blocking costs
69
+ ```
70
+
71
+ `lint` exits `1` when it finds an error, `0` otherwise, so it works as a CI
72
+ gate:
73
+
74
+ ```sh
75
+ ai-crawler-robots lint robots.txt --strict || exit 1
76
+ ```
77
+
78
+ ## `audit` — the question people actually have
79
+
80
+ ```sh
81
+ $ ai-crawler-robots audit robots.txt
82
+ robots.txt: 56 crawlers, 12 blocked, 39 allowed, 5 unenforceable
83
+
84
+ BLOCKED (12)
85
+ openai-gptbot GPTBot OpenAI ai-training
86
+ anthropic-claudebot ClaudeBot Anthropic ai-training
87
+ ...
88
+ ALLOWED, AND YOU PROBABLY MEANT TO BLOCK (7)
89
+ google-extended Google-Extended Google ai-training
90
+ ...
91
+ UNENFORCEABLE BY robots.txt (5)
92
+ These fetch on behalf of a user in a live session. Their operators state
93
+ robots.txt does not govern them. Blocking needs an edge rule.
94
+ ```
95
+
96
+ The third group is the one that surprises people. A user-triggered fetcher is
97
+ not a crawler, most operators say so in their own documentation, and a
98
+ `Disallow` aimed at one is a line that will never fire.
99
+
100
+ ## `diff` — did that edit do anything?
101
+
102
+ ```sh
103
+ $ ai-crawler-robots diff robots.old robots.new
104
+ 2 crawler(s) changed verdict for /
105
+ google-extended allowed -> blocked
106
+ meta-externalagent allowed -> blocked
107
+ 44 unchanged. Formatting-only changes are not reported.
108
+ ```
109
+
110
+ Compares by **effect**. Reordering groups, changing whitespace and rewriting
111
+ comments produce an empty diff, because none of them change what a crawler does.
112
+
113
+ ## Ready-made stances
114
+
115
+ 8 of them, the same bodies published at the index:
116
+
117
+ - `allow-all` — Every crawler on this index is named and allowed. Use when you want maximum reach into search and assistants and have nothing to withhold
118
+ - `block-ai-training` — Refuse the crawlers that feed model training. Keep the ones that put you in ChatGPT, Claude, Perplexity and Gemini answers
119
+ - `block-all-ai` — Training, AI search, user-triggered fetches and corpus builders, all refused. Classic search engines still allowed
120
+ - `block-datasets` — Refuse the crawlers whose output is a dataset other people train on: Common Crawl, AI2, Webz.io, Diffbot, ImagesiftBot
121
+ - `allow-ai-search-only` — Be findable and citable in assistants without contributing to training corpora
122
+ - `block-seo-tools` — Ahrefs, Semrush and friends. No user-facing consequence, and often the largest single slice of your bot traffic
123
+ - `block-disputed` — The ones repeatedly reported as ignoring robots.txt. Included for completeness — expect to enforce this at the edge instead
124
+ - `maximum-ai-visibility` — Allow every AI crawler and every search engine; refuse only SEO scrapers. For sites whose goal is to be found and cited by machines
125
+
126
+ `--merge` adds a stance to an existing file **without touching the groups
127
+ already in it**, so your Googlebot rules survive.
128
+
129
+ ## Where the data comes from
130
+
131
+ The [AI Crawler Index](https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/) — an independent, non-commercial public
132
+ reference. Every crawler record links to the operator's own documentation, and
133
+ `explain` prints that link. Presence in the table means the operator documents
134
+ that crawler; it is not an opinion about whether you should allow it.
135
+
136
+ `refresh()` fetches today's table and caches it six hours, which is how often
137
+ the index behind it is rebuilt:
138
+
139
+ ```python
140
+ from ai_crawler_robots import refresh
141
+ refresh() # {'crawlers': 56, 'generated_at': '...'}
142
+ ```
143
+
144
+ ## Companion packages
145
+
146
+ Same table, different questions:
147
+
148
+ - [`ai-crawler-index`](https://pypi.org/project/ai-crawler-index/) — what does
149
+ this user-agent claim to be?
150
+ - [`ai-crawler-verify`](https://pypi.org/project/ai-crawler-verify/) — is that
151
+ claim true? Checks the address against the operator's published ranges.
152
+ - [`ai-crawler-logs`](https://pypi.org/project/ai-crawler-logs/) — who was
153
+ actually in your access log, and what to paste to act on it.
154
+
155
+ ## Licence
156
+
157
+ Code MIT. Bundled data CC0-1.0. Independent and non-commercial; not affiliated
158
+ with, endorsed by or speaking for any crawler operator named in the table.
@@ -0,0 +1,60 @@
1
+ # GENERATED by playbooks/package_lane.py from lane.json. Do not edit:
2
+ # every field below has a single source of truth in that file.
3
+ [build-system]
4
+ requires = ["setuptools>=68"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "ai-crawler-robots"
9
+ version = "1.0.0"
10
+ description = "Lint a robots.txt and audit it against 56 AI crawlers: which ones it really blocks, which rules do nothing, and which fetchers robots.txt cannot stop at all. RFC 9309, zero dependencies, no network."
11
+ readme = "README.md"
12
+ requires-python = ">=3.8"
13
+ license = { text = "MIT" }
14
+ authors = [{ name = "Pathwren" }]
15
+ keywords = [
16
+ "robots.txt",
17
+ "robots",
18
+ "rfc9309",
19
+ "lint",
20
+ "linter",
21
+ "crawler",
22
+ "ai",
23
+ "gptbot",
24
+ "claudebot",
25
+ "seo",
26
+ "crawl",
27
+ "user-agent",
28
+ "offline",
29
+ ]
30
+ classifiers = [
31
+ "Development Status :: 4 - Beta",
32
+ "Intended Audience :: Developers",
33
+ "Intended Audience :: System Administrators",
34
+ "License :: OSI Approved :: MIT License",
35
+ "Operating System :: OS Independent",
36
+ "Programming Language :: Python :: 3",
37
+ "Programming Language :: Python :: 3 :: Only",
38
+ "Topic :: Internet :: WWW/HTTP",
39
+ "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
40
+ "Topic :: Software Development :: Quality Assurance",
41
+ "Topic :: Text Processing :: Linguistic",
42
+ "Typing :: Typed",
43
+ ]
44
+ dependencies = []
45
+
46
+ [project.scripts]
47
+ ai-crawler-robots = "ai_crawler_robots:_main"
48
+
49
+ [project.urls]
50
+ "Homepage" = "https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/"
51
+ "Documentation" = "https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/"
52
+ "Data source" = "https://www.pathwren.workers.dev/c/pypi-registry/ai-crawler-robots/data.json"
53
+ "Companion package" = "https://pypi.org/project/ai-crawler-index/"
54
+
55
+ [tool.setuptools]
56
+ package-dir = { "" = "src" }
57
+ packages = ["ai_crawler_robots"]
58
+
59
+ [tool.setuptools.package-data]
60
+ ai_crawler_robots = ["data.json", "py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+