侧边栏壁纸
  • 累计撰写 191 篇文章
  • 累计收到 2 条评论

网站套了全站 CDN 加速搜索收录却断崖暴跌?排查边缘节点回源超时、If-Modified-Since 被吞与源站防刷误封我踩过的几个坑

2026-10-12 / 0 评论 / 5 阅读
AI 摘要由 AI 生成

接入全站CDN后,搜索引擎抓取频率和异常率暴跌,原因在于边缘节点回源超时、If-Modified-Since被吞及源站防刷误封。通过启用realip模块,加入信任列表并调整限流策略,可解决爬虫请求被丢弃问题。同时,CDN边缘节点抹除If-Modified-Since也需注意,以保障爬虫配额。

上个月给站点接入全站 CDN 加速,本意是为了扛住偶尔的高峰流量,把各地访客的白屏时间压到 300 毫秒以内。部署完后用各地测速工具测了一圈,全国节点一片飘绿,静态资源基本全命中边缘缓存,自测体验流畅。

谁知道跑了两个星期,去翻百度搜索资源平台和 Google Search Console 的数据,抓取频次从之前每天四万多次骤降到不足两千次,抓取异常率飙升到了 35% 以上,索引库里的新页面几乎停滞收录。

翻看抓取报错清单,全是 504 Gateway Timeout、502 Bad Gateway 以及大量的重复抓取全量下载。明明浏览器点开飞快,为什么搜索引擎爬虫抓取却千疮百孔?排查了几天网络抓包、CDN 节点配置和源站 Nginx 日志,我理清了四个隐蔽的翻车点。

源站限流防刷误杀 CDN 回源节点,爬虫被批量丢弃

这是最容易发生、隐蔽性最高的坑。

在没有接 CDN 之前,源站 Nginx 的防御配置通常长这样:

limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

server {
    listen 443 ssl http2;
    server_name www.llbbs.cn;

    location / {
        limit_req zone=one burst=20 nodelay;
        proxy_pass http://127.0.0.1:8080;
    }
}

这套配置对直连访客没问题。单 IP 一秒请求超过 10 次就触发限流,外加系统层的 fail2ban 监控 access.log,一分钟出现 50 次 503 就直接拉进 iptables 封禁 1 小时。

但是一旦接入全站 CDN,源站面对的客户端 IP 不再是分散在各地的真实爬虫,而是 CDN 厂商部署在各省的有限几个回源出口服务器。

当百度或谷歌爬虫发动突发式抓取(比如集中扫描几十篇新文章或更新内链)时,数十个甚至上百个爬虫请求在毫秒级内全部通过同一个 CDN 回源节点发向源站。

源站 Nginx 拿到 $binary_remote_addr,发现这个 IP 瞬间冲上每秒 80 次请求,立即返回 503 Service Unavailable 或者连接直接被丢弃。fail2ban 更是火上浇油,直接调用 iptables 把该 CDN 回源节点封禁。CDN 节点连不上源站,随即给搜索引擎爬虫吐出 504 Gateway Timeout。

排查时登录源站服务器查看日志:

# 查看被 iptables 拦截的记录
iptables -L -n -v | grep DROP

# 查看 fail2ban 封禁名单
fail2ban-client status nginx-limit-req

看到被封禁的几个 IP 归属地全是大厂 CDN 回源段,真相就清楚了。

解决办法有两个步骤。首先在源站 Nginx 启用 realip 模块,把 CDN 节点 IP 加入信任列表,将真实客户端 IP 从头部解析出来:

# 填入 CDN 厂商公布的完整回源 IP 段
set_real_ip_from 111.13.0.0/16;
set_real_ip_from 183.60.0.0/16;

# 指定获取客户端真实 IP 的请求头
real_ip_header X-Forwarded-For;
real_ip_recursive on;

接着重写限流策略,限流依据使用 $realip_remote_addr,并且对合法的搜索引擎蜘蛛通过 User-Agent 和反查机制做放行,避免误封回源通道:

