mcpField
C
CrawlCheck

CrawlCheck

远程服务
crawlcheck.io浏览器自动化01 天前更新

关于 CrawlCheck

CrawlCheck 的无依赖核心模块可读取网站爬虫策略、核验爬虫身份、提取可引用内容信号并探测面向 Agent 的页面入口。它面向需要在本地复现测量结果的开发者;评分、按爬虫身份抓取、监控和历史记录由托管服务提供。

主要功能

  1. 01解析 robots.txt 爬虫规则
  2. 02检查爬虫访问及规则遮蔽
  3. 03根据 IP 段核验爬虫身份
  4. 04提取可引用内容信号
  5. 05探测 Markdown 和 Agent 页面入口
  6. 06提供 JavaScript 与 Python SDK

适用场景

  1. 01审查 robots.txt 允许哪些爬虫
  2. 02判断访问日志中的爬虫是否可核验
  3. 03测量问答引擎可引用的页面内容
  4. 04在本地复现面向爬虫的页面读取结果

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://crawlcheck.io/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

24
参数类型必填说明
agentstring否你的爬虫 user-agent 令牌,例如 GPTBot
actionenum否即将执行的操作(默认为读取)read | cite | connect | transact | administer
domainstring是纯域名,例如 example.com
policyobject否max_age_hours, on_warn (warn|require_confirmation|block), require_entity, human_approval[], allow[], deny[]
templatestring否safe_citation, safe_data_retrieval, safe_api_connect, safe_mcp_tool, safe_commerce, safe_oauth or support_escalation

详细介绍

crawlcheck-core

tests

CrawlCheck 的无依赖模块。CrawlCheck 是面向智能体网络的验证层,这些模块从生产服务中提取而来。它们是相关读取器,负责为关于网站允许 AI 爬虫和 Agent 执行哪些操作、向它们提供哪些内容的签名回答提供依据。无需构建步骤,无依赖,采用 ESM,支持 Node 20+。

托管服务会以 12 种爬虫身份(另加一个浏览器和两次控制请求)抓取网站,并在三个阶段衡量 47 个部分——触达(爬虫能否访问)、读取(爬虫实际收到什么)、引用(答案引擎能否提取内容)。本仓库是开放核心:其中包含无需网络、存储或密钥的读取器和检查项,公开发布以便复现这些测量结果。

当前包含的内容: robots.txt 策略读取器、爬虫标识符/日志行伪造验证器、可引用内容读取器、图像信号读取器,以及 Markdown 协商和 Agent 表面探测器。**仍保留在服务中的内容:**评分——评判每项读取结果的阈值、每个部分在总评分中的权重,以及用于设定这些数值的语料库校准——还有以这些爬虫身份进行抓取、历史记录、监控、数据集和计费。规则很简单:读取你的页面是你有权检查的事实,而校准才是产品。

本仓库还包含:SDK

  • sdk/——@crawlcheck/sdk(npm):用于 CrawlCheck API 的类型化客户端,以及用于验证签名回答、证据包和收据的零依赖离线验证器。npm test 会针对生产环境运行其测试套件。
  • sdk-python/——crawlcheck(PyPI):面向 Python 3.9+ 的同款客户端和验证器,无依赖。

两者均由私有服务仓库通过工作流发布;该工作流会拒绝发布目录与实际发布内容不完全相同时的版本。因此,这份副本是软件包页面指向的源代码,而不是可能逐渐偏离的镜像。

安装

npm install github:emmanuelorta/crawlcheck-core

目前尚未发布到 npm 注册表,因此请从仓库安装。以下每个子路径都可从该安装中解析。

robots — robots.txt 实际允许哪些爬虫

import { crawlerPolicy } from "crawlcheck-core/robots";

const policy = crawlerPolicy(robotsTxt, /* unreadable */ false, "/wp-admin/");
policy.agents.find(a => a.ua === "GPTBot");
// { ua:"GPTBot", label:"OpenAI training corpus", role:"train",
//   allowed:true, allowed_root:true, named:true, rules_shadowed:true }
policy.shadowed;
// [{ uas:["gptbot"], missing:["/wp-admin/","/feed/"], missing_count:2 }]

