跳至正文
网络安全

要不要屏蔽 AI 爬虫?按 bot 决策,按 IP 执行

按职能屏蔽 AI 爬虫:拦截训练类 bot,保留 AI 搜索 bot,并通过检查 IP 对伪造的 GPTBot 返回 403。附经过测试的 nginx 与 Express 代码。

作者GeoIPHub 团队发布于阅读时长阅读约 34 分钟
一道光之门配有检查镜头,一个节点通过、一个被拦下,表示在决定是否屏蔽之前先验证 AI 爬虫的 IP

您的访问日志里出现了 GPTBot、ClaudeBot,还有几个您不认识的名字。您能找到的指南都说同一件事:在 robots.txt 里列出这些 User-Agent,再在服务器配置里屏蔽同样的字符串。

这个建议有漏洞。User-Agent 是客户端自己填写的字符串,写上 "GPTBot" 毫无成本。按名称拦截的黑名单只能挡住诚实的 bot,而它们本来就会遵守 robots.txt;同时,每一个借用您所允许名称的抓取程序都会被放行。名称只是声明,IP 才是证据,这与一般意义上的按 IP 检测 bot 是同一个原则。

下面我们按职能对 bot 分类,说明 robots.txt 能做什么、不能做什么,用我们的 API 查询四个真实的爬虫地址,并给出一套三层策略以及经过测试的 nginx 和 Express 代码。伪造的 GPTBot 会得到 403,真正的 Googlebot 永远不会。

三层叠放的半透明层,一束光穿过、偏折和减速,表示由 robots.txt、IP 验证和限流组成的三层爬虫策略
三层:礼貌请求,验证 IP,再对其余流量限流。

要不要屏蔽 AI 爬虫?取决于 bot 的职能

您应该屏蔽那些职能让您反感的 AI 爬虫,保留那些能给您带来读者的爬虫。AI bot 承担三种不同的职能,屏蔽每一种的代价也各不相同。

  • 训练爬虫收集可能用于训练未来模型的页面。OpenAI 表示,禁止 GPTBot "indicates a site's content should not be used in training generative AI foundation models"(表示网站内容不应被用于训练生成式 AI 基础模型)。屏蔽它们不会让您在搜索上有任何损失。
  • AI 搜索 bot 为页面建立索引,以便助手引用。OpenAI 表示,选择退出 OAI-SearchBot 的网站 "will not be shown in ChatGPT search answers"(不会显示在 ChatGPT 搜索答案中)。屏蔽它们会让您失去引用以及由此带来的点击。
  • 用户触发的抓取器是因为有人让助手阅读某个页面才去加载它。屏蔽它们会让那个人的请求失败。

有付费墙的出版商有充分理由拒绝训练。希望助手推荐自己的品牌应当保留搜索 bot。大多数网站最终会采取折中方案:拒绝训练,允许搜索,对抓取器限速。我们在另一篇文章中介绍了应该屏蔽哪个 OpenAI bot 以及每种选择的代价。代表用户购物的智能体是另一个问题,见结账环节的 AI 购物智能体欺诈。

哪些 AI bot 用于训练,哪些会引用您

下表按职能对您最可能遇到的 AI bot 进行分类,并列出各运营方对 robots.txt 的说法。每个单元格都来自运营方自己的页面,于 2026 年 10 月 20 日打开查阅。

Bot运营方职能robots.txt,运营方原话
GPTBotOpenAI训练Disallow 表示内容 "should not be used in training"(不应被用于训练)
OAI-SearchBotOpenAIAI 搜索选择退出的网站不会显示在 ChatGPT 搜索答案中
ChatGPT-UserOpenAI用户触发的抓取robots.txt 规则 "may not apply"(可能不适用)
ClaudeBotAnthropic训练遵守 robots.txt 和 Crawl-delay
Claude-SearchBotAnthropicAI 搜索遵守 robots.txt 和 Crawl-delay
Claude-UserAnthropic用户触发的抓取遵守 robots.txt 和 Crawl-delay
Google-ExtendedGoogle用于 AI 用途的令牌,不是爬虫仅是 robots.txt 令牌,没有自己的 User-Agent
GooglebotGoogle搜索,不是应屏蔽的 AI bot通用爬虫 "always obey robots.txt rules when crawling automatically"(自动抓取时始终遵守 robots.txt 规则)
PerplexityBotPerplexityAI 搜索未说明;Perplexity 建议允许它
Perplexity-UserPerplexity用户触发的抓取"generally ignores robots.txt rules"(通常会忽略 robots.txt 规则)
CCBotCommon Crawl任何人都可下载的开放网络存档先检查 robots.txt;遵守 Crawl-delay
Applebot-ExtendedApple用于 AI 训练用途的令牌,不是爬虫Apple 表示它 "does not crawl webpages"(不抓取网页)
Meta-ExternalAgentMeta训练和索引未说明;Meta 为它记录了一条 Disallow 规则
BytespiderByteDance我们能打开的页面上没有说明我们能打开的页面上没有说明