map $http_user_agent $is_crawler {
    default 0;
    "~*Baiduspider" 1;
    "~*Googlebot" 1;
    "~*bingbot" 1;
}

# 爬虫请求设为空值,Nginx 不会对空 key 计算限流
map $is_crawler $limit_key {
    0 $realip_remote_addr;
    1 "";
}

limit_req_zone $limit_key zone=crawler_safe:10m rate=10r/s;

这样既保护了源站,又确保 CDN 集中回源时不被源站防火墙当成攻击拦截。

CDN 边缘节点抹除 If-Modified-Since,拖垮爬虫配额

正常情况下,搜索引擎蜘蛛抓取过某个页面后,第二次再来巡检时,请求头里会带上上一次获得的协商缓存标记:

GET /article/123.html HTTP/1.1
Host: www.llbbs.cn
User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)
If-Modified-Since: Tue, 15 Sep 2026 08:30:00 GMT
If-None-Match: "66e69318-4a12"

源站如果检测到内容没有修改,就会立即返回 304 Not Modified,不返回任何正文数据。这几十个字节的响应能让爬虫瞬间确认网页状态,把有限的抓取配额(Crawl Budget)留给站点新发布的其他文章。

但我排查 CDN 访问日志时,发现爬虫发来的几万次重复抓取,CDN 节点全部回了 200 OK,而且每次都老老实实吐出了上百 KB 的完整 HTML。

用 curl 模拟带头的请求测试边缘节点:

curl -I -H "If-Modified-Since: Tue, 15 Sep 2026 08:30:00 GMT" \
     -H "User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0)" \
     https://www.llbbs.cn/article/123.html

返回的头部赫然显示:

HTTP/2 200
date: Mon, 12 Oct 2026 07:15:22 GMT
content-type: text/html; charset=UTF-8
last-modified: Tue, 15 Sep 2026 08:30:00 GMT
x-cache-lookup: Hit From MemCache

明明传了 If-Modified-Since,时间戳也完全对得上,CDN 节点却依然直接返回 200 并塞出完整内容。

这是因为部分 CDN 厂商的默认边缘策略把客户端送上来的条件请求头直接给过滤掉了,边缘节点只管比对 URL 缓存是否存在,只要在缓存有效期内就一律吐出 200。

爬虫每天来站里巡视,抓取预算全花在重读那些一模一样的老文章上面,消耗了几十 GB 带宽,而新发表的几十篇文章根本排不上抓取队列。

需要在 CDN 控制台的高级缓存配置里开启两项开关:

  1. 开启“保留并透传客户端条件请求头(If-Modified-Since / If-None-Match)”。
  2. 开启“边缘支持 304 响应协商”。

同时检查源站 Nginx,确保输出静态 HTML 或反向代理时,没有被 gzip 模块意外抹掉 ETag 弱验证字段:

# 保持静态协商头完整输出
etag on;
if_modified_since exact;

配置生效后再次测试,带有旧时间戳的请求就能在 CDN 边缘节点直接获得 304 响应,爬虫配额迅速被解放出来。

偶然故障的 502/504 错误页被 CDN 缓存,导致批量降权

这是另一个极其致命的隐患。

源站难免遇到重启服务、发布代码或者数据库瞬时打满的情况,有时只会出现短短 3 到 5 秒的 502 Bad Gateway。

但在很多 CDN 系统的默认模板里,“异常状态码缓存”是默认启用的。规则往往写成这样:

  • 404 状态码缓存 10 秒
  • 500、502、504 状态码缓存 300 秒(5 分钟)

这意味着,只要蜘蛛在源站抖动的这 3 秒钟内发起了回源请求,碰到了一个 502,这个 502 错误页面就会被固化在 CDN 该边缘节点上整整 5 分钟。

在这 5 分钟里,即使源站早就恢复正常,蜘蛛在这片区域的其他抓取节点发来探测,拿到的全是边缘节点缓存的 502。百度爬虫对同一 URL 在短时间内连续遭遇 5xx 错误极其敏感,连续两次失败就会迅速降低重试频率,甚至直接将该 URL 移出索引候选库。

