网站在实施防刷限流和WAF防护后,由于仅凭User-Agent放行导致假蜘蛛泛滥、rDNS双向反查失效和Nginx limit_req误杀爬虫,搜索引擎蜘蛛抓取频繁出现403和429错误。作者详细梳理了几个典型踩坑点和最终落地配置方案,强调了仅依靠User-Agent放行爬虫和Nginx limit_req白名单映射中的空变量机制陷阱。
几个月前一台跑着内容站的云服务器,连续几天半夜遭遇突发流量和脚本扫库,CPU占用率直接被推到90%以上。为了防止数据库连接池被打满导致整站宕机,我连夜在Nginx网关层和WAF加上了防刷限流规则,把单IP每秒请求数卡得很严,顺带把没有白名单标识的异常频次请求直接拒绝。
当时盯着实时访问日志看了半个小时,恶意刷量确实被成功压了下去。但一周之后登录搜索资源平台和站长后台,抓取监控面板的数据让人吓出一身冷汗:抓取频次从每天几万次掉到了两位数,抓取异常率曲线一路冲顶,排查诊断日志里充斥着密密麻麻的 403 Forbidden 和 429 Too Many Requests。与此相伴的,是核心栏目的快照更新彻底停滞,部分刚发的新内容一周过去连抓取日志都见不到一条。
顺着网关限流、WAF策略以及反向代理的配置链路逐层排查,我才发现给高频抓取的搜索引擎蜘蛛配置防护规则,远比给普通业务接口限流复杂得多。这里把几个典型踩坑点和最终落地的配置方案详细梳理出来。
拿 User-Agent 字符串做爬虫豁免,防了君子却被小人打穿
在给网站上防刷规则时,很多人最直观的想法就是拿 User-Agent 做白名单放行。我最开始也是这么干的,在 Nginx 里写了一段正则匹配:
# 表面看起来合理的爬虫白名单规则
if ($http_user_agent ~* "(Baiduspider|Googlebot|bingbot|Sogou web spider)") {
set $is_crawler 1;
}
紧接着把这批请求绕过限流和人机验证。但跑了不到三天就出了两个方向的反向事故。
第一个事故是恶意流量伪造 UA 畅通无阻。那些刷接口和扫后台的爬虫脚本只要在请求头里带上 User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html),就能直接穿透全部限流逻辑,把原本用来保护服务器的规则当成了虚设。后台监控显示,有一批海外机房的 IP 顶着百度蜘蛛的 UA,在半小时内发起了数万次高负载搜索请求。
第二个事故是真正的官方爬虫被误伤。各家搜索引擎在不同抓取场景下使用的 UA 变体很多,比如百度有移动渲染、小程序爬虫、图片爬虫,谷歌也有智能手机渲染与桌面抓取区分。正则一旦匹配不全面,或者把带有特定标识的 UA 漏掉,这些请求就会直接撞上限流拦截或者人机验证,直接拿到 403 页面。
只靠 User-Agent 做身份判定不仅防不住伪造攻击,还会因为误判直接把官方蜘蛛挡在门外。
Nginx limit_req 白名单映射的空变量机制陷阱
确定不能仅靠单一 UA 放行之后,我开始梳理 Nginx 原生的限流机制。很多人在用 ngx_http_limit_req_module 模块做限流白名单时,最容易犯的一个错误就是没理解限流 key 的取值逻辑。
Nginx 的 limit_req_zone 官方文档里有一条核心规则:如果限流 key 的最终评估值为空字符串(empty string ""),Nginx 会自动忽略该请求,既不会对其限流,也不会将其记入共享内存区。
利用这个特性,正确的做法是通过 map 指令将白名单内的 IP 或者判定为真实爬虫的请求映射为空字符串,非白名单请求则映射为 $binary_remote_addr。
但我最初在写映射规则时,踩了一个极其隐蔽的坑:
# 错误示范:白名单赋予固定字符串
map $is_trusted_spider $spider_limit_key {
1 "trusted_bot";
0 $binary_remote_addr;
}
limit_req_zone $spider_limit_key zone=req_limit_zone:20m rate=10r/s;
这段配置的问题在于,一旦某个请求被标记为信任爬虫,它的 $spider_limit_key 被赋予了固定的字符串 "trusted_bot"。这意味着全网所有爬虫发来的请求,在 Nginx 眼里全部共享同一个限流 bucket!
当百度和谷歌的数十个抓取节点并发拉取网站的不同页面时,瞬间就把这每秒 10 次的全局配额打穿。除了前两三个连接正常返回 200,后续几百个并发请求全被判定为超频,清一色返回 429 Too Many Requests。
修改后的正确结构必须确保白名单命中后变量值绝对为空:
# 正确姿势:白名单留空,普通流量按真实IP计数
map $is_trusted_spider $spider_limit_key {
1 "";
0 $binary_remote_addr;
}
limit_req_zone $spider_limit_key zone=req_limit_zone:20m rate=15r/s;
同时,针对不能完全确定是合法蜘蛛的未知请求,如果要单独限制并发,不要与普通用户公用同一个 zone,必须拆分不同的内存区并给出合理的 burst 缓冲值。搜索引擎抓取具有瞬时突发特性,抓完一个列表页紧接着会并发抓取内页,如果把 burst 设得太小或者直接加 nodelay 严格阻断,哪怕均值没超标,突发请求也会被直接抛弃。
rDNS 双向验证的深度坑:别在请求主链路上做实时解析
既然 UA 靠不住,搜索引擎官方给出的推荐鉴别方案是 rDNS 反向 DNS 验证。具体流程分两步:
- 提取请求来源 IP,做 PTR 反查,验证其域名后缀是否属于官方域名(例如
.baidu.com、.googlebot.com、.google.com); - 拿到反查出来的域名,立刻做正向 A 记录解析,检查解析出来的 IP 是否与当前请求的源 IP 完全一致。
只有这两步都校验通过,才能确认为真正的官方蜘蛛。
在实施 rDNS 验证时,我见识到了最惨痛的性能灾难:直接在应用层或者 Nginx 动态脚本里,对每个爬虫请求现场做实时反查。
# 模拟一次外网 PTR 与 A 记录查询
host 66.249.66.1
host crawl-66-249-66-1.googlebot.com
一次完整的跨国 DNS 往返反查,在网络抖动时耗时轻松突破 500 毫秒甚至超过 1 秒。如果在请求主流程里同步执行这套查询,搜索引擎蜘蛛刚爬几个页面,后端的工作进程就被密密麻麻卡在 DNS 等待上。不仅新请求进不来,爬虫本身也因为响应超时(Read Timeout)放弃抓取,搜索引擎站长平台立刻亮起抓取超时红灯。
此外,如果只做了 PTR 反查而偷懒不做第二步的正向 A 记录验证,攻击者只需要在自己的 VPS 上配置一条 PTR 指针指向 crawl-xxx.googlebot.com,你的防御系统就会彻底受骗,把恶意攻击者当成谷歌官方爬虫长驱直入。
在生产环境中,可行的处理方式只有两种:
第一种是维护离线 IP 池。各大搜索引擎(Google、Bing、Baidu)都公开了官方爬虫的 IP 范围列表或者通过 BGP/ASN 发布。写一个定时任务每天跑一次,抓取官方公布的 JSON/文本网段,自动生成 Nginx 的 geo 配置文件并平滑重载:
# 定时生成的爬虫网段白名单文件 spiders_geo.conf
geo $is_official_spider_ip {
default 0;
66.249.64.0/19 1; # Googlebot
220.181.108.0/24 1; # Baiduspider 示例段
157.55.39.0/24 1; # Bingbot
}
第二种是异步缓存。如果使用 OpenResty 或者自研网关,可以把通过双向 rDNS 校验的结果写入 Redis 或本地共享内存字典(lua_shared_dict),设置 7 天到 30 天的有效期。未命中缓存的新 IP 先按正常规则排队限流,后台异步拉起 worker 跑双向校验,校验通过后再加入白名单。千万不要让主请求线程等待 DNS 解析。
CDN 回源与反向代理层:限流错打了 CDN 节点 IP
网站在主服务器前面加了 CDN 节点或高防清洗服务后,架构往往变成了:客户端 -> CDN边缘节点 -> 源站Nginx。
刚切到 CDN 的头两天,日志里的 429 数量翻了倍。排查后发现,Nginx 限流配置里的 $binary_remote_addr 拿到的根本不是爬虫的真实 IP,而是 CDN 回源节点的 IP。
CDN 回源的并发量非常集中,来自几十条线路的蜘蛛请求可能由同一个回源节点汇总发送到源站。如果源站 Nginx 没有配置 Real-IP 还原模块,几只爬虫一并发,直接把该回源节点对应的限流额度耗尽。结果不仅爬虫被打回 429,经过该 CDN 节点回源的普通访客也被一并关在门外。
源站 Nginx 必须显式配置 http_realip_module,把来自 CDN 机房的 IP 加入信任列表:
# 信任 CDN 边缘节点的回源网段
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 104.16.0.0/13;
# ...加入你的实际 CDN 回源网段
# 指定从哪个请求头获取真实来源 IP
real_ip_header X-Forwarded-For;
real_ip_recursive on;
只有当 Real-IP 模块正确生效后,$binary_remote_addr 才会真正代表爬虫或访客的原始 IP,限流规则才不会误杀整座 CDN 节点。
429 响应头缺失 Retry-After,导致搜索引擎惩罚性降频
很多运维人员在配置 Nginx 限流时,认为只要返回 429 或者 503 就足够了。但对于搜索引擎蜘蛛而言,没有标准的返回头会带来灾难性的抓取降权。
Google 搜索团队和各大技术规范中多次明确:当服务器向爬虫返回 429 Too Many Requests 或 503 Service Unavailable 时,应当在 HTTP 响应头中携带 Retry-After,明确告知爬虫多少秒之后可以重试。
如果服务直接丢出一个光秃秃的 429 页面,甚至为了省事直接用 403 Forbidden 粗暴切断连接,搜索引擎爬虫会根据抓取算法启动退避惩罚。如果是 403,爬虫会判定该页面已被站长移除访问权限,过不了几天就会直接把该 URL 从搜索索引库中剔除;如果是无头的 429,爬虫会认定服务器承载能力严重不足,系统性大幅降低全站的抓取频次配额。
在 Nginx 中,应当规范配置 429 的错误处理:
# 定义 429 错误状态码处理
error_page 429 = @spider_rate_limited;
location @spider_rate_limited {
add_header Retry-After 120 always;
add_header Content-Type "text/plain; charset=utf-8" always;
return 429 "Too Many Requests. Please reduce concurrency and retry later.\n";
}
告诉蜘蛛服务器当前处理能力处于峰值,请在 120 秒后平缓重试,这样抓取系统会自动保持低频尝试,而不会将其误判为死链或永久封禁。
生产落地的全流程 Nginx 配置方案
把前面的几个坑点串联起来,一套兼顾防恶意扫库与保障合规搜索引擎平稳抓取的 Nginx 配置结构如下:
# 1. 真实 IP 还原(如果使用了 CDN 或反代网关)
set_real_ip_from 192.168.1.0/24; # 替换为真实的 CDN/LB 回源 IP 网段
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# 2. 爬虫网段与 UA 组合判定
# 预先生成的官方蜘蛛 IP 池
include /etc/nginx/conf.d/spider_ip_ranges.conf;
# 组合校验:必须是官方网段且符合基础爬虫特征
map "$is_official_spider_ip:$http_user_agent" $is_verified_spider {
default 0;
~^1:.*(Baiduspider|Googlebot|bingbot).* 1;
}
# 3. 提取限流 Key:白名单爬虫赋予空值,其余按真实 IP 限流
map $is_verified_spider $req_limit_key {
1 "";
0 $binary_remote_addr;
}
# 4. 配置限流区与突发缓冲
limit_req_zone $req_limit_key zone=general_zone:20m rate=15r/s;
limit_req_status 429;
server {
listen 443 ssl http2;
server_name www.llbbs.cn;
# 针对 429 添加合规的 Retry-After 响应头
error_page 429 = @rate_limited_handler;
location @rate_limited_handler {
add_header Retry-After 60 always;
add_header Content-Type "text/plain; charset=utf-8" always;
return 429 "Server busy, please retry in 60 seconds.\n";
}
location / {
# 允许一定程度的突发请求,避免瞬间被打爆
limit_req zone=general_zone burst=30 nodelay;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
limit_req zone=general_zone burst=20 nodelay;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
上线后的验证与监控排查
配置上线之后,不要只看页面能不能打开,必须主动模拟爬虫并发测试,并监控日志中的状态码分布。
1. 模拟蜘蛛并发测试
用 curl 伪造爬虫 UA 发送连续突发请求,验证白名单机制是否按预期生效:
# 测试非白名单 IP 频繁请求,验证是否正常触发 429 和 Retry-After 头
for i in {1..25}; do curl -I -s "https://www.llbbs.cn/" | grep -E "HTTP|Retry-After"; done
# 在已经加入白名单的测试机上,使用真实蜘蛛 UA 发起测试,确认全链路返回 200
curl -I -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "https://www.llbbs.cn/"
2. 分析 Access 日志中的爬虫状态分布
在日常运维中,通过简单的 shell 命令每天检查一次主流搜索引擎的抓取状态分布:
# 统计过去 24 小时内 Baiduspider 的 HTTP 响应状态码分布
grep "Baiduspider" /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -nr
# 统计过去 24 小时内 Googlebot 的 HTTP 响应状态码分布
grep "Googlebot" /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -nr
健康的日志结构中,200 和 304 应当占据 98% 以上的比例,403 和 429 应当基本为 0。如果发现 429 出现频率上升,说明 burst 缓冲池容量偏小,或者官方蜘蛛有了新的出口网段未被及时收录进白名单,需要根据实际日志反查来源并更新配置。
确保搜索引擎抓取通道畅通,同时把恶意扫库与伪造爬虫精准隔绝在防护网外,网站的收录配额和关键词排名才能在安全防护下稳定维系。
文章标题:网站加了防刷限流与WAF防护,搜索引擎蜘蛛抓取却大面积报403与429?排查仅凭UA放行导致假蜘蛛泛滥、rDNS双向反查失效与Nginx limit_req误杀爬虫,我踩过的几个深坑
文章链接:https://www.llbbs.cn/jishujaocheng/269.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?