能否证明身份与采取什么策略是两个问题。只有运营方公布了抓取来源地址,bot 才能被验证:

运营方地址段文件2026 年 10 月 20 日的前缀数是否记录了反向 DNSGeoIPHub 能否验证
OpenAIgptbot.json,以及 searchbot.json 和 chatgpt-user.json18、39 和 234未记录能,三个都能
Anthropicbots.json,三个 bot 共用一个文件38(文件日期为 2026 年 10 月 7 日)未记录能,匹配结果名为 ClaudeBot
Googlecommon-crawlers.json 及另外四个文件通用爬虫 317 个是:googlebot.com、google.com、googleusercontent.com能
Perplexityperplexitybot.json 和 perplexity-user.json8 和 4未记录能
Common Crawlccbot.json5是,crawl.commoncrawl.org,仅 IPv4能
Appleapplebot.json24是,applebot.apple.com能,针对 Applebot
Meta未公布未公布未记录仅 ASN 匹配,verified: false
ByteDance我们没有找到未找到未记录不能

在从其他网站复制名单之前,先核对上面的名称。一些在该主题上排名靠前的指南仍然列出 anthropic-ai 和 Claude-Web,而 Anthropic 当前的页面并没有列出它们,同时却漏掉了 Claude-SearchBot 和 Claude-User。过时的名单屏蔽的是没人会发送的字符串。

robots.txt 能做什么,不能做什么

robots.txt 可以告诉守规矩的 bot 您的意愿,但它什么也拦不住。定义它的标准 RFC 9309 写道:"These rules are not a form of access authorization."(这些规则不是一种访问授权形式。)无视它是否有法律后果,是律师需要回答的问题。

它仍然是第一层,因为大多数有名有姓的 bot 都会读取它。下面这个文件拒绝训练,欢迎 AI 搜索,并且不影响 Google 搜索:

GeoIPHub API
# Training: no User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot User-agent: Google-Extended User-agent: Applebot-Extended User-agent: Meta-ExternalAgent User-agent: Bytespider Disallow: / # AI search: yes User-agent: OAI-SearchBot User-agent: PerplexityBot Allow: / # Anthropic honours Crawl-delay (seconds between requests) User-agent: Claude-SearchBot Allow: / Crawl-delay: 2

有四个细节决定这个文件是否真的按您的设想工作:

  • Google-Extended 和 Applebot-Extended 是令牌,不是爬虫。 Google 的通用爬虫页面(2026 年 10 月 15 日更新)写道 "Google-Extended doesn't have a separate HTTP request user agent string"(Google-Extended 没有单独的 HTTP 请求 user agent 字符串),并且它 "does not impact a site's inclusion in Google Search"(不会影响网站在 Google 搜索中的收录)。不会有任何请求来自 Google-Extended,因此任何 IP 规则都无法屏蔽它。Apple 的 Applebot 页面也表示 Applebot-Extended "does not crawl webpages"(不抓取网页)。
  • 为阻止 AI 使用而屏蔽 Googlebot,代价是失去搜索。 带有 Disallow: / 的 User-agent: * 组也是如此。
  • 用户触发的抓取器可能会忽略该文件。 OpenAI 对 ChatGPT-User 的说法是 "robots.txt rules may not apply"(robots.txt 规则可能不适用)。Perplexity 表示 Perplexity-User "generally ignores robots.txt rules"(通常会忽略 robots.txt 规则)。
  • 修改不会立即生效。 OpenAI 表示 "it can take ~24 hours from a site's robots.txt update for our systems to adjust"(网站更新 robots.txt 后,我们的系统可能需要约 24 小时才能调整)。