对于 114 个具名 Agent(答案引擎、搜索索引、训练爬虫、SEO 工具、社交媒体链接预览器、区域性引擎),它会报告:适用的用户代理组(named)、该组是否允许访问给定路径和根路径,以及该组是否会遮蔽 * 规则。

遮蔽缺陷

爬虫只遵循最具体的组。只要你写下

User-agent: *
Disallow: /wp-admin/

User-agent: GPTBot
Allow: /

GPTBot 就不会再看到 Disallow: /wp-admin/——* 组对它完全不再适用。添加 AI Agent 允许列表的网站,常常会使它们以为仍然生效的所有规则失效。shadowedGroups() 会列出每个具名组,以及它失去的 * 规则中的禁止访问项。我们最先在自己的网站上发现了这个问题:如何屏蔽 AI 爬虫以及用户代理组词汇条目。

API
导出项功能
crawlerPolicy(txt, unreadable, path)获取某个文件的完整逐 Agent 策略。文件为空或无法读取时,会返回 measured:false 和原因——绝不会将文件缺失报告为策略
parseGroups(txt)解析 RFC 9309 组:连续的 User-agent 行共用一个组;按顺序排列 Allow/Disallow 规则
groupFor(groups, ua)适用于某个 Agent 的组:按 Agent 令牌进行最长前缀匹配,* 作为后备项
pathAllowed(group, path) / rootAllowed(group)按 RFC 9309 进行匹配(* 可出现在任意位置,末尾的 $ 锚定路径末尾);最长规则优先;长度相同时 Allow 优先于 Disallow
ruleMatches(rule, path) / decide(rules, path)规则匹配器本身,以及包含判定依据规则的判定结果
shadowedGroups(groups)* 禁止规则失效的命名组(未被该组自己的某条规则覆盖,例如该组自己的 Disallow: /)
AGENTS包含 114 个 Agent 的表:[token, label, role]
不支持的功能

忽略 Crawl-delay、Sitemap 和未知字段,也不会对百分号编码进行规范化(规则和路径按原样比较)。路径匹配遵循 RFC 9309:* 匹配任意长度的字符序列,末尾的 $ 锚定路径末尾,因此 Disallow: / + Allow: /$ 会开放主页,但不会开放其他页面。0.6.0 之前的版本会移除 * 并忽略 $;生产环境于 2026-09-30 变更,0.6.0 让此模块恢复为服务端采用的解析方式。测试会在真实的 robots.txt 文件上检查一致性,包括由 * 规则判定的路径。

agents — 谁发起了抓取,以及其声明是否属实

import { identifyBot, loadRanges, verifyLines } from "crawlcheck-core/agents";

identifyBot("Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)"); // "GPTBot"

const ranges = await loadRanges(fetch);          // the operators' published IP feeds (cache this for a day)
const r = verifyLines(accessLogText, ranges);
r.tally;            // { verified, spoofed, unverifiable, no_bot }
r.forged_rate_pct;  // spoofed / (verified + spoofed), or null when nothing is checkable
r.rows[0];          // { line, bot, ip, verdict, why }

判定结果有三种,绝非两种。已验证:地址属于其声称的运营方公布的范围。伪造:地址不属于该运营方公布的任何范围。无法验证:运营方未发布数据源(Common Crawl、ByteDance、Meta),或数据源无法读取——这类情况会报告为无法验证,而非伪造。伪造率仅以已验证和伪造的数量之和为分母;将无法验证的访问计为伪造会夸大伪造率,计为真实则会低估伪造率。

