robots.txt 是一份「你对爬虫的声明」,它记录的是你希望发生什么,而不是发生过什么。要判断某个 AI 爬虫是否真的抓过你的站,唯一的证据是访问日志。
下面是一套可以每周跑一次的流程:确认日志字段 → 按 UA 聚合 → 拉行为画像 → IP 校验 → 判定抓取质量 → 落成放行/拦截策略。
一句话:robots.txt 用来看「声明」,日志用来看「事实」,两者要能对上。
nginx 的 combined 格式已经包含 $http_user_agent:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;先确认字段对不对:
tail -n 3 /var/log/nginx/access.log一行里应该能清楚看到 "GET /some-path HTTP/1.1" 200 12345 "-" "Mozilla/5.0 ..."。如果 UA 位置是个 -,说明日志格式没打开,后面所有分析都无从谈起。
源站日志里的 $remote_addr 会是 CDN 回源节点的 IP。这时日志能判断「抓了什么」,不能判断「谁抓的」。两种做法:
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;验证还原是否生效:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5如果前几名全是同一两个网段,多半还是 CDN 节点 IP,说明还原没生效。
三十秒版本:
awk -F'"' '{c[$6]++} END {for (u in c) printf "%8d %s\n", c[u], u}' /var/log/nginx/access.log \
| sort -rn | head -30这一眼能看到:谁在大量抓、有没有空 UA、有没有可疑字符串混在一堆正常浏览器 UA 里。
历史日志一起看(按天轮转、已压缩的):
zgrep -ai 'gptbot' /var/log/nginx/access.log*.gz | wc -l光有请求数不够,要看它抓了什么、抓成功没有。保存下面这段为 ai_crawler_report.py(按 combined 格式解析):
#!/usr/bin/env python3
import re
import sys
import collections
LINE = re.compile(
r'(?P<ip>\S+) \S+ \S+ \[(?P<ts>[^\]]+)\] '
r'"(?P<method>[A-Z]+) (?P<path>\S+)[^"]*" '
r'(?P<status>\d{3}) (?P<size>\S+) "[^"]*" "(?P<ua>[^"]*)"'
)
SUSPECT = re.compile(
r'gptbot|oai-searchbot|chatgpt-user|claudebot|claude-user|perplexitybot|'
r'ccbot|bytespider|amazonbot|applebot|meta-externalagent',
re.I,
)
def main(paths):
stats = collections.defaultdict(lambda: {
'hits': 0, 'ips': set(), 'status': collections.Counter(),
'paths': collections.Counter(), 'first': None, 'last': None,
})
for path in paths:
with open(path, encoding='utf-8', errors='replace') as fh:
for line in fh:
m = LINE.match(line)
if not m:
continue
hit = SUSPECT.search(m.group('ua'))
if not hit:
continue
b = stats[hit.group(0).lower()]
b['hits'] += 1
b['ips'].add(m.group('ip'))
b['status'][m.group('status')] += 1
b['paths'][m.group('path').split('?')[0]] += 1
b['first'] = b['first'] or m.group('ts')
b['last'] = m.group('ts')
for name, b in sorted(stats.items(), key=lambda kv: -kv[1]['hits']):
print(f"\n== {name} ==")
print(f" hits={b['hits']} unique_ips={len(b['ips'])}")
print(f" window: {b['first']} -> {b['last']}")
print(" status: " + ", ".join(f"{k}x{v}" for k, v in b['status'].most_common(5)))
print(" top paths:")
for p, n in b['paths'].most_common(8):
print(f" {n:>7} {p}")
if __name__ == '__main__':
main(sys.argv[1:] or ['/var/log/nginx/access.log'])用法:
python3 ai_crawler_report.py /var/log/nginx/access.log
# 历史压缩日志
gunzip -c /var/log/nginx/access.log.1.gz > /tmp/access.1.log
python3 ai_crawler_report.py /tmp/access.1.log注意:上面那份 UA 名单只是「常见字符串」,用来缩小排查范围,不能当作身份证明——UA 是客户端自己写的,谁都能复制。身份要靠下一步。
两条路子,能同时用最好。
① 反解 + 前向确认(FCrDNS):先反查 PTR,再用 PTR 正查回来,看是否回到同一个 IP。
check_ip() {
ip="$1"
ptr=$(dig +short -x "$ip" | head -1 | sed 's/\.$//')
printf '%-16s PTR=%s ' "$ip" "${ptr:-<none>}"
if [ -n "$ptr" ] && dig +short "$ptr" A | grep -qxF "$ip"; then
echo "FCrDNS=OK"
else
echo "FCrDNS=FAIL"
fi
}
# 只查请求量靠前的 IP,避免对全量做 DNS 查询
awk '{print $1}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -50 | awk '{print $2}' \
| while read -r ip; do check_ip "$ip"; done② 对方公布的 IP 段:如果爬虫运营方公开了 IP 段文件,下载后用 grep -F -f 或现成的网段比对工具校验。这一条比反解更硬。
两个结果叠加,才有结论:
UA 声明 | IP 校验 | 结论 |
|---|---|---|
声称是某个 AI 爬虫 | PTR / 网段对得上 | 基本可信,进入抓取质量判定 |
声称是某个 AI 爬虫 | 校验不通过 | 伪装流量,按普通可疑流量处理 |
普通浏览器 UA | PTR 指向爬虫网段 | 有爬虫改了 UA,单独标注 |
有的爬虫只取 /robots.txt 就走了,这不算抓过你的内容。三个判据一起看:
/robots.txt、/、/sitemap.xml。状态码分布一行就能出:
grep -ai 'GPTBot' /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn再看看它有没有抓到不该给的内容(后台、站内搜索结果、购物车、结算页):
grep -ai 'GPTBot' /var/log/nginx/access.log \
| awk '{print $7}' \
| grep -aE '(/wp-admin/|/search\?|\?s=|/cart|/checkout)' \
| sort | uniq -c | sort -rn | head有输出就说明,「不该抓」的清单需要用服务器层规则落实,而不是写在 robots.txt 里等它自觉。
一个实用分工:用 CDN 日志判定「谁来过、来了多少」,用源站日志判定「抓到了什么、返回了什么」,两边按时间和 path 关联。
有了「哪个爬虫、多久来一次、抓了哪些路径、成功率如何」这张表,策略才有依据。常见的三类处理:
这里要强调:robots.txt 是请求,执行点在服务器 / CDN / WAF。 想让拦截真正生效,规则必须落在 nginx、CDN 或 WAF 上,而不是只写一行 Disallow。
如果站点跑在 WordPress 上,想在站内持续看到「哪些 AI 爬虫在抓、各自抓了多少」,可以用免费插件 AumCrawl,它做的就是这件事,并且能按爬虫分别放行或拦截:crawlers。
动作 | 命令 / 位置 | 看什么 |
|---|---|---|
确认日志在写 |
| UA 字段是否存在 |
UA 全貌 |
| 有没有新增可疑 UA |
拉行为画像 |
| 请求量、路径、状态码分布 |
IP 校验 |
| 是否存在伪装 |
质量判定 | 状态码 + 路径 | 是「碰过」还是「真抓」 |
策略落地 | nginx / CDN / WAF | 拦截是否真的生效 |
归档 | 日志按月打包 | 便于回溯趋势 |
做完这一遍,你会得到一张属于自己站点的「AI 爬虫访问清单」——它比任何 robots.txt 都更能说明实际发生了什么。
AumCrawl 是免费插件,可以在 WordPress 后台直接装:AumCrawl。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。