llms.txt、ai.txt 等较新的文件只是提案。和 robots.txt 一样,它们只是请求,并不执行。

相反的错误是把搜索爬虫放进 "AI bot" 黑名单。Contentstack 的屏蔽 AI 爬虫指南展示了一个边缘函数,其 bot 列表中 googlebot 和 bingbot 与 gptbot、claudebot 并列,并对它们一律回复 "Forbidden: AI crawlers are not allowed."。照原样复制的话,这条规则会让网站从 Google 和 Bing 搜索中消失。任何冒充 Googlebot 的请求都是伪造的 Googlebot,那是另一个问题,有另一种解决办法。

真爬虫还是伪造:验证如何工作

区分真爬虫和伪造者的方法,是将连接 IP 与所声称运营方公布的信息进行比对。三种方法中哪一种可用,取决于运营方。

  • 地址段文件。 OpenAI、Anthropic、Perplexity、Common Crawl、Apple 和 Google 都以 JSON 列表公布其前缀。对 OpenAI 和 Anthropic 来说,这是唯一的方法,因为它们的地址没有反向 DNS。
  • 正向确认的反向 DNS。 Google 的验证指南要求您先把 IP 解析为名称,检查域名,再把该名称正向解析回来,确认返回的是同一个 IP。Apple 和 Common Crawl 记录了同样的方法。
  • 签名请求(Web Bot Auth)。 bot 用公布的密钥对每个请求签名。它仍是草案,下文的边缘部分会介绍。

我们的指南如何按 IP 验证 AI 爬虫详细介绍了这三种方法。我们在 2026 年 10 月 20 日 04:37 UTC 未使用密钥查询了四个爬虫地址。以下是 ClaudeBot 的地址,经过裁剪,保留了嵌套结构,值未做修改:

GeoIPHub API
{ "ip": "216.73.216.10", "asn": { "asn": 16509, "org": "AMAZON-02 - Amazon.com, Inc." }, "threat": { "is_crawler": true, "crawler": { "verified": true, "name": "ClaudeBot", "operator": "Anthropic", "category": "ai", "verification_method": "ip_range_list" } }, "scoring": { "fraud_score": 0, "recommended_action": "allow", "detection_methods": ["datacenter_ip", "no_ptr_datacenter", "verified_crawler"] } }

同一轮查询中的另外三个:

IPcrawler.name / 运营方 / 类别是否验证,方法评分,动作detection_methodsASN
132.196.86.1GPTBot / OpenAI / aitrue, ip_range_list0, allowdatacenter_ip, no_ptr_datacenter, verified_crawler8075 MICROSOFT-CORP-MSN-AS-BLOCK
66.249.66.1Googlebot / Google / search_enginetrue, ip_range_list0, allowdatacenter_ip, verified_crawler15169 GOOGLE
107.178.192.1GoogleUserTriggeredFetcher / Google / fetchertrue, ip_range_list0, allowdatacenter_ip, rdns_hosting, verified_crawler396982 GOOGLE-CLOUD-PLATFORM

这个 GPTBot 地址是 132.196.86.0/24 的第一个主机,而该网段是 OpenAI gptbot.json 中的第一个前缀。接着我们在 04:45 UTC 向 DNS 查询名称:

GeoIPHub API
$ dig -x 216.73.216.10 +short # ClaudeBot: NXDOMAIN, no name $ dig -x 132.196.86.1 +short # GPTBot: NXDOMAIN, no name $ dig -x 66.249.66.1 +short crawl-66-249-66-1.googlebot.com. $ dig crawl-66-249-66-1.googlebot.com +short 66.249.66.1

这正是大多数指南遗漏的发现。ClaudeBot 和 GPTBot 没有 PTR 记录,所以反向 DNS 无法验证它们,地址段文件是唯一的检查手段。no_ptr_datacenter 在两者上都触发了,但评分仍为 0,因为 verified_crawler 是一个免责信号。对这些运营方来说,缺少 PTR 是正常的。Googlebot 两种方式都能通过:它的名称会解析回同一个地址。

有两个局限。category 字段对训练类和非训练类 AI bot 一律显示 "ai",所以策略代码应以 crawler.name 为依据。另外,查询只能看到 IP,看不到 User-Agent,因此 API 永远不会标记伪造。将声称的名称与已证实的名称进行比对,这一步在您的代码中完成。您也可以在我们的 IP 查询工具中手动执行同样的检查。