导出项功能
identifyBot(ua)从 user-agent 中识别命名的爬虫令牌;最长匹配优先;浏览器返回 null
verifyLines(lines, ranges)/api/tool/verify 背后的验证器:将字符串或日志行数组转换为统计结果、比率和逐行判定结果
verifyBotIP(bot, ip, ranges)判定单次访问是否符合预期,返回 true / false / null
loadRanges(fetch)获取 10 个运营方的数据源(OpenAI ×3、Anthropic、Perplexity ×2、Google ×2、Bing、Apple),并转换为验证器所需的格式。该包中唯一的网络调用;缺少数据源时,对应运营方将无法验证
ipInCidr(ip, cidr)、ip4ToInt、ip6ToBig范围匹配,支持 IPv4 和 IPv6(包括 IPv4 映射地址)
BOT_TOKENS、BOT_FEED_OF、BOT_IP_FEEDS、CRAWLERS各类数据表:令牌、令牌 → 数据源系列、数据源 URL,以及包含 18 行的运营方/角色/用途表

存在此功能的原因:CrawlCheck 曾在自有遥测数据中将 85 次“GPTBot”访问中的 78 次计为伪造——实际上那些是我们自己的测试请求。反向 DNS 是数据源取代的旧验证方式;user-agent 是自行声明,仅凭它无法证明任何事情。

quotable — 答案引擎能从页面中直接提取什么

import { quotable } from "crawlcheck-core/quotable";

const { signals } = quotable(html, "Acme Fencing LLC");

signals.lead_defines;    // does the first sentence say what this IS
signals.answer_units;    // paragraphs of 15-70 words that carry a fact
signals.quotable_share;  // % of sentences an engine could attribute unedited
signals.faq_questions;   // declared in FAQPage markup
signals.faq_visible;     // ...and actually readable on the page

问题不在于文案写得好不好,而在于:当答案引擎想陈述一个关于此页面的事实时,页面上是否有一句话可以原样提取——还是说引擎必须自行组织一句话,此时答案采用的就是引擎的措辞,而不是你的措辞。

此模块公布的是读取结果,而非评分。生产服务为页面记录的每一项计数和占比都在这里生成,并且可以针对实时扫描逐字节复现——这正是奇偶校验测试的作用;只有当页面字节仍与采集测试样例时的字节一致,它才会进行比较。服务保留的是用于评判这些读取结果的依据:阈值、该部分在总评分中的权重,以及用于校准这些数值的数百个网站构成的语料库扫描结果。

这一区别不是营销话术,而是让产品保持诚信的关键。任何人都可以检查我们是否正确读取了他们的页面;没人需要盲信我们给出的数值。但他们无法直接提取的是校准数据,而这部分成果是通过语料库积累而来的。

这里明确说明两点容易弄错的细节。FAQ 奇偶校验依据的是整个可见页面,而不是剥离后的正文——<footer> 中的 FAQ 折叠面板仍是读者可见的文本。计数来自正文段落和列表项,导航、页眉、页脚和表单均会移除,因为导航菜单不是散文,计入其中会抬高页面上的所有比率。

导出项作用
quotable(html, nameHint)生产环境中的完整调用:解析 JSON-LD 图谱并返回 { signals }
quoteSignals(html, nodes, nameHint)读取器。nodes 来自 ldGraphNodes(html);若输入没有 <body>,则返回 null

对读取结果进行评分——quoteRows、quoteBlock、部分权重和评分版本——由服务负责,不属于此软件包。输入一个 URL,即可通过免费扫描查看各项结果:https://crawlcheck.io/.

rows — 每个读取器都会返回的三态行

import { tgt, pctScore } from "crawlcheck-core/rows";

一行的格式为 { k, cur, opt, ok, why }:检查项、此页面当前的情况、应有的情况、判定结果,以及其重要性。ok: null 是第三种状态,也是不可或缺的——它表示已测量但未评分。对于过于罕见、无法据此判断的信号、不适用于此类网站的信号,或纯信息类信号,系统会向读者展示,但有意将其排除在计算之外。pctScore() 仅以已评分的行作为分母;如果某个部分没有已评分的行,则返回 null,而不是 0:未测量的部分并不代表未通过。

正因如此,服务区域类企业不会因没有街道地址而被扣分,新上线的网站也不会因缺少实地性能数据而被报告为速度慢。大多数审计工具只有两种状态,它们自身测量中的每一处缺口,都会变成你的零分。

surfaces — Markdown 协商,以及让网站得了 F 的 Accept 标头