在 CDN 控制台的“状态码缓存”设置里,我把 500、502、503、504 的缓存时间全部手动改成了 0 秒(或者直接删除该规则)。对于 404 状态码,如果文章存在误删或改名情况,缓存时间保留 3 秒即可,避免搜索引擎抓取时被虚假错误缠住。

另外在源站 Nginx 针对错误页面补上无缓存标识:

error_page 500 502 503 504 /50x.html;
location = /50x.html {
    root /usr/share/nginx/html;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    add_header Pragma "no-cache";
}

这样即便偶尔触发服务异常,CDN 也绝对不会把故障页面存下来分发给爬虫。

边缘缓存键忽略 URL 查询参数,导致分页与列表窜页

为了提高静态资源的缓存命中率,很多站长习惯在 CDN 开启“忽略 URL 参数(Ignore Query String)”功能。

对于图片和 JS 文件,比如 avatar.jpg?v=1.2 和 avatar.jpg?v=1.3,忽略参数共享一份缓存确实省流量。但如果把这项规则全局套用到了全站或者 HTML 页面上,灾难就来了。

很多博客和内容站的分页是基于查询参数的:

  • 第一页:https://www.llbbs.cn/category/dev/
  • 第二页:https://www.llbbs.cn/category/dev/?page=2
  • 第三页:https://www.llbbs.cn/category/dev/?page=3

一旦 CDN 忽略了查询参数,Cache Key 只取路径部分,当有人访问了第 1 页后,第 2 页和第 3 页在边缘节点上的缓存键完全一样。

搜索引擎蜘蛛顺着分页链接抓到 ?page=2 时,CDN 返回的内容却和首页一模一样。蜘蛛抓到 ?page=3,内容还是首页。

没过几天,搜索引擎就会判定你的列表页全是内容一字不差的重复镜像,直接把深层分页页面全部过滤出索引,底下的文章内链也跟着断流。

测试缓存键非常简单,用两个带不同参数的命令连续请求:

curl -s "https://www.llbbs.cn/category/dev/?test=param1" | md5sum
curl -s "https://www.llbbs.cn/category/dev/?test=param2" | md5sum

如果返回内容的哈希值一模一样,而且页面里渲染的参数标记毫无变化,说明参数已经被 CDN 丢弃。

正确的做法是按目录拆分缓存规则:

  1. 对 /static/*、*.css、*.js、*.jpg 开启“忽略参数缓存”。
  2. 对根目录 /、HTML 后缀页面、文章页和分类目录保留“完整 URL(含参数)计算缓存键”。

对于内容站,更稳妥的做法是把分页路由彻底改写为伪静态路径,比如 /category/dev/page/2.html,让每个资源在语义上拥有独立的静态路径,彻底避开参数丢弃的暗坑。

节点变更后的抓取健康度验证

把上述几项配置修正完成后,不能干等站长平台的统计延迟。我用编写好的小脚本,通过指定的 CDN 边缘节点 IP,模拟百度蜘蛛的抓取行为:

# 模拟百度蜘蛛请求,观察头部信息与状态码
curl -v -H "Host: www.llbbs.cn" \
     -H "User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" \
     -H "If-Modified-Since: Tue, 15 Sep 2026 08:30:00 GMT" \
     --resolve www.llbbs.cn:443:120.232.145.10 \
     https://www.llbbs.cn/article/123.html

重点核对三点:

  1. 传入旧的 If-Modified-Since,节点能否正确返回 304 而不是 200 全量包。
  2. 带不同分页参数时,返回的内容长度和正文是否有实际区分。
  3. 检查源站 Nginx 的 access.log,记录中的 $remote_addr 必须是真实客户端 IP,而不是清一色的 CDN 节点内网地址。

改完后观察了三到四天的抓取日志,CDN 回源阶段的 504 报警完全消失,爬虫的 304 协商占比恢复到 40% 左右,每日抓取频次逐步爬坡回升,落下的那些新页面也终于陆续进入了正常收录流程。

评论一下?

OωO
取消