用访问日志识别 AI 爬虫及抓取强度
通过命令行和 Python 对访问日志进行三步分析:按 User-Agent 分类、核验请求来源、统计抓取频率。文章还提醒反向代理可能遮蔽真实客户端 IP,爬虫名称列表应放入可维护配置。
AI 深度解读
识别 AI 爬虫,不一定要先接 SDK 或装统计插件。作者给出的办法,是对同一份 nginx 访问日志做三轮处理:按 User-Agent 分类、按来源 IP 验证身份、按时间和路径观察抓取行为。核心结论是:爬虫自报的名字只是线索,来源和行为才是判断依据。
这套方法适合已经能拿到服务器日志的开发者。它能帮助你回答谁在访问、访问了多少、是否需要限制,但原文提供的是操作方案,没有给出真实站点的实测结果,有几处判断也需要收紧。
先确认日志里的 IP 是谁,再统计爬虫
作者使用 nginx 的 combined 日志格式,一行对应一次请求,包含客户端 IP、时间、请求行、状态码、响应字节数、referer 和 User-Agent。这些字段足以支撑初步分析,不需要额外埋点。
第一步是看看站点实际收到了哪些 User-Agent:
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
这里要看的是 -F'"' 和 $6:命令按双引号分隔,取出 combined 格式中的 User-Agent,再统计出现次数,列出前 20 项。换成 JSON 或自定义日志后,这个取法不能直接照搬。
作者列出的识别对象包括 GPTBot、OAI-SearchBot、ChatGPT-User、ClaudeBot、PerplexityBot、CCBot、Bytespider、Amazonbot 和 meta-externalagent。匹配规则应该作为可维护的数据保存,因为名字可能调整,也会有新爬虫出现。不过,示例脚本仍把规则写在 BOTS 字典里,配置外置还需要自己完成。
更关键的前提是客户端 IP。站点前面有 CDN 或反向代理时,$remote_addr 可能记录的是代理地址。这个问题没处理好,后续的独立 IP 数、来源验证、单 IP 请求量都会失真。
作者的 ai_crawlers.py 用正则解析日志,通过 classify(ua) 做不区分大小写的子串匹配;空值归为 no-ua,包含 bot|crawl|spider|slurp 的其余请求归为 other-bot,剩下归为 browser。最后一个名字只能理解为「没有匹配到机器人特征」,不能证明请求来自真人浏览器。
执行 python ai_crawlers.py /var/log/nginx/access.log,可以得到总请求量、独立 IP 数、最常访问路径、每日矩阵、状态码分布,以及每类爬虫最忙的 5 个 IP。状态码只展示数量最多的 4 类。脚本还会跳过无法解析的行,因此正式使用时最好补上解析失败计数,避免把漏数当成流量下降。
User-Agent 对得上,为什么还不能确认身份
User-Agent 是客户端自己填写的字符串,匹配到 GPTBot,只能说明对方自称 GPTBot。
作者建议用运营方公布的 IP 地址段验证来源。示例从 published-ranges.json 读取爬虫名称与 CIDR 地址段的映射,再通过 ipaddress.ip_network()、ipaddress.ip_address() 判断请求 IP 是否落在相应网段内。
其中的 203.0.113.0/24 是示例,不能当成 GPTBot 的真实地址段。原文也没有附上各运营方的地址段文件,需要使用者另行核对。
地址段还要定期获取、保存并比较差异。否则,运营方调整了出口,你维护的旧名单可能把真实爬虫判成未通过验证。这里的配置更新和历史记录,与第一次写出判断函数同样重要。
反向 DNS 是补充检查。作者给出的 fcrdns(ip) 先调用 socket.gethostbyaddr() 查主机名,再调用 socket.gethostbyname_ex() 正向解析,确认结果包含原 IP,避免只相信反向解析返回的名字。作者建议只对地址段检查未通过的请求执行它,减少查询开销。
但这段函数没有验证运营方声明的域名后缀,正向解析部分也没有完整覆盖 IPv6。即便正反向一致,也不能直接等同于身份确认。
原文的三种结果定义还有不一致:函数遇到没有地址段名单的爬虫名称时返回 None,注释称其为 unknown;后文却把 unknown 描述为没有 User-Agent。落地时应该分别保留「缺少 UA」「缺少验证名单」「未通过网段验证」,否则报表会混淆身份信息不足与验证失败。
抓了多少次,要和抓了什么一起看
作者用 grep -i 'ClaudeBot' 筛选日志,再通过 awk -F'[][]'、cut -d: -f1,2、sort | uniq -c 统计每小时请求量。这比单看总量更容易找到突发访问,但结果仍然属于「自称 ClaudeBot」的请求,并没有自动接入来源验证。
原文给出几种排查方向:少量 IP 每天稳定访问,可以记录为基线;单个 IP 访问数千个 URL 且大多返回 404,可能是探测,也可能是 sitemap 还指向已删除页面;几分钟内突然增多,可以对照当天部署记录;大量 IP 各访问少量页面,则应先验证地址段,再判断是否属于同一运营方。
同一路径不断变化查询参数,也可能让缓存无法复用,导致每次请求重新生成 HTML。对服务端渲染站点来说,请求次数之外,还需要看这些请求是否持续触发渲染。
这些都是排查线索。404 多不能单独证明恶意,短时间突增也不能单独证明是更新后的重新抓取。
路径同样重要。访问 /blog/* 与访问 /cart、/checkout、/api/*,业务影响明显不同。不过,作者把前者直接称为索引行为,证据仍不充分;日志能证明访问发生过,不能独自证明抓取目的。
为了持续观察,作者建议保留足够历史进行逐周比较,用 cron 定时运行、输出 JSON、与前一天比较,并优先检查请求超过 100 次的新 UA。这里的 100 次是作者建议的关注阈值,没有对照数据支持。
日志很大时,可以按爬虫家族分流,减少反复扫描:
map $http_user_agent $ai_log {
default main;
~*GPTBot ai_gptbot;
~*ClaudeBot ai_claude;
~*PerplexityBot ai_perplexity;
~*CCBot ai_ccbot;
}
open_log_file_cache max=100 inactive=60s;
access_log /var/log/nginx/$ai_log.log combined;
要注意 map 放在 http 块内,access_log 使用了变量路径,因此作者配上了 open_log_file_cache max=100 inactive=60s。这段配置负责分流记录,不负责验证或封禁。
我的判断
我认可这套方法的顺序:先看请求,再验证来源,最后决定如何处理。相比只有总访问量的面板,原始日志保留了路径、状态码和 IP,便于追查判断依据。
但它更像一个分析起点。完整脚本会先把解析结果全部放进 rows,大文件下存在内存压力;JSON 输出、每日差异、名单更新和身份验证,也没有被串成可直接运行的完整流程。原文声称会生成三类报告,实际仍需要使用者拼装。
关于处置,作者强调 robots.txt 是请求对方遵守的规则,nginx 拦截才是服务端执行的限制,并建议拦截后继续留日志,方便六周后回看影响。这个区分有用,但原文写了「四个分类、四种行动」,却没有给出具体分类和对应动作,不能当作现成策略表。
文末推荐的 WordPress 产品 AumCrawl,按原文描述可查看爬虫并分别允许或阻止,但没有提供准确率、维护成本或效果对比。这属于产品推荐,不能据此判断比自建方案更合适。
能拿来做什么
- 给 nginx 前面的 CDN、反向代理检查一次真实客户端 IP 配置,再运行分类脚本,建立包含请求量、路径和状态码的初始基线。
- 排查前端 SSR 或 Node 服务的异常负载时,把突发请求、查询参数变化与部署时间放在一起看,确认是否反复触发渲染,再决定是否调整缓存或限制访问。
- 将 UA 分类与来源验证分别记录,并保留「缺少名单」的状态;首次接入时先观察,避免把验证信息不完整直接当成封禁依据。
- 把分析任务接入 cron,补上 JSON 输出、解析失败计数和逐日比较;需要限制爬虫时继续记录被拦截请求,让后续调整有据可查。