NginxSEO爬虫WordPress站点的 AI 爬虫策略最后都会落到一个文件上:robots.txt。但大多数配置只做对了一半——规则写对了,却没人验证;或者把 Disallow: / 当成「内容不会被用」。这篇把配置、验证脚本和边界一次讲完,代码可以直接跑。
一、先分类:谁该放,谁该拦
| 用途 | 典型 User-agent token | 建议 |
|---|---|---|
| 搜索索引 | Googlebot、Bingbot、Baiduspider、YandexBot、DuckDuckBot、Applebot | 放行,否则丢收录 |
| 搜索厂商的模型用途 | Google-Extended | 与 Googlebot 分开决策 |
| 语料 / 训练 | GPTBot、ClaudeBot、CCBot、Bytespider | 按你的内容策略定 |
| 用户触发的实时抓取 | ChatGPT-User、PerplexityBot | 和训练型分开看,别一刀切 |
两个提醒:
- 这些 token 是各家自己公布的,会新增、会改名,配置前以对方文档为准;
Google-Extended不影响Googlebot的抓取和收录,它管的是内容是否被用于特定用途——所以「拦 Google-Extended」和「能被 Google 搜到」并不冲突。
二、两种拓扑,选一个
A. 默认放行 + 点名拦(推荐)
User-agent: *
Allow: /
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
优点:新搜索引擎、站点监控、SEO 工具不会被误伤。缺点:新出现的训练爬虫在你补名单之前是放行的,所以必须配日志(第六节)。
B. 默认拒绝 + 点名放
User-agent: *
Disallow: /
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
优点:新爬虫默认进不来。缺点:会连社交预览、监控探针、无障碍工具、你没想到的搜索爬虫一起挡掉,等于用收录换安心。除非站点本来就不指望搜索流量,一般不建议。
三、规则语义里的四个坑
- 分组靠
User-agent行,不靠空行。 一个User-agent后面连续的Allow/Disallow属于同一组,再出现User-agent就是新组。用注释分隔,别用空行去赌解析器实现。 - 最长匹配优先,长度相同 Allow 赢。
User-agent: *
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php$
- 只支持
*和$。*匹配任意字符,$锚定结尾。没有正则,也没有「一个字符通配」。 - 路径大小写敏感。
/Blog/和/blog/是两条互不相干的规则。
补充一句:Crawl-delay 不是通用语义,主流搜索爬虫不以它为准,限速要去网关层做(见第七节)。
四、一份可以直接抄的配置
# ===== 搜索引擎:放行 =====
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Baiduspider
Allow: /
User-agent: YandexBot
Allow: /
User-agent: DuckDuckBot
Allow: /
User-agent: Applebot
Allow: /
# ===== 训练 / 语料型:拦 =====
User-agent: GPTBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Bytespider
Disallow: /
# ===== 兜底:未点名 UA 默认放行,交给日志观察 =====
User-agent: *
Allow: /
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php$
Sitemap: https://example.com/sitemap.xml
几处说明:
Sitemap行写绝对 URL,它和User-agent组之间没有从属关系;Disallow: /wp-admin/只是减少无意义抓取,不是安全措施——robots.txt是公开文件,写进去的路径等于告诉别人这里有什么;- 训练型那几个 UA 段按实际策略增删,不要照抄自己都没想清楚的 token;
- 有多个域名或多个入口时,每个入口都要各自放一份,别只改主站。
五、写个判定器先验一遍
肉眼读 robots.txt 很容易读错,尤其是涉及最长匹配和多组规则的时候。下面这段是完整可运行的判定器,实现了分组、*/$ 通配和最长匹配优先:
# robots_check.py
import re
import urllib.request
ROBOTS_URL = 'https://example.com/robots.txt'
TARGETS = [
('Googlebot', '/blog/hello-world/'),
('Bingbot', '/blog/hello-world/'),
('GPTBot', '/blog/hello-world/'),
('ClaudeBot', '/blog/hello-world/'),
('CCBot', '/'),
('SomeNewBot', '/'),
]
def fetch(url):
req = urllib.request.Request(url, headers={'User-Agent': 'robots-checker'})
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.read().decode('utf-8', 'replace')
def parse(text):
groups, cur, closed = [], None, True
for raw in text.splitlines():
line = raw.split('#', 1)[0].strip()
if ':' not in line:
continue
key, val = line.split(':', 1)
key, val = key.strip().lower(), val.strip()
if key == 'user-agent':
if closed or cur is None:
cur = {'uas': [], 'rules': []}
groups.append(cur)
closed = False
cur['uas'].append(val)
elif key in ('allow', 'disallow') and cur is not None:
cur['rules'].append((key, val))
closed = True
return groups
def pattern_len(pattern, path):
if pattern == '':
return -1
if '*' not in pattern and '$' not in pattern:
return len(pattern) if path.startswith(pattern) else -1
body = ''
for ch in pattern:
if ch == '*':
body += '.*'
elif ch == '$':
body += '$'
else:
body += re.escape(ch)
return len(pattern) if re.match('^' + body, path) else -1
def is_allowed(groups, ua, path):
ua_l = ua.lower()
best = None
for g in groups:
if not any(u == '*' or u.lower() in ua_l for u in g['uas']):
continue
for kind, pat in g['rules']:
n = pattern_len(pat, path)
if n < 0:
continue
cand = (n, 1 if kind == 'allow' else 0)
if best is None or cand > best:
best = cand
return True if best is None else bool(best[1])
if __name__ == '__main__':
groups = parse(fetch(ROBOTS_URL))
for ua, path in TARGETS:
verdict = 'ALLOW' if is_allowed(groups, ua, path) else 'DENY'
print(f'{ua:<12}{path:<24}{verdict}')
按第四节的配置跑,输出大致是:
Googlebot /blog/hello-world/ ALLOW
Bingbot /blog/hello-world/ ALLOW
GPTBot /blog/hello-world/ DENY
ClaudeBot /blog/hello-world/ DENY
CCBot / DENY
SomeNewBot / ALLOW
最后一行是关键:默认放行拓扑下,任何没被点名的 UA 一定是 ALLOW。这就是为什么日志不能省。
六、日志里看到的才算数
robots.txt 没有强制力,判断它有没有生效只能看请求。nginx combined 格式里,UA 是第 6 个双引号字段:
# 1) 各 UA 的请求量排名
awk -F'"' '{print $6}' /var/log/nginx/access.log \
| awk '{print $1}' \
| sort | uniq -c | sort -rn | head -20
# 2) 被你在 robots.txt 里拦掉的 UA,实际还来了多少
awk -F'"' '$6 ~ /GPTBot|CCBot|ClaudeBot|Bytespider/ {print $6}' /var/log/nginx/access.log \
| awk '{print $1}' | sort | uniq -c | sort -rn
# 3) 这些请求打了哪些路径
awk -F'"' '$6 ~ /GPTBot/ {print $2}' /var/log/nginx/access.log \
| awk '{print $2}' | sort | uniq -c | sort -rn | head -20
如果被拦的 UA 还在大量出现,只有三种解释:对方不遵守、UA 是伪造的、或者你的配置没生效(缓存、多域名、CDN 边缘没同步)。这三种在日志里长得一模一样,得结合路径和来源 IP 再分辨。
要按爬虫分别看进站量、分别放行或拦截,可以用 AumCrawl,它做的就是「看见哪些 AI 爬虫在抓你的站,并按爬虫分别放行或拦截」这一件事。
七、边界:robots.txt 到此为止
配完之后,必须清楚它能做和不能做的:
- 不遵守的爬虫:协议没有强制力,也没有惩罚机制,写了不等于执行了;
- UA 可以伪造:改一行 header 就绕过去了,服务端看到的 UA 只是对方的一句自述;
- 表达不了「可检索、不可训练」:除非厂商自己把用途拆成独立 token(
Googlebot/Google-Extended就是拆过的例子),否则你只能整段放或整段拦; - 不能限速:它管「哪些路径」,不管「多少次、多快」;
- 不能撤回:已经被抓走的内容,事后改
robots.txt也拿不回来; - 挡不住间接获取:搜索快照、聚合站、他人转载、第三方数据集,都不经过你的
robots.txt。
所以完整的策略是分层的:robots.txt 表达意愿,网关做限速与拒绝,内容分级控制暴露面,必要时再上法务手段。给 AI 侧一个「欢迎读什么」的说明(llms.txt 这类社区提案)也可以有,但它同样是引导性的,没有强制力。
八、WordPress 站怎么落地
WordPress 的 robots.txt 默认是动态输出的,没有物理文件。用 robots_txt filter 追加最省事:
add_filter('robots_txt', function ($output, $public) {
if (! $public) {
return $output;
}
$extra = <<<TXT
# ===== custom: block training crawlers =====
User-agent: GPTBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
TXT;
return $output . $extra;
}, 10, 2);
两个注意点:
- 站点根目录一旦存在物理
robots.txt,WordPress 就不再输出虚拟版本,改 filter 也不会生效; - 上 CDN 或多域名时,每个入口都要单独
curl验一遍,别只看源站。
curl -s https://example.com/robots.txt | grep -iE 'user-agent|allow|disallow|sitemap'
九、上线前清单
- 搜索爬虫逐条列出并放行
- 训练型爬虫按策略点名拦截
- 判定器脚本跑过,结果符合预期
- 连续观察一周日志,确认被拦 UA 的实际行为
- 网关层有兜底限速,不依赖
Crawl-delay -
Sitemap指向真实存在的地址
最后一句话:内容策略决定「值不值得抓」,robots.txt 只决定「允不允许抓」。把这两件事分开想,就不会写成拍脑袋的 Disallow: /。
如果还想让 AI 侧准确知道你希望它读什么、怎么读,可以从 AumViso 的 llms.txt 与结构化数据入手——它是引导,不是强制,配合上面的配置一起用才完整。