三层策略:请求、验证、限流

一套站得住脚的策略分三层:用 robots.txt 提出请求,将每个 bot 声明与其 IP 进行验证,对剩下的一切做限流。按职能屏蔽,按 IP 执行,永远不要把 Googlebot 放进 AI 黑名单。

第 1 层:请求。 robots.txt 承载您针对每个 bot 的决定,并为遵守 Crawl-delay 的运营方设置该参数。

第 2 层:验证。 当 User-Agent 声称是某个 bot 时,查询连接 IP 并比对名称。如果该 IP 不属于那个 bot,请求得到 403。如果属于,就应用您在第 1 层的决定,这样一个已验证但无视您 Disallow 的 GPTBot 仍会被拒绝。

第 3 层:限流。 其余所有请求都受速率限制:不声称是 bot 的请求、无法验证的 bot,以及代表个人行事的抓取器。

情形示例动作
您允许的已验证 bot来自 66.249.66.1 的 Googlebot正常响应,不限流
您已拒绝但仍在访问的已验证 bot在禁止 GPTBot 的情况下,来自 132.196.86.1 的 GPTBot403
未验证的声明:UA 声称是某个 bot,IP 不匹配来自 216.73.216.10(一个 Anthropic 地址)的 GPTBot UA403,或者您愿意的话改用质询
无法验证的 botBytespider、Meta-ExternalAgent限流;robots.txt 是您唯一的手段
没有 bot 声明浏览器,以及伪装成浏览器的抓取程序按 IP 限流

第三行最有启发性。216.73.216.10 是一个已验证的爬虫,只是不是请求所声称的那个,所以只问"这是不是已验证的爬虫?"的检查会放它进来。要比对名称。同样,107.178.192.1 是一个已验证的 Google 抓取器,但从它发出、自称 Googlebot 的请求并不是 Googlebot。

记录每一次第 2 层的失败。公布的地址段之外的地址通常意味着伪造,但也可能是运营方刚添加的前缀。我们的地址段文件每天重新拉取,查询数据库每天重建,所以一个新前缀可能需要最多约两天才能被验证。

在 nginx 和 Express 中执行,并测试伪造的 GPTBot

执行部分由一个负责查询的小型 Express 服务,加上一份 nginx 配置组成,nginx 会就每个声称是 bot 的请求询问该服务。原生 nginx 无法内联调用 HTTP API,所以由 auth_request 模块向验证服务发送子请求,nginx 缓存其答复。不用 nginx 时,同一个文件也能单独保护一个 Express 应用。

验证服务将 User-Agent 与当前的 bot 令牌进行匹配,用 GET https://api.geoiphub.com/v1/lookup/{ip} 和 x-api-key 请求头查询 IP,将结果缓存一小时,并将 threat.crawler.name 与声称的名称进行比对。它需要一张名称映射表,因为 API 写某些名称时不带连字符:OAISearchBot、ChatGPTUser、PerplexityUser。参见 API 文档中的爬虫字段。

