scan_site
Run a live AI-accessibility audit of a domain: robots.txt policy for every tracked AI crawler, live user-agent probes, JavaScript-free readability, structured data and llms.txt. Returns a score out of 100 with per-check detail.
domain必填
io.github.taylorsmithgg由 GitHub 账号 taylorsmithgg 发布v1.0.0更新于 Oct 24, 2026
Ask before you fetch: will this domain serve your crawler, refuse it, or charge it?
一个远程 MCP 服务器,让爬虫在抓取前知道某个域名会放行、拒绝还是对其 user agent 收费。
Web executable,通过 Streamable HTTP。 远程服务在工作区中配置后即可从网页运行时运行。
把这段提示词粘贴到 Claude Code、Codex、Cursor 等智能体,或 SourceWeft 对话中。智能体会读取该服务器的安装指南,先向你展示发布者、端点和登录方式,经你同意后再把它添加到你的客户端。密钥不会写进任何命令。
阅读 https://sourceweft.com/mcp/io.github.taylorsmithgg%2Fcrawl-census/install.md,并按其中说明把这个 MCP 服务器接入我的客户端。修改任何配置之前,先告诉我它的发布者、端点和登录方式,等我确认。不要把 API 密钥写进命令或我会提交的文件;问我密钥应从哪里获取。把它添加到你客户端的 mcpServers 配置中。
{
"mcpServers": {
"crawl-census": {
"type": "http",
"url": "https://crawlcensus.com/mcp"
}
}
}Ask before you fetch. A drop-in client that stops your crawler spending requests on doors that are shut, and stops it routing around content someone is trying to sell.
Reading robots.txt answers one question and hides two others. Measured across 23,482 domains by Crawl Census:
HTTP 402 Payment Required. That is a price,
not a refusal. Treating it as a block walks away from content the operator wants to sell you.
Retrying around it takes something they are charging for.No dependencies. No key required.
The same measurement is exposed as a remote MCP server, so an agent can ask before it fetches
rather than after it fails. Listed in the
official MCP registry as
io.github.taylorsmithgg/crawl-census.
No authentication for read tools. Streamable HTTP.
Skipping is returned, not raised. It is the normal outcome for a large share of the web, and a crawl loop should be able to count skips without a try/except around every URL.
One call per 1,000 domains instead of one per host:
For a fetcher that only needs a deny list in memory, skip the per-domain calls entirely:
The delta feed is https://crawlcensus.com/agents/<agent>/changes.json?since=<unix> and each
response carries next_since, so a long-running crawler stays current on a few hundred bytes
an hour instead of re-downloading the list.
That file covers robots.txt only. Edge refusal and HTTP 402 are per-request behaviours and
still need preflight or politeFetch.
Measured, not asserted. Twenty hosts fetched concurrently used to cost twenty preflight calls carrying one domain each; the same host requested three times at once cost three, because the cache only helps after the first lookup resolves. The anonymous allowance is 240 calls an hour, so a crawler hit its ceiling at 240 hosts when one call covers twenty-five.
politeFetch now shares work automatically: lookups issued in the same tick leave as one
batched call, and concurrent lookups for the same host await a single request.
batchSize defaults to 25, the per-call cap without a key. Raise it with a Pro or Data key.
batchWaitMs widens the coalescing window for concurrency that arrives in waves rather than
all at once; the default of zero flushes on the next tick.
A pay verdict carries the amount when the origin named one:
Two things worth knowing. Most origins answering HTTP 402 name no amount at all, so price
is usually null and the arrangement has to be made out of band. And pricing is per crawler:
across the measured corpus, 78 of 213 charging origins charge some agents and serve others
free, so ask with your own token rather than assuming a domain on the list will charge you.
partition splits a work queue into crawl, pay, skip, unknown and undecidable.
The last two look alike and are not. unknown means the census has not measured that domain
yet: submit it and the next pass gets a real verdict. undecidable means the site's robots.txt
disallows CrawlCensusBot, so this census will never measure it — retrying is guaranteed waste,
and a loop that resubmits its unknowns each pass would resubmit those forever. The server marks
the difference with a measurable boolean; read that, never the reason prose.
Submission is a separate call on purpose. A library that quietly POSTs during what reads as a lookup is a bad citizen, and you should choose when your queue positions are spent.
A deny list is the smaller half. Measured against the live census, a crawler that skips only robots disallows still spends around 2,900 requests a pass on domains that permit it in robots.txt and refuse it at the edge, or that answer HTTP 402 — for PerplexityBot that set is larger than its deny list. Those fetches return nothing and cost a round trip each.
The three sets are kept apart internally, so a change moves the one it belongs to. why()
follows the same precedence as preflight: a disallow outranks a price, because a price is not
permission.
syncBlocklist / BlocklistSync download the list once, then apply only what changed.
The list is served with the exact position in the change feed it was built at, in an
x-cursor header and a # cursor: comment. The clients read it and resume from there, so
there is no gap between the snapshot and the first poll, and no reliance on your clock being
in step with the server's. Polling by second cannot express a position inside a second, and a
crawl batch writes dozens of events into one, so a second-granularity resume can drop the
remainder of it: measured live, resuming after the first of three same-second changes
recovered both siblings by cursor and neither by second.
refresh() applies only robots transitions to the list, because that is what the list is made
of. Edge refusals, new prices and llms.txt changes come back in other for you to act on
separately — an earlier version deleted those domains from the deny list, so a crawler resumed
fetching exactly what had just started refusing it.
The authoritative definition of each verdict — what it means, what it obliges a crawler to do,
and whether asking again could change it — is published as data at
/api/v1/verdicts. The list below is a summary; if
the two ever disagree, the endpoint is right and this file is stale.
politeFetch skips disallow, refuse and pay by default, which is the endpoint's derived
do_not_fetch set. The copy here is deliberate — a crawl loop should not need a network call to
decide — and a test compares the two so it cannot drift unnoticed.
onPay: "fetch" (on_pay="fetch") overrides the paywall default. It is an explicit opt-in and
is recorded on the result as paidRouteOverridden so it shows up in your logs.
If the census is unreachable every verdict becomes unknown and your crawl proceeds as it
normally would. A third-party outage must never stop your pipeline. There is a live test for
exactly this.
If a figure is wrong, the correction channel is in that response and on your operator page. Registry facts are corrected without argument; disputed measurements are published alongside the dispute with the underlying scan records, rather than quietly amended.
25 domains per preflight call anonymously, 200 with a Pro key, 1,000 with a Data key. Pass
apiKey. Details at https://crawlcensus.com/for-crawlers.
node test.mjs runs against the live census on purpose. The value of this client is whether
its verdicts match reality, and a mocked test would assert only that the mock agrees with itself.
MIT. Data is CC BY 4.0, attribute as "Source: Crawl Census (crawlcensus.com)".
来源:README.md,提交 ee0fd1b
Run a live AI-accessibility audit of a domain: robots.txt policy for every tracked AI crawler, live user-agent probes, JavaScript-free readability, structured data and llms.txt. Returns a score out of 100 with per-check detail.
domain必填Return the most recent stored audit for a domain without triggering a new scan. Faster and free of load on the target site.
domain必填Corpus-level statistics: how many measured domains block each AI crawler, mean access score, llms.txt adoption.
无需输入参数。
Decide whether a crawler may fetch a list of domains before spending requests on them. Works for any crawler token, not only the ones this census tracks: an unrecognised agent is resolved from each domain's stored robots.txt rather than refused. For each domain returns one of: allow (robots permits it and a live request carrying that agent's user agent was served), disallow (robots.txt forbids it), refuse (robots permits it but the edge refused the agent anyway, so the allowance is not real), pay (the origin answered HTTP 402 Payment Required, meaning it will serve this agent on commercial terms), or unknown. The full definition of each, including what it obliges a crawler to do, is published at https://crawlcensus.com/api/v1/verdicts. Built for crawler operators rather than site owners: it prevents wasted fetches against doors that are shut, and flags content an operator is trying to sell rather than withhold.
agent必填domains必填What this census measures and publishes about one AI crawler: how often it is disallowed in robots.txt, how often live requests carrying its user agent are refused at the network edge whatever robots.txt says, whether its operator documents it as honouring robots.txt, and where to correct any of that. Intended for the operator of the agent as much as for anyone studying it, so it includes the correction channel and the public page a claim can be disputed against.
agent必填Every headline finding from the census as discrete, dated records rather than prose. Each carries its value, unit, denominator, measurement date, the page it comes from and a ready-made citation line, plus the caveats that apply to all of them. Use this when answering a question about how open the web is to AI crawlers: lifting a percentage out of a rendered page loses the denominator and the date, which is what makes the number wrong when it is repeated.
无需输入参数。
Queue domains the census has not measured yet so a later crawl_preflight can answer them. This closes the loop crawl_preflight starts: anything it returns as unknown with measurable true is worth submitting, and the reply names any that were already fresh or that this census will never measure, so a caller looping over its own unknowns converges instead of resubmitting the same set. Queueing is a database write rather than a fetch, so the allowance is far higher than scan_site and submitted domains are measured ahead of the ranked backlog.
domains必填已满足适用检查项的 90%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
请在仓库或注册中心条目中声明许可证。
{ "mcpServers": { "crawl-census": { "url": "https://crawlcensus.com/mcp" } } }npm i github:taylorsmithgg/crawl-census-clientpip install git+https://github.com/taylorsmithgg/crawl-census-clientimport { politeFetch } from "crawl-census-client";
const r = await politeFetch("https://example.com/", { agent: "gptbot" });if (r.skipped) console.log(r.verdict, r.reason); // disallow | refuse | payelse process(await r.response.text());from crawl_census import polite_fetch
r = polite_fetch("https://example.com/", agent="gptbot")if r.skipped: print(r.verdict, r.reason)else: process(r.body)const { crawl, skip, pay, unknown } = await partition(urls, { agent: "gptbot" });p = partition(urls, agent="gptbot")p.crawl, p.skip, p.pay, p.unknowncurl https://crawlcensus.com/agents/gptbot/blocklist.txt # one domain per line, commented headerconst sync = await syncBlocklist("gptbot"); // full list onceif (sync.blocked.has(host)) skip();setInterval(() => sync.refresh(), 3600_000); // then deltas only, a few hundred bytessync = BlocklistSync("gptbot")if host in sync: skip()sync.refresh() # {'added': 3, 'removed': 1, 'size': 3310, 'cursor': ...}const r = await politeFetch(url, { agent: "claudebot" });if (r.verdict === "pay") console.log(r.price); // "USD 0.5", or null if none was quotedconst p = await partition(urls, { agent: "gptbot" });await Promise.all(p.crawl.map(politeFetchOne));if (p.unmeasured.length) await submitUnmeasured(p, { agent: "gptbot" });// p.undecidable: read their robots.txt yourself. Asking us again cannot help.const skip = await syncSkipList("gptbot");if (skip.has(host)) continue; // disallowed, refused at the edge, or pricedskip.why(host); // "disallow" | "pay" | "refuse" | nullsetInterval(() => skip.refresh(), 3600_000);const sync = await syncBlocklist("gptbot"); // cursor comes from the list itselfif (sync.blocked.has(host)) skip();setInterval(() => sync.refresh(), 3600_000); // a few hundred bytes per pollconst p = await agentProfile("claudebot");// robots disallow rate, edge refusal rate, operator page, correction channel