Gentoo 项目的 Bugzilla 实例因为 AI bot scraper 流量过载而被迫限制访问这类事件已经不是孤立案例。任何还在用传统 Nginx 访问日志、默认 robots.txt、不做限流的开源基础设施都可能在某一天早上收到“页面打不开”的告警。AI 爬虫的抓取方式与普通搜索引擎差别很大请求频率更高、遍历路径更广、很多还会带上查询参数去翻动态页面。问题一旦出现通常不是加几台机器就能解决而是要先把“是谁在打、打的是什么、为什么打挂”这条链路理清楚。这篇文章从 Gentoo Bugzilla 事件出发围绕一个自建 Web 服务如何抵御 AI 爬虫洪峰整理出一套可以落地的思路先识别流量再在入口拦截然后做动态封禁和应用层限流最后通过日志和指标验证效果。整个过程不依赖商业产品使用 Nginx、fail2ban、robots.txt 和日志分析就能完成大部分工作。适合正在维护开源项目站点、自建 Bugzilla 或其他 Web 应用的开发者参考。1. 从 Bugzilla 过载看 AI 爬虫流量问题的本质1.1 Bugzilla 为什么容易成为 AI 爬虫的受害者Bugzilla 是很多开源项目使用的缺陷跟踪系统页面大多是动态生成的。用户访问一个 bug 详情、搜索一个关键字、查看附件列表都会触发后端查询数据库并渲染 HTML。这种“每页都在干活”的应用天然对请求量非常敏感。日常使用中正常开发者提交 bug、补充注释、上传附件的频率不高服务器压力有限。但 AI 爬虫不会按照人的节奏来。它们会从公开入口开始顺着所有链接不断抓取把show_bug.cgi?id1到id50000扫一遍再把buglist.cgi?quicksearch...的各种查询组合都试一遍。这些请求会持续占用数据库连接、后端进程和带宽最终导致正常用户无法访问。从维护者角度看麻烦的不只是“某个 IP 请求量大”而是流量来源分散、User-Agent 伪装情况多、单次请求看起来很像正常浏览器。如果没有提前做监控和限流问题往往要等到服务不可用或者数据库连接池耗尽时才会暴露。1.2 AI 爬虫和传统搜索引擎爬虫的差异搜索引擎爬虫并不是新鲜事物。但传统爬虫通常有明确的 User-Agent 标识会遵守 robots.txt抓取频率也相对克制。更重要的是传统搜索引擎的目的是收录页面不会为了一个搜索结果无限制地组合 URL。AI 爬虫则不同维度传统搜索引擎爬虫部分 AI 爬虫User-Agent标识稳定容易识别可能标识稳定也可能伪装成浏览器robots.txt多数会遵守部分遵守部分忽略请求频率相对低可能极高甚至并发抓取抓取范围按入口和链接发现页面可能遍历动态参数、构造查询对动态站点的压力中等高因为每个请求都会触发服务端处理这不是说所有 AI 爬虫都是恶意的而是说它们的抓取策略以“拿到足够多语料”为目标并不会考虑源站资源压力。对于 Bugzilla 这类动态查询系统压力会被明显放大。1.3 过载事故中受影响最严重的部分一次 AI 爬虫引发的过载最先出问题的往往不是入口网络而是下面几个环节Web 服务器连接数满大量并发请求占满 Nginx worker。数据库资源紧张每次页面渲染都执行 SQL数据库连接池耗尽。日志和磁盘压力请求量暴增后 access log 写入变快磁盘占用上升。正常用户体验恶化页面响应时间从几百毫秒变成几十秒甚至直接超时。理解这些受影响环节是为了确定防护重点。只加带宽没有用因为压力在后端只封几个 IP 也没有用因为 AI 爬虫可能来自大量 IP。需要从入口到应用层做多层防护。2. 先定位流量如何确认是 AI 爬虫在打 Bugzilla2.1 从访问日志入手不要凭感觉判断流量来源先打开访问日志看真实请求。很多情况下AI 爬虫的 User-Agent 会直接出现在日志里。以 Nginx 默认的 combined 格式为例sudo tail -f /var/log/nginx/bugzilla.access.log日志中每一行大致是192.0.2.10 - - [14/May/2025:10:15:30 0000] GET /show_bug.cgi?id1 HTTP/1.1 200 12345 - GPTBot/1.0 192.0.2.11 - - [14/May/2025:10:15:31 0000] GET /show_bug.cgi?id2 HTTP/1.1 200 12350 - ClaudeBot/1.0看到大量同一类 User-Agent 连续访问不同 bug id基本可以确定是爬虫在批量抓取。2.2 统计 User-Agent 分布手动 tail 只能看几行要判断整体情况需要对历史日志做统计。Nginx combined 格式中最后一个双引号字段是 User-Agent。如果日志格式未做特殊改动可以用 awk 按双引号切分后提取第 6 个字段sudo awk -F {print $6} /var/log/nginx/bugzilla.access.log | sort | uniq -c | sort -rn | head -20输出示例15230 GPTBot/1.0 12110 ClaudeBot/1.0 8800 Bytespider 21 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...如果某些浏览器 User-Agent 的请求数量也异常高还要进一步看请求路径和频率因为有些爬虫会伪装成浏览器。2.3 分析请求行为特征只统计 User-Agent 还不足以覆盖伪装场景。更可靠的方式是结合请求行为判断。以下特征组合起来说明某类流量很可能是 AI 爬虫请求全部是 GET没有登录、提交表单等完整用户行为。请求路径集中在show_bug.cgi、buglist.cgi、attachment.cgi等动态接口。短时间内访问大量不同 ID例如从id1到id100000。单 IP 请求速率远高于人类操作速度。请求之间没有思考间隔不加载图片、CSS 等静态资源。Referer 为空或来自固定入口页面。建议写一个小脚本按 IP 和 User-Agent 维度统计请求量sudo awk -F {print $1, $6} /var/log/nginx/bugzilla.access.log \ | sed s/ - - .*GET/ GET/ \ | sort | uniq -c | sort -rn | head -30日志格式不同字段切分方式需要相应调整。统计时要特别注意不要只看总请求量还要看同一 IP 在相同时间段内对动态 URL 的请求次数。2.4 识别常见的 AI 爬虫标识下面表格汇总了常见 AI 爬虫的 User-Agent 关键字。不同爬虫的标识可能随版本变化当前信息以各家官方公开文档为准关键字示例通常关联的爬虫GPTBotOpenAI 抓取工具ChatGPT-UserOpenAI 交互类爬虫ClaudeBotAnthropic 抓取工具Claude-UserAnthropic 相关客户端Google-ExtendedGoogle 的 AI 训练数据抓取声明Bytespider字节跳动系爬虫CCBotCommon Crawl 爬虫PerplexityBotPerplexity 爬虫AmazonbotAmazon 爬虫GrokBotxAI 抓取工具需要注意的是User-Agent 黑名单只是基线不能作为唯一防线。部分爬虫会伪造浏览器标识也有新爬虫不断出现。识别阶段的目标是“找出大多数已知流量”而不是做到 100%。3. 分层次拦截与限流从入口到应用层的保护方案3.1 在 Nginx 入口拦截常见 AI 爬虫最直接有效的拦截位置是 Nginx。在http块中定义一段map把匹配到的 User-Agent 映射为拒绝标记map $http_user_agent $ai_scraper { default 0; ~*GPTBot 1; ~*ClaudeBot 1; ~*GrokBot 1; ~*Bytespider 1; ~*CCBot 1; ~*PerplexityBot 1; ~*Amazonbot 1; ~*Google-Extended 1; }然后在 server 块中处理server { listen 80; server_name bugs.example.org; if ($ai_scraper) { return 403; } location / { proxy_pass http://127.0.0.1:8080; include proxy_params; } }这里使用了正则匹配~*表示不区分大小写。所有 User-Agent 中包含GPTBot、ClaudeBot等关键字的请求都会在进入后端之前直接返回 403。注意Nginx 的if指令放在location中可能产生意外行为尤其是使用proxy_pass时。如果需要按 User-Agent 拒绝请求尽量把if放在 server 上下文只做return 403不要在里面写复杂逻辑。3.2 用 robots.txt 声明抓取规则robots.txt 不是安全机制它只对“愿意遵守协议”的爬虫有效。但它是成本最低的合规手段也能避免误伤愿意协商的 AI 爬虫。在站点根目录放置 robots.txtUser-agent: * Allow: / User-agent: GPTBot Disallow: / User-agent: ClaudeBot Disallow: / User-agent: GrokBot Disallow: / User-agent: Bytespider Disallow: / User-agent: CCBot Disallow: / User-agent: PerplexityBot Disallow: / User-agent: Amazonbot Disallow: /在这个配置中普通搜索引擎仍然可以抓取常见 AI 爬虫被明确禁止。很多知名爬虫会定期读取 robots.txt因此即使 Nginx 拦截已经生效也建议保留这份声明。注意robots.txt 不能替代访问控制。如果一个爬虫不遵守该协议同时伪装成浏览器那它仍会继续请求。不要因为加了 robots.txt 就降低其他防护。3.3 用 fail2ban 做动态封禁已知 User-Agent 黑名单无法应对伪装场景。当一段时间内某个 IP 频繁访问动态 URL 时更适合用 fail2ban 按照 IP 维度做动态封禁。创建过滤器/etc/fail2ban/filter.d/nginx-ai-scraper.conf[Definition] failregex ^HOST .*(?:GET|POST|HEAD) .* (?:403|404|429) .*(?:GPTBot|ClaudeBot|GrokBot|Bytespider|CCBot|PerplexityBot|Amazonbot) ignoreregex 然后创建 jail 配置/etc/fail2ban/jail.d/bugzilla-ai.conf[nginx-ai-scraper] enabled true filter nginx-ai-scraper logpath /var/log/nginx/bugzilla.access.log maxretry 5 findtime 60 bantime 3600参数含义参数含义推荐值maxretry在 findtime 内触发多少次后封禁5findtime统计窗口单位秒60bantime封禁时长单位秒3600 或更长logpath需要检测的日志文件按实际路径填写配置好之后启动并查看状态sudo systemctl restart fail2ban sudo fail2ban-client status sudo fail2ban-client status nginx-ai-scraper如果看到 banned IP 列表说明该 IP 已经触发阈值。fail2ban 底层通过防火墙规则丢弃来自这些 IP 的包因此请求不会到达 Nginx能显著降低后端压力。3.4 对 Bugzilla 应用层做基础限流入口拦截解决的是已知爬虫fail2ban 解决的是明显异常的单个 IP。但 AI 爬虫可能使用大量不同 IP单看某个 IP 可能并不超标总体请求量却仍然很高。这时候需要加一层“每 IP 速率限制”。在 Nginx 中可以使用limit_req_zone和limit_req实现limit_req_zone $binary_remote_addr zonebugzilla_req:10m rate30r/m; server { listen 80; server_name bugs.example.org; location / { limit_req zonebugzilla_req burst20 nodelay; limit_req_status 429; proxy_pass http://127.0.0.1:8080; include proxy_params; } }这里有两个关键参数rate30r/m每个 IP 平均每分钟最多 30 个请求。burst20允许瞬间超过平均速率 20 个请求超过后进入排队。nodelay突发请求不延迟处理但超过 burst 的部分直接返回 429。对于 Bugzilla 这类系统正常开发者每分钟不会发起超过 30 个页面请求。如果读者的社区规模较小还可以把速率调低到10r/m。使用限流时要留意一个常见问题企业内部 NAT 出口可能让很多用户共享同一个公网 IP。如果这个出口的请求量超过了速率上限会导致正常用户被 429。此时可以在测试环境先观察再结合geo或map对可信网段放行。3.5 引入更严格的边缘防护如果站点使用了云厂商的 CDN、防火墙或负载均衡可以在边缘配置托管质询、WAF 规则或速率限制。这类方案通常能提供更细粒度的人工验证比如浏览器自动通过 JavaScript 质询而爬虫无法执行完整的浏览器环境。具体配置因厂商而异不在这里展开。使用原则是边缘优先拦截大流量攻击源站负责兜底边缘限流规则要和 Nginx 的限流规则保持协同避免边缘放行后源站仍然过载。4. 验证防护效果日志、指标与用户影响评估4.1 确认拦截请求命中配置完成后先用模拟请求验证 Nginx 是否按预期工作# 模拟已知 AI 爬虫 curl -I -A GPTBot/1.0 https://bugs.example.org/ # 模拟普通浏览器 curl -I -A Mozilla/5.0 (Windows NT 10.0; Win64; x64) https://bugs.example.org/预期结果使用 GPTBot User-Agent 的请求返回 403。使用普通浏览器 UA 的请求返回 200 或 302取决于 Bugzilla 是否需要登录。如果要看状态码可以简化输出curl -o /dev/null -s -w %{http_code}\n -A GPTBot/1.0 https://bugs.example.org/输出403表示拦截生效。4.2 对比请求量和资源占用防护效果不能只看“403 有没有出现”还要看整体流量是否下降。比较规则上线前后的访问日志sudo awk -F {print $6} /var/log/nginx/bugzilla.access.log | sort | uniq -c | sort -rn | head -20如果 GPTBot 等已知爬虫的请求量大幅下降说明入口拦截有效。如果仍然很高需要检查日志里记录的是拦截后返回 403 的请求还是进入后端的请求。返回 403 的请求也写 access log但后端压力已经消失所以不要看到日志里还有爬虫名字就认为拦截无效。同时关注系统指标top free -h df -h重点观察数据库连接数、PHP-FPM 或后端进程数、CPU 使用率和磁盘占用。正常状态下这些指标应当回落到爬虫爆发之前的水位。4.3 检查正常用户是否受影响防护规则上线后最怕误伤正常用户。观察状态码分布是一个快速方法。在 Nginx 默认日志格式中第 9 个字段是状态码sudo awk {print $9} /var/log/nginx/bugzilla.access.log | sort | uniq -c | sort -rn | head -20如果 403 或 429 占比过高可能说明规则过严。 403 不一定都是误伤但要确认被 403 的请求里有没有大量正常浏览器 UA。正常情况下200、301、302、404 占绝大多数403/429 应该是少数。4.4 建立简单告警防住一次不等于永远安全。可以写一个简单脚本每天统计日志中 AI 爬虫请求量并设置阈值告警#!/bin/bash LOG/var/log/nginx/bugzilla.access.log THRESHOLD1000 COUNT$(grep -cE GPTBot|ClaudeBot|GrokBot|Bytespider|CCBot|PerplexityBot|Amazonbot $LOG) if [ $COUNT -gt $THRESHOLD ]; then echo AI scraper request count in last log period: $COUNT | mail -s AI scraper alert adminexample.com fi这个脚本只是演示。实际生产环境建议让日志进入集中采集系统再结合 Prometheus、Loki 或 Elasticsearch 做自动告警。告警阈值不是越高越好要根据站点正常请求量确定否则要么天天误报要么真出事时没有通知。5. 常见坑与排查链路5.1 只做 User-Agent 黑名单爬虫换 UA 后就失效现象最开始封了一批 GPTBot 请求流量下降明显几天后流量又回到高位。查询日志发现大量“Mozilla/5.0”请求。原因有些爬虫会伪装成浏览器或定期切换 UA。处理方式把 User-Agent 黑名单当作“基础过滤”同时启用 fail2ban 和 Nginx 速率限制。封禁的维度从“UA 关键词”迁移到“IP 行为”。如果请求来自大量 IP则在应用层增加验证码或托管质询。5.2 正则写得太宽误杀正常用户现象某些安装软件、SDK 或普通浏览器的请求被 403。原因正则匹配了bot这个通用词比如把SomeBot也当作 AI 爬虫或者网站本身存在名称包含 bot 的模块。处理方式使用完整的已知爬虫关键词不要写~*bot这种过宽规则。在 map 中的正则尽量用具体名称上线前用一组正常 UA 做回归测试。5.3 日志字段切分错误统计结果不准现象统计 User-Agent 时输出为空或者把 IP 当成了 UA。原因Nginx 的log_format不是默认格式字段顺序发生了变化。比如把 Referer 和 User-Agent 的位置换了或加了额外字段。处理方式先看 Nginx 配置中log_format的定义。如果不确定字段位置可以临时加一个专门的日志格式只输出$remote_addr、$request、$status、$http_user_agentlog_format ai_protect $remote_addr $request $status $http_user_agent;然后在需要分析的 server 中单独配置 access_log统计时按这个格式切分即可。5.4 fail2ban 重启后封禁消失现象重启服务器后之前封禁的爬虫 IP 又能访问了。原因fail2ban 的封禁规则保存在内存中重启后需要重新读取日志并积累触发次数如果系统没有配置防火墙规则持久化也可能丢失。处理方式确认 fail2ban 服务已设置开机自启并检查iptables或nftables规则是否持久化。与安全相关的封禁本身有时间属性短暂失效后如果爬虫继续产生异常日志fail2ban 会再次封禁。5.5 排查顺序推荐遇到 Web 服务被爬虫打挂不要先急着加规则按以下顺序排查确认服务当前状态CPU、内存、数据库连接、磁盘是否异常。从 access log 看请求量最高的 UA、IP、URL。从 error log 看是否有连接超时、后端错误。确认日志记录时间与服务器时区避免统计窗口错乱。决定优先拦截点已知 UA 用 Nginx 拦截单 IP 异常用 fail2ban整体请求量大用限流。上线规则后再次统计同一指标验证是否恢复正常。6. 生产环境的长期方案6.1 基础设施层不要把压力都留给源站Nginx 的 UA 拦截和限流能解决很多问题但面对大规模分散爬虫流量时源站仍然可能被高并发打满。更稳妥的做法是引入边缘缓存和边缘防护。静态资源可以通过 CDN 缓存减少源站请求。动态页面虽然不能全部缓存但可以在边缘做质询和速率限制。这样即使爬虫来自成千上万个 IP源站也只处理真正通过边缘校验的请求。架构调整可以分三步走第一步在 Nginx 层完成 UA 黑名单和 IP 限流。第二步把 fail2ban、访问日志监控纳入日常运维。第三步根据流量变化评估是否需要边缘防护和托管质询。6.2 应用层保护动态接口和数据库对 Bugzilla 这类动态应用建议关注查询接口的资源消耗。常用做法包括给热点用户页面加缓存减少重复渲染。限制历史 bug 数据的深度遍历比如对附件、全文检索接口做单独限流。对需要登录才能访问的接口强制会话校验。对公开搜索接口增加分页大小限制避免单次请求查询范围过大。在数据库层设置连接池上限和慢查询阈值防止单类查询拖垮整个实例。这些优化不直接针对 AI 爬虫但能显著提高系统的抗压能力。出现过载时后端越“轻”防护规则越容易生效。6.3 治理层面维护一个 AI 爬虫清单AI 爬虫列表会不断变化维护者可以建立一份内部清单记录启动时间、User-Agent、访问范围、是否遵守 robots.txt 等信息。这不仅能帮助快速封禁也能在未来评估某个爬虫是否符合站点政策。清单建议包含以下字段name: GPTBot user_agent: GPTBot official_docs: https://openai.com/gptbot respects_robots: true/unknown/false observed_at: 2025-05-14 notes: 曾批量抓取 show_bug.cgi用 YAML、JSON 或表格维护都可以。关键是让团队在遇到“新 UA 请求量高”时有一个快速查询和沉淀答案的地方。6.4 开源项目维护者还可以做什么开源基础设施的维护者通常没有专职安全团队能做的更多是提前准备开启 Web 访问日志的轮转防止磁盘被日志打满。定期复盘访问日志中的异常流量。在官方站点放置明确的抓取政策。与大型 AI 公司约定抓取配额很多厂商提供 robots.txt 或单独的抓取协议入口。重要数据提供离线打包下载降低按页面抓取的频率。开源项目的特点决定了站点很难完全封闭。与其被动等下一次爬虫把站点打挂不如把可观测性做好把拦截规则提前放在线上。到这里这套从日志分析到 Nginx 拦截再到 fail2ban 动态封禁、应用层限流和效果验证的方法就可以在真实基础设施上落地了。核心判断是AI 爬虫流量不会消失只会越来越多只靠某一种手段无法长期有效必须形成“识别、拦截、限流、监控、复盘”的循环。对于正在维护 Bugzilla 或其他动态站点的开发者建议先从修改 Nginx 配置和写一个 UA 统计命令开始半天内就能建立起基本防线。