GeoIPHub API
// bot-gate.js: tier 2 (verify the IP behind a bot claim) and tier 3 (rate-limit the rest). // Tested with Node 24.20.0, Express 5.3.0 and express-rate-limit 8.7.1. const express = require('express'); const { rateLimit } = require('express-rate-limit'); const { BlockList, isIP } = require('node:net'); const API_KEY = process.env.GEOIPHUB_KEY; // production: always set a key const CACHE_MS = 60 * 60 * 1000; // one hour per IP const cache = new Map(); // ip: { at, crawler } // Your policy: User-Agent tokens you refuse even when verified. Everything verified and not listed passes. const BLOCKED = new Set((process.env.BLOCKED_BOTS || 'GPTBot,ClaudeBot,CCBot').split(',')); // Maps each User-Agent token to the crawler.name our API returns for that operator's range file. // Anthropic publishes one file for all three of its bots, so a match comes back as ClaudeBot. const EXPECTED = { 'GPTBot': 'GPTBot', 'OAI-SearchBot': 'OAISearchBot', 'ChatGPT-User': 'ChatGPTUser', 'ClaudeBot': 'ClaudeBot', 'Claude-User': 'ClaudeBot', 'Claude-SearchBot': 'ClaudeBot', 'PerplexityBot': 'PerplexityBot', 'Perplexity-User': 'PerplexityUser', 'CCBot': 'CCBot', 'Googlebot': 'Googlebot', 'bingbot': 'Bingbot', 'Applebot': 'Applebot', }; // Longest tokens first, so "Claude-SearchBot" is not read as a shorter token. const TOKENS = Object.keys(EXPECTED).sort((a, b) => b.length - a.length); // A real crawler never connects from a private or loopback address. const privateNets = new BlockList(); for (const [net, bits] of [['10.0.0.0', 8], ['172.16.0.0', 12], ['192.168.0.0', 16], ['127.0.0.0', 8], ['100.64.0.0', 10]]) privateNets.addSubnet(net, bits, 'ipv4'); privateNets.addSubnet('fc00::', 7, 'ipv6'); privateNets.addAddress('::1', 'ipv6'); function claimedBot(ua = '') { const lower = ua.toLowerCase(); return TOKENS.find((t) => lower.includes(t.toLowerCase())) || null; } async function lookupCrawler(ip) { const hit = cache.get(ip); if (hit && Date.now() - hit.at < CACHE_MS) return hit.crawler; const res = await fetch(`https://api.geoiphub.com/v1/lookup/${ip}`, { headers: API_KEY ? { 'x-api-key': API_KEY } : {}, // keyless only for a local test signal: AbortSignal.timeout(1500), }); if (!res.ok) throw new Error(`lookup ${res.status}`); const data = await res.json(); cache.set(ip, { at: Date.now(), crawler: data.threat.crawler }); return data.threat.crawler; } async function verifyBot(req, res, next) { const claim = claimedBot(req.get('user-agent')); if (!claim) return next(); // no bot claim: tier 3 handles it const ip = req.ip.replace(/^::ffff:/, ''); const family = isIP(ip) === 6 ? 'ipv6' : 'ipv4'; if (privateNets.check(ip, family)) { return res.status(403).send(`unverified ${claim} claim\n`); } let crawler; try { crawler = await lookupCrawler(ip); } catch (err) { // Fail mode: never fail open to "verified". Mark it and let the rate limiter decide. req.botUnverified = true; return next(); } // The API never flags a spoof: it sees only the IP, not the User-Agent. // The spoof check is this comparison, in your code. const verified = crawler.verified === true && crawler.name === EXPECTED[claim]; if (!verified) return res.status(403).send(`unverified ${claim} claim\n`); if (BLOCKED.has(claim)) return res.status(403).send(`${claim} is blocked by policy\n`); req.verifiedBot = crawler.name; return next(); } // Tier 3: everyone without a verified bot name, keyed per IPv4 address or per IPv6 /64. // nginx auth_request understands only 2xx, 401 and 403, so the verifier endpoint answers 403. const limiter = rateLimit({ windowMs: 60 * 1000, limit: 120, ipv6Subnet: 64, skip: (req) => Boolean(req.verifiedBot), handler: (req, res) => res.set('Cache-Control', 'no-store') .status(req.path === '/_verify_bot' ? 403 : 429).send('rate limited\n'), }); const app = express(); app.set('trust proxy', 'loopback'); // only nginx on this host may set X-Forwarded-For // Endpoint for nginx auth_request: 204 lets the request through, 403 stops it. // After a failed lookup, no-store keeps nginx from caching the 204, so the limiter sees every request. app.get('/_verify_bot', verifyBot, limiter, (req, res) => { if (req.botUnverified) res.set('Cache-Control', 'no-store'); res.sendStatus(204); }); // Standalone use without nginx: protect every route. app.use(verifyBot, limiter); app.get('/', (req, res) => res.send('ok\n')); app.listen(3000, '127.0.0.1');

有三个选择是刻意为之。如果查询失败或耗时超过 1.5 秒,请求绝不会被视为已验证,而是落到限流器。在 nginx 后面时,这个答复带有 no-store,因此 nginx 不会缓存它,验证服务会把每个请求都计入同一个限额。策略列表保存的是 User-Agent 令牌,所以拒绝 ClaudeBot 并不会拒绝 Claude-SearchBot,尽管两者在查询结果中都显示为 ClaudeBot。trust proxy 只信任回环地址,否则客户端可以自己发送 X-Forwarded-For,从而决定您要检查哪个地址。另外,只有 bot 声明才会被查询,所以查询量随 bot 流量而变,而不是随页面浏览量。使用密钥时,免费套餐每天提供 1,500 次查询,包含全部字段。

