Crawl Census

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?

已驗證Streamable HTTP可網頁執行網頁搜尋與抓取資料與分析

概覽

AI 產生的概覽

一個遠端 MCP 伺服器,讓爬蟲在抓取前知道某個網域會放行、拒絕,還是對其 user agent 收費。

可以做什麼
抓取前檢查網域:為任意爬蟲識別碼回傳預檢結論(allow、disallow、refuse、pay、unknown),並可提交尚未量測的網域、讀取已存的站點稽核,或即時掃描並給出百分制評分。查詢爬蟲檔案:某個 AI 爬蟲在 robots.txt 中被禁止的頻率、在網路邊緣被拒絕的頻率,以及更正已發布數據的位置。取得普查結論:帶日期、分母與引用行的重點紀錄,以及關於封鎖情況和 llms.txt 採用率的語料庫層級統計。
適用情境
適合在建置或執行網路爬蟲、代理抓取程式時使用,避免把請求浪費在封鎖自己、在網路邊緣拒絕自己或回傳 HTTP 402 的網域上。也適合回答關於網路對 AI 爬蟲開放程度的問題,並提供可引用的數據。
執行需求
遠端 streamable HTTP 端點;讀取類工具無需驗證。匿名使用限制為每次預檢呼叫 25 個網域、每小時 240 次呼叫;更高額度需要向客戶端程式庫傳入 Pro 或 Data API 金鑰。需要能連線至該普查服務的網路。
安裝前請注意
submit_domains 工具會把網域排入待量測佇列,屬於寫入而非單純讀取。結論來自第三方的量測,可能為 unknown 或已過時;README 建議讀取 measurable 旗標而不是 reason 文字。pay 結論表示來源站要求付費;繞過付費牆需明確選擇開啟。資料採用 CC BY 4.0,需要標示來源。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Crawl Census,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.taylorsmithgg%2Fcrawl-census/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "crawl-census": {
      "type": "http",
      "url": "https://crawlcensus.com/mcp"
    }
  }
}

README

crawl-census-client

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:

  • 2,874 domains permit AI agents in robots.txt and then refuse those same agents at the network edge. A parser sees permission; the fetch returns 403. You pay for the round trip and get nothing.
  • 208 domains answer an AI user agent with 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.

MCP server

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.

json
{ "mcpServers": { "crawl-census": { "url": "https://crawlcensus.com/mcp" } } }
ToolAnswers
crawl_preflightwill these domains serve my agent, refuse it, or charge it?
agent_profilewhat does this census publish about my crawler, and how do I correct it?
census_factsthe headline findings as dated records with denominators and citation lines
site_reportthe stored audit for one domain
scan_sitemeasure a domain now
census_statscorpus-level totals

No authentication for read tools. Streamable HTTP.

Install

bash
npm i github:taylorsmithgg/crawl-census-clientpip install git+https://github.com/taylorsmithgg/crawl-census-client

Use

js
import { 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());
python
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)

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.

Split a queue before crawling it

One call per 1,000 domains instead of one per host:

js
const { crawl, skip, pay, unknown } = await partition(urls, { agent: "gptbot" });
python
p = partition(urls, agent="gptbot")p.crawl, p.skip, p.pay, p.unknown

Or just take the file

For a fetcher that only needs a deny list in memory, skip the per-domain calls entirely:

bash
curl https://crawlcensus.com/agents/gptbot/blocklist.txt   # one domain per line, commented header
js
const sync = await syncBlocklist("gptbot");   // full list onceif (sync.blocked.has(host)) skip();setInterval(() => sync.refresh(), 3600_000);  // then deltas only, a few hundred bytes
python
sync = BlocklistSync("gptbot")if host in sync: skip()sync.refresh()          # {'added': 3, 'removed': 1, 'size': 3310, 'cursor': ...}

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.

What a crawl costs the census

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.

patternbeforenow
20 hosts, concurrent20 calls1 call of 20
1 host, 3 URLs, concurrent3 calls1 call
60 hosts, concurrent60 calls3 calls (25 / 25 / 10)
partition then fetch2 calls1 call

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.

Paying, when an origin quotes a price

A pay verdict carries the amount when the origin named one:

js
const r = await politeFetch(url, { agent: "claudebot" });if (r.verdict === "pay") console.log(r.price);   // "USD 0.5", or null if none was quoted

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.

Two kinds of unknown

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.

js
const 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.

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.

Skipping everything that will not serve you

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.

js
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);
agentdeny listalso skippabletotal
GPTBot3,5422,9446,486
ClaudeBot3,1693,1946,363
PerplexityBot1,1283,6584,786

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.

Keeping a deny list current

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.

js
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 poll

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.

Verdicts

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.

VerdictMeaningDefault behaviour
allowrobots.txt permits this agent, and a live request carrying its user agent was servedfetch
disallowrobots.txt forbids this agent at the site rootskip
refuserobots.txt permits it; the edge refused it anyway. The allowance is not realskip
paythe origin answered HTTP 402. It will serve this agent on commercial termsskip
unknownnot measured recently enough to answerfetch

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.

It degrades, it does not fail

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.

What we publish about your agent

js
const p = await agentProfile("claudebot");// robots disallow rate, edge refusal rate, operator page, correction channel

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.

Limits

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.

Tests

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

工具

7

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必填

site_report

Return the most recent stored audit for a domain without triggering a new scan. Faster and free of load on the target site.

輸入參數domain必填

census_stats

Corpus-level statistics: how many measured domains block each AI crawler, mean access score, llms.txt adoption.

無需輸入參數。

crawl_preflight

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必填

agent_profile

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必填

census_facts

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.

無需輸入參數。

submit_domains

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必填

版本歷史

1
  1. v1.0.0最新Sep 16, 2026

評分

已滿足適用檢查項的 90%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

    請在儲存庫或註冊中心條目中宣告授權條款。