import { agentSurfaces, grab } from "crawlcheck-core/surfaces";

const root = await grab("https://example.com/");
const out = await agentSurfaces("https://example.com", root);

out.found.markdown;    // Accept: text/markdown got a text/markdown answer
out.found.agents_md;   // an authored /agents.md, not a 200 of HTML
out.found.link_header; // read off the root response — no extra request
out.evidence.markdown; // { content_type: "text/markdown; charset=utf-8" }

扫描器的 Accept 标头是其身份的一部分;如果设置错误,整个评分衡量的就是错误的文档。这个抓取器一直发送 Accept: */*,直到 2026-09-01。vercel.com 对此返回了 3,025 字节的 text/markdown,而所有命名爬虫行——它们发送 text/html——收到的都是 521 KB 页面。随后,扫描将移动端评分为 0(Markdown 文件中没有 viewport meta)、实体映射一致性评分为 0,并将网站评为 F:这个评分针对的文档,根本不会被任何爬虫获取。Googlebot、GPTBot 和 ClaudeBot 都会发送类似浏览器的 Accept 标头,因此 CRAWLER_ACCEPT 也如此。

因此,Markdown 协商会有意地单独测量一次,通过自己的请求完成——绝不会作为获取页面的副作用。把“这个网站向 Agent 提供 Markdown”与“我们的抓取器请求了错误的内容”混为一谈,就是故障所在。本包中有一个测试专门用于固定这一点:恰好一个请求携带 Accept: text/markdown,其他所有请求都携带 CRAWLER_ACCEPT。

这里的另一条规则是,返回 HTML 的 200 响应属于软 404。大多数网站都没有 /SKILL.md、/agents.md 和三个 .well-known 路径,而兜底路由会为所有这些路径以 200 返回 SPA 外壳;简单的检查会报告它们全部存在。本模块渲染的十行中,有九行仅供参考,且都不计分——采用还处于早期阶段,缺失并非缺陷;而“不适用:未发现 Product、Offer 或结账流程”这样的行,衡量的是网站,而不是检查清单。

导出项作用
agentSurfaces(base, root)同时发出的四个 .well-known 探测请求、从 root 获取的 Link 标头,以及 Markdown 协商请求
grab(url)CrawlCheck 的抓取器:爬虫 UA、类似浏览器的 Accept 标头、10 秒超时中止、读取正文至 60,000 个字符
skillMd(host) / agentsMd(host)/SKILL.md 及其 YAML frontmatter、/agents.md——两者都采用软 404 规则
agentSurfaceRows(result)十行检查结果,包括适用性说明
looksHtml(body, ctype)、UA、CRAWLER_ACCEPT、SURFACE_PATHS各个组成部分均已导出,调用方可以据此复现单个探测请求

jsonld — 页面实际声明的实体

import { ldGraphNodes } from "crawlcheck-core/jsonld";
ldGraphNodes(html); // root nodes and @graph children, merged by @id

有两条规则,都很容易弄错。嵌套在另一个节点中的节点是一个值——如 PostalAddress、Offer、ListItem、Answer——而不是页面所断言的主体,也不需要满足实体所遵循的标准。相同的 @id 出现两次,仍然是同一个主体:RDF 会合并陈述,因此,如果读取器保留第一个节点、丢弃第二个节点,就会把一个在完整 Organization 之前声明了无类型指纹桩的网站,读成拥有一家无类型企业。这是在真实网站上测出的结果,因此这里实现了合并逻辑。

images — 引擎能从图片中了解什么

import { imageSignals, imageRows } from "crawlcheck-core/images";

const sig = imageSignals(html);
// { total:9, no_alt:0, empty_alt:0, no_dimensions:0, lazy:7, lazy_above_fold:0,
//   modern_format:8, svg:1, og_image:true, ld_image:true, ld_imageobject:true, ... }
imageRows({ images: sig });   // eight rows, five scored

五项会计分:缺少 alt 属性、声明了宽度和高度、首屏图片没有延迟加载、JSON-LD 中的实体图片,以及 og:image。另外三项会报告,但刻意不计分,这正是本模块的设计目的。

alt="" 不算缺少 alt。这是装饰性图片的正确标记,而大多数真实网站几乎全是装饰性 alt;若将其计分,几乎所有网站都会因为一个通常正确的信号而不及格。只有缺少属性才计数。文件格式和主图优先级属于偏好,而非缺陷。没有图片的页面会返回一行不计分的结果,因此该部分的得分为 null,而不是 0。

阅读器保留了一项在编写这些测试时发现并修复的问题:属性匹配模式排除了空白,因此任何包含空格的带引号值都会被读为空字符串,导致 alt="a cedar fence" 与 alt="" 无法区分。在六个真实首页上测得,当几乎所有图片都带有实际 alt 文本时,该问题却将 15 张中的 15 张、9 张中的 9 张和 11 张中的 10 张图片报告为装饰性图片。

测试

npm test            # 48 tests, no network (4 skipped)
CC_LIVE=1 npm test  # 48: adds the four live parity checks

robots — RFC 组解析、优先级、双向遮蔽情形、未测量输入、表格完整性、RFC 9309 通配符和结束锚点情形,以及与生产服务针对两个真实 robots.txt 文件的结果一致性。

agents — 令牌匹配、IPv4/IPv6 范围逻辑、三态判定,以及针对 11 条真实日志行,与生产验证器的结果一致性;这些日志行涉及的五个运营方数据源快照已存储于 test/fixtures/ranges.json(文件内注明日期)。

quotable — 阅读器(段落的判定方式、导语测试、问题式标题、FAQ 一致性、句子具备引用价值的条件)、行(哪些会评分、哪些有意不评分、段落少于五段时的处理方式)、JSON-LD 图规则,以及与线上生产扫描结果的一致性。

surfaces — 实际发送的 Accept 标头、仅一次的协商请求、软 404 处理方式,以及行文措辞。

一致性测试样例的工作方式

每个 .quotable.json 都保存了一次在 crawlcheck.io 上真实扫描的输出——服务自身的数据,未经修改地复制,并附有其来源扫描的 id、score version 和 build sha。页面本身不会提交。相反,测试样例会保存 page_sha256,即该次扫描读取的确切响应正文的摘要;实时测试会使用相同的 user-agent 和 Accept 重新获取页面,只有在字节完全相同时才会进行比较,然后检查此模块是否能复现全部 21 个字段和分区分数。

这样一来,偏差会明确暴露,而不是悄无声息地发生。存储的 HTML 内容会永远通过针对已不存在页面的测试;摘要不匹配则会清楚表明,是网站变了,而不是阅读器出错,并且需要重新捕获测试样例。

其中一个测试样例特意不作为重新获取的目标。emmanuelorta.com 每小时刷新一次实时数据——2026-09-03 测得,捕获三小时后,长度相同的响应返回了不同的摘要——因此该测试样例标记为 reproducible: false 并附上原因。保留它是因为两次记录结果不一致(100 对 67;九个 FAQ 问题对零个;协商返回 markdown 对未返回),这证明阅读器不会把一个网站的结果压平成另一个网站的结果。

images — 将以上三种区别固定为测试(缺少 alt 与 alt 为空、无图片的页面、格式和优先级不评分)、行约定和三态判定、格式错误的输入,以及与线上生产扫描结果的一致性,复现服务根据相同字节记录的每个字段。

路线图

下一步:机器文件阅读器(llms.txt、实体映射,以及其背后的软 404 和挑战判定)。每项功能发布时都必须附带与线上服务的一致性测试。

赞助商

crawlcheck-core 和 SDK 均采用 MIT 许可,可免费使用。如果它们为你节省了时间,可以在 GitHub 上赞助这项工作:赞助款用于修复问题、针对真实网站进行测试,并确保库与爬虫和 Agent 的行为同步演进。

机构赞助商 — 暂无。

支持者 — 暂无。

赞助者 — 暂无。

许可证

MIT。由 Emmanuel Orta 构建。服务本身位于 crawlcheck.io:免费审计 AI 爬虫和 Agent 获取的内容,评级范围为 A–F,无需账户。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。