nginx 一侧将 User-Agent 映射为声称的 bot,在没有声明时跳过子请求,并按地址和 bot 将验证服务返回的 204 或 403 缓存一小时,标记为 no-store 的答复除外:

GeoIPHub API
# Goes in the http {} block. Tested with nginx 1.30.6 built with --with-http_auth_request_module. # Which bot does the User-Agent claim? Empty means no claim. map $http_user_agent $claimed_bot { default ""; "~*(GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User|CCBot|Googlebot|bingbot|Applebot)" $1; } # Tier 3 key: rate-limit requests with no bot claim. Claims go to tier 2 instead. map $claimed_bot $limit_key { "" $binary_remote_addr; default ""; } limit_req_zone $limit_key zone=perip:10m rate=10r/s; # Cache the verifier's answer per address and claimed bot for one hour. proxy_cache_path /var/cache/nginx/botcheck keys_zone=botcheck:1m max_size=10m inactive=1h; server { listen 8080; location / { limit_req zone=perip burst=20 nodelay; limit_req_status 429; auth_request /_verify_bot; proxy_pass http://127.0.0.1:8000; # your app } location = /_verify_bot { internal; if ($claimed_bot = "") { return 204; } # no claim: no lookup proxy_pass http://127.0.0.1:3000; # bot-gate.js proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Forwarded-For $remote_addr; proxy_cache botcheck; proxy_cache_key $remote_addr$claimed_bot; proxy_cache_valid 204 403 1h; } }

这份配置通过了 nginx -t,我们于 2026 年 10 月 20 日 05:05 UTC 把它放在 Express 文件前面运行。首先是伪造 GPTBot 的测试,经由 nginx,从同一台机器发起:

GeoIPHub API
$ curl -s -o /dev/null -w "%{http_code}\n" \ -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot" \ http://localhost:8080/ 403 $ curl -s -o /dev/null -w "%{http_code}\n" \ -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" \ http://localhost:8080/ 200

这个 GPTBot 字符串是 OpenAI bots 页面上当前的示例。从您自己的机器发出时,请求来自 127.0.0.1,没有任何爬虫会使用这个地址,所以验证服务在查询之前就会拒绝它。要观察查询过程,请直接调用 Express 文件,并带上 nginx 会转发的地址:

GeoIPHub API
$ GPT='Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot' $ GB='Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' $ curl -s -A "$GPT" -H "X-Forwarded-For: 216.73.216.10" http://127.0.0.1:3000/ unverified GPTBot claim $ curl -s -A "$GPT" -H "X-Forwarded-For: 132.196.86.1" http://127.0.0.1:3000/ GPTBot is blocked by policy $ curl -s -A "$GB" -H "X-Forwarded-For: 107.178.192.1" http://127.0.0.1:3000/ unverified Googlebot claim $ curl -s -A "$GB" -H "X-Forwarded-For: 66.249.66.1" http://127.0.0.1:3000/ ok

把第一行从头到尾走一遍。User-Agent 声称是 GPTBot。验证服务查询 216.73.216.10,API 的答复是一个名为 ClaudeBot 的已验证爬虫。ClaudeBot 不是 GPTBot,所以请求得到 403。真正的 GPTBot 地址 132.196.86.1 通过了验证,只是因为该策略拒绝训练才被拒绝;设置 BLOCKED_BOTS=CCBot 时它得到了 200。来自 216.73.216.10 的 Claude-SearchBot 声明得到了 200,因为该策略拒绝的是 ClaudeBot 令牌,而不是 Anthropic 的搜索 bot。一份仅用于测试、转发同样四个地址的 nginx 配置副本返回了 403、403、403 和 200。在我们刻意让 API 无法访问的情况下,经由 nginx 从同一地址发出的 130 个 bingbot 声明得到了 120 个状态为 200 的响应,随后是 403:故障时 bot 声明会被限流,而不是被直接放行。

如果您不使用密钥测试,请控制次数。匿名查询按分钟限流,在一个 60 秒窗口内的第 30 次调用就会返回 429,并将您的 IP 锁定两小时。

对无法验证的流量限流,而不是屏蔽

对于无法从任何一方面证明的流量,限流是正确的工具:无法验证的 bot、用户触发的抓取器,以及任何不声称是 bot 的请求。想了解哪些 bot 公布了地址段文件、哪些没有,请查看我们逐个文件整理的列表。限流可以阻止抓取程序在一个下午内复制您的整个网站,同时不会把抓取器背后的真实用户拒之门外。

两段代码都已经这样做了。nginx 的 limit_req 对没有 bot 声明的请求按客户端地址计数,速率为每秒 10 次,突发上限 20 次。在我们的测试中,40 个连续请求返回了 25 个成功和 15 个状态为 429 的响应。请调整这些数值,如果一次页面加载会获取几十个静态资源,就把它们排除在外。Express 限流器按 /64 对 IPv6 客户端计数,因为一个用户通常控制整个 /64;关于何时扩大到 /56 或 /48,请参阅按 /64 前缀对 IPv6 限流。

对于遵守 Crawl-delay 的 bot,它是更礼貌的做法。Anthropic 的爬虫帮助页面写道 "we support the non-standard Crawl-delay extension to robots.txt"(我们支持 robots.txt 的非标准 Crawl-delay 扩展),Common Crawl 的 FAQ 写道 "We obey the Crawl-delay parameter"(我们遵守 Crawl-delay 参数)。其他运营方则不然。Google 的 robots.txt 规范表示 "such as crawl-delay aren't supported"(诸如 crawl-delay 之类的字段不受支持),Apple 表示 "Applebot does not follow crawl-delay"(Applebot 不遵循 crawl-delay)。OpenAI 和 Perplexity 的页面没有提及。

第 2 层只能捕获声称是某个 bot 的请求。要屏蔽从不声称自己是 bot 的 AI 抓取程序,需要在第 3 层加入行为特征:请求速率、访问路径,以及请求来自的网络。我们关于如何从日志中检测网页抓取的文章介绍了这些信号。

边缘方案:Cloudflare 的 AI 屏蔽与按次抓取付费

如果您使用 Cloudflare,可以通过一项设置而不是代码来屏蔽 AI 爬虫,或者按请求向它们收费。两者都无法免去您了解所面对的是哪些 bot 的需要。

Cloudflare 于 2025 年 7 月 1 日在一篇文章(最后修改于 2026 年 7 月 15 日)中宣布,它正在 "changing the default to block AI crawlers unless they pay creators for their content"(将默认设置改为屏蔽 AI 爬虫,除非它们为内容向创作者付费)。如果您同时运行自己的规则,请检查 Cloudflare 已经在您的 zone 上屏蔽了什么,使两层保持一致。

使用按次抓取付费(pay per crawl)时,请求付费页面的爬虫会收到 "a 402 Payment Required response, accompanied by a crawler-price header"(一个 402 Payment Required 响应,并附带 crawler-price 请求头),并可以带着付款意向重试。爬虫依据 Web Bot Auth 提案对请求签名,这样就没人能冒充他人付款。Cloudflare 表示按次抓取付费 "is currently in private beta"(目前处于非公开测试阶段)。

Web Bot Auth 还不是标准。2026 年 10 月 20 日,IETF webbotauth 工作组只有一份工作组文档,即 2026 年 9 月 1 日的 draft-ietf-webbotauth-httpsig-protocol-00,没有 RFC;其章程中 2026 年 4 月发布标准轨道规范的里程碑已经过期。一份个人草案 draft-illyes-webbotauth-jafar 提议为爬虫地址段文件制定通用的 JSON 格式。在这些落地之前,您能检查的就是 IP 地址段和反向 DNS。

衡量策略带来的变化

衡量策略的方法是:对每个 bot 请求记录验证结果,然后比较变更前后的情况。

  • 按已验证 bot 统计占比。 在每个声称是 bot 的请求旁记录 crawler.name 和 crawler.verified。声明失败的占比就是您遭遇伪造的比例。按名称统计的占比显示哪些运营方真的在抓取您。
  • AI 引荐流量。 统计来自 chatgpt.com、perplexity.ai、claude.ai、copilot.microsoft.com 和 gemini.google.com 的引荐访问。把这个数字当作下限,因为部分助手流量到达时不带引荐来源。
  • 抓取量。 比较变更前后每个 bot 每天的请求数。Search Console 的抓取统计信息报告只涵盖 Google 的爬虫,其他 bot 的数据只能来自您自己的日志。

如果在屏蔽某个 AI 搜索 bot 之后引用量下降,您就得到了关于这个 bot 的答案。

何时重新审视您的 AI 爬虫策略

每当运营方新增 bot、修改地址段文件或弃用某个令牌时,都要重新审视您的 AI 爬虫策略,并且至少每季度一次。OpenAI 的 bots 页面现在列出了 OAI-AdsBot,它有自己的 adsbot.json。我们的爬虫数据源不包含该文件,上面的代码也没有列出它,所以在您做出决定之前,OAI-AdsBot 声明会落到第 3 层。

要清楚这套方案做不到什么:

  • 它只能证明公布了地址段的 bot。 Bytespider 和 Meta-ExternalAgent 无法验证,对其中任何一个的伪造看起来都和真的一模一样。
  • 新前缀存在延迟。 今天新增的前缀可能需要最多约两天才能被验证,因此真实的 bot 可能短暂地无法通过第 2 层。
  • IP 屏蔽可能适得其反。 Anthropic 警告说,屏蔽其 IP "may not work correctly or persistently guarantee an opt-out, as doing so impedes our ability to read your robots.txt file"(可能无法正常生效,也无法持续保证退出,因为这样做会妨碍我们读取您的 robots.txt 文件)。即使第 2 层已经在执行,也要在 robots.txt 中保留 Disallow。
  • 查询看不到 User-Agent。 伪造检查是您代码中的名称比对。
  • 第 2 层会漏掉从不声称名称的抓取程序。 那些是第 3 层的工作。

从您自己的日志开始。挑一个本周声称是 GPTBot 或 ClaudeBot 的地址,对它做一次查询,并将 crawler.name 与它声称的名称进行比对。

常见问题

我应该屏蔽 AI 爬虫吗?

取决于 bot 的职能。如果您不希望内容被用于训练模型,就屏蔽 GPTBot、ClaudeBot 这类训练爬虫。如果您希望被引用,就允许 OAI-SearchBot 这类 AI 搜索 bot。对用户触发的抓取器做限流,而不是依赖 robots.txt。

robots.txt 能阻止 AI 爬虫吗?

只能阻止愿意遵守它的爬虫。RFC 9309 写道,其规则 "are not a form of access authorization"(不是一种访问授权形式)。使用 GPTBot User-Agent 的抓取程序会无视它,所以执行必须检查请求到达时的 IP。

屏蔽 AI 爬虫会影响 SEO 吗?

屏蔽 Google-Extended 令牌不会影响 Google 搜索;Google 表示它 "does not impact"(不会影响)网站在搜索中的收录。为了阻止 AI 使用而屏蔽 Googlebot,会让您从搜索中消失。屏蔽 OAI-SearchBot 这类 AI 搜索 bot,会让您无法出现在该助手的搜索答案中。

可以按 IP 屏蔽 Google-Extended 吗?

不可以。Google-Extended 是一个 robots.txt 令牌,没有自己的 User-Agent。实际抓取由 Google 现有的爬虫完成,所以唯一的控制手段是为该令牌写一条 robots.txt 规则。Applebot-Extended 同理:Apple 表示它不抓取网页。

如何判断一个自称 GPTBot 的请求是真的?

检查连接 IP 是否在 OpenAI 公布的 gptbot.json 中。反向 DNS 帮不上忙:我们检查过的 GPTBot 和 ClaudeBot 地址都没有 PTR 记录。一次查询会返回 threat.crawler.verified 和 bot 名称,由您的代码将该名称与 User-Agent 进行比对。

所有 AI 爬虫都能按 IP 验证吗?

不能。我们没有找到 ByteDance 为 Bytespider 发布的地址段文件,Meta 也没有为 Meta-ExternalAgent 发布任何文件,所以这两者都无法按 IP 验证。对它们做限流,而不要信任 User-Agent。

作者

GeoIPHub 团队

GeoIPHub API 背后的团队

由 GeoIPHub 团队撰写,即构建 GeoIPHub IP 情报 API 及其扫描器的团队。草稿在 AI 辅助下完成,并由团队编辑;除非标注为示例,API 示例均为针对线上 API 的真实查询。

分享本文
更多文章

相关 文章