CDN 缓存把旧页面喂给爬虫两周:边缘缓存头对收录更新的影响与配置

2026-09-27 01:19:31 0 次浏览
SEOCDNCloudflareNginxHTTP缓存

一、问题是怎么被发现的

今年 6 月初,我们接手一个做工业配件出口的独立站(Magento 架构,前面套了一层 Cloudflare 免费版)。客户 5 月 20 日完成了一次大改版:商品页的 SEO 标题全部重写、十几款主力商品调了价格、新增了 60 多个 SKU 页面。按以往经验,Google 对这种量级的站点,标题更新一般一周内就能在收录里反映出来。

CDN 边缘缓存与收录更新示意图

结果到了第 10 天,用 site: 语法抽查了 30 个商品页,收录快照里的标题还是旧版。Google Search Console(GSC)里「网页索引编制」报告显示这些页面状态是「已编入索引」,但「上次抓取时间」大多停在 5 月 22 日前后——也就是说抓取确实发生了,抓到的却是旧内容,Google 自然认为页面没变,不值得重新收录。

真正定位到问题是在 6 月 3 日。我用 GSC 的「网址检查」功能对其中一个商品页点了「测试实际网址」,实时抓取返回的 HTML 里,<title> 和价格字段全是改版前的旧值。源站当时已经确认是新版内容,那么中间只有一个嫌疑人:CDN 边缘缓存。

结论先说:这不是 Google 的问题,是边缘节点把旧页面当成新页面,连续两周喂给了爬虫。

二、排查过程:从响应头到日志

2.1 curl 看缓存命中状态

排查缓存问题,curl -I 是第一工具。对同一个商品页连续请求两次:

# 环境要求:任意带 curl 的终端(Windows 10 自带 curl.exe 即可)
# 第一次请求,观察缓存命中标记与缓存头
curl -sI "https://www.example-parts.com/products/flange-bolt-m12" \
  -H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

# 第二次请求同样的 URL,对比 CF-Cache-Status 是否变为 HIT
curl -sI "https://www.example-parts.com/products/flange-bolt-m12"

两次响应头里的关键信息:

响应头 实际返回值 说明
CF-Cache-Status HIT 边缘节点命中,内容来自缓存而非源站
Cache-Control public, max-age=604800, s-maxage=2592000 HTML 被允许缓存 7 天,CDN 边缘存 30 天
Last-Modified 缺失 爬虫无法发起条件请求校验新旧
ETag 缺失 同上,连 304 协商缓存的基础都没有
Age 86423 这份缓存已经在边缘节点存活近 24 小时

问题很清楚了。max-age=604800 配在 HTML 上,意味着边缘节点最长 7 天才会回源取一次新内容;而源站响应里居然没有 Last-Modified 和 ETag,即使边缘缓存过期回源,也无法利用条件请求(Conditional Request)机制。更糟的是,Cloudflare 命中缓存时直接返回 200 加缓存体,Googlebot 拿到的永远是旧快照,连发起 If-Modified-Since 验证的机会都没有。

2.2 X-Cache 与 Nginx 场景的对照

我们另一批客户的站点是 Nginx proxy_cache 自建的边缘层,排查思路一样,只是命中的判断标记换成 X-Cache-Status:

# Nginx 自建缓存层的站点,检查 X-Cache-Status 头
# 需要在 nginx.conf 的 add_header 中暴露 $upstream_cache_status
curl -sI "https://shop.another-site.com/products/valve-actuator" | grep -iE "x-cache|cache-control|etag|last-modified"

返回里 X-Cache-Status: HIT,同样配着 proxy_cache_valid 200 7d,同样没有给 HTML 关闭缓存。两套架构,同一个病根。

2.3 GSC 实时抓取与日志的 304 占比

第二个证据来自服务器访问日志。我写了个简单的统计脚本,把 5 月 20 日到 6 月 3 日的日志里 Googlebot 的请求按状态码分组:

时间段 Googlebot 请求总数 200 占比 304 占比 收录标题更新延迟
5 月 20 日 - 6 月 3 日(修复前) 3,182 97.4% 2.6% 约 12 天
6 月 4 日 - 6 月 17 日(修复后) 3,405 41.8% 58.2% 1 天以内

注意这是单站观测数据,样本只有这一个站点,不代表行业普遍水平。但方向很有说服力:修复前 97% 的爬虫请求都拿到 200 全量响应——全是从边缘缓存吐出来的旧 HTML;修复后 304 占比冲到 58%,说明爬虫开始大量用条件请求校验,页面没变化就省掉传输,页面变了(收到 200 或新校验结果)就立刻更新收录。

抓取配额(Crawl Budget)没有变多,但有效抓取的质量完全不同了。这也是网站优化里一个常被忽视的点:让爬虫拿到正确的新鲜内容,比单纯追求抓取频率重要得多。

三、原理剖析:抓取、缓存、索引之间的更新时延链路

搜索引擎更新一条收录,链路是「调度器决定抓取 → 抓取器请求页面 → 解析内容与旧快照比对 → 有实质变化才进入重索引队列」。这条链路里每一步都依赖一个前提:抓取器请求到的内容必须能代表源站的当前状态。

CDN 缓存介入后,这个前提被破坏了。梳理一遍完整链路:

sequenceDiagram
    participant G as Googlebot
    participant E as CDN 边缘节点
    participant O as 源站
    G->>E: GET /products/xxx
    alt 缓存命中且未过期(max-age 内)
        E-->>G: 200 + 旧 HTML(不回源)
    else 缓存过期
        E->>O: GET(带 If-Modified-Since/If-None-Match)
        O-->>E: 304 或 200 + 新 HTML
        E-->>G: 200(可能仍是 TTL 内的旧内容)
    end
    G->>G: 与索引快照比对(无变化则不重索引)

关键在第一个分支:只要 TTL 没过期,爬虫的请求根本到不了源站。HTML 的 max-age=604800 意味着在最长 7 天的窗口里,无论源站改了多少次内容,所有访问者——包括爬虫——拿到的都是同一份旧页面。改版日 5 月 20 日写入缓存的旧 HTML,会一直存活到 5 月 27 日左右;下一个 7 天窗口又是同样的循环。两周看不到更新,时间线完全对得上。

再看条件请求为什么失效。HTTP 缓存协商依赖 ETag(实体标签,Entity Tag)或 Last-Modified:爬虫第二次请求时带上 If-None-Match 或 If-Modified-Since,源站比对后如果内容没变就返回 304,只回头部不回正文。但这里有两个坑同时存在:

  1. 源站响应缺 ETag 和 Last-Modified,爬虫没有凭证可带,每次只能全量请求;
  2. 边缘缓存命中时直接返回 200,校验动作被边缘层短路了,Cache-Control: public 又允许缓存器自作主张跳过回源验证。

这就是「缓存命中掩盖内容更新」的机制本质:缓存层替爬虫做了「内容没变」的判断,但它的判断依据是时间(TTL),不是内容本身。 TTL 是站长配的,一旦配得比内容更新周期长,爬虫的世界就比真实世界滞后了。

MDN 的 HTTP 缓存文档里对 stale-while-revalidate 的描述恰好给出了折中方案:允许先返回旧内容保证速度,同时异步回源刷新——对爬虫场景来说,这能把「旧内容窗口」压缩到秒级。

四、修复配置:HTML 短缓存 + 静态资源长缓存指纹化

修复思路是分层的:HTML 是收录的载体,必须新鲜;CSS/JS/图片变化频率低且带指纹,可以放心长缓存。两层策略分开配。

4.1 Nginx 源站配置

# 环境:Nginx 1.24,作为 Magento 源站的前置层
server {
    listen 443 ssl;
    server_name www.example-parts.com;

    location / {
        proxy_pass http://magento_backend;

        # HTML 与动态路径:禁止源站输出长缓存头
        # 交给 CDN 用 s-maxage 控制边缘短缓存
        add_header Cache-Control "public, s-maxage=120, max-age=0, must-revalidate" always;

        # 保留实体校验头,让条件请求(304)能够生效
        # etag on 与 last_modified 系列是 nginx 默认值,显式写出防止被覆盖
        etag on;
        last_modified compiling;
    }

    # 静态资源:带内容指纹(版本号/哈希文件名),可放心长缓存
    location ~* \.(js|css|png|jpg|jpeg|webp|woff2)$ {
        proxy_pass http://magento_backend;
        # 一年缓存 + immutable:浏览器和 CDN 都不回头验证
        add_header Cache-Control "public, max-age=31536000, immutable" always;
    }
}

这里有个容易踩的细节:s-maxage 只对共享缓存器(CDN、代理)生效,max-age=0, must-revalidate 约束的是浏览器端不要缓存 HTML。这样即使边缘出了问题,用户浏览器也不会本地留存旧页面。

4.2 Cloudflare 面板配置对照

Cloudflare 免费版默认不缓存 HTML(只缓存静态扩展名),我们的旧缓存头之所以生效,是前任运维加了一条「Cache Everything」页面规则,TTL 还被「Edge Cache TTL」设置顶到了 30 天。修改动作:

配置项 修复前 修复后 作用
Cache Everything 页面规则 全站启用 移除,改用 Cache Rules 避免无差别缓存 HTML
Edge Cache TTL 30 天 遵循源站(Respect Existing Headers) 边缘 TTL 跟随 s-maxage=120
Browser Cache TTL 4 小时 遵循源站 浏览器侧不再叠加缓存
源站 Cache-Control max-age=604800 s-maxage=120 + must-revalidate HTML 两分钟内过期回源
静态资源规则 默认 max-age=31536000 + 指纹文件名 长缓存不回源

改完后我又在 Cache Rules 里对 /products/* 显式加了一条:缓存状态可用但必须 stale-while-revalidate=60,即过期后允许先回旧内容撑 60 秒,同时后台回源刷新。爬虫的高频抓取由此几乎全部命中边缘,源站压力没涨,新鲜度却上来了。

另外建议保留两个验证手段:一是 Cloudflare 的 CF-Cache-Status 头改版后应当看到 MISS → HIT 的快速交替(两分钟 TTL 下正常流量就能观察到循环);二是 Nginx 场景下 $upstream_cache_status 配合 REVALIDATED 状态,能确认条件请求真的在工作。

五、修复后的缓存策略分层

把最终生效的策略画成一张分层图,方便对照检查自己的站点:

flowchart TD
    A[用户 / 爬虫请求] --> B{请求的是 HTML 还是静态资源?}
    B -->|HTML 动态页| C[边缘缓存 s-maxage=120]
    B -->|JS/CSS/图片 指纹文件| D[边缘+浏览器长缓存 max-age 一年 immutable]
    C --> E{缓存是否命中?}
    E -->|未命中| F[回源带条件请求 If-None-Match]
    E -->|命中| G[返回 200 缓存内容 Age 小于 120 秒]
    F --> H{源站内容变了吗?}
    H -->|没变| I[返回 304 边缘继续用旧缓存]
    H -->|变了| J[返回 200 新 HTML 刷新边缘]
    G --> K[TTL 过期后 stale-while-revalidate 异步刷新]
    J --> L[爬虫解析新内容 进入重索引 收录更新]

修复一周后复查:GSC 里抽查的 30 个商品页,标题全部更新为新版;「上次抓取时间」集中在改版后的 24 小时内。源站回源请求量只上升了约 18%(从日志统计),因为静态资源的长缓存完全没动,只有 HTML 层多回了几次源。这笔账是划算的——收录更新延迟从约 12 天压缩到 1 天以内,对促销季频繁调价的独立站来说,这意味着价格页面的收录能跟上实际售价。

六、误区澄清与收尾

三个最常见的误区,写在最后提醒一下:

  1. 「CDN 缓存只影响用户,不影响 SEO」——错。Googlebot 和普通访客走的是同一条边缘链路,缓存喂给谁旧内容,收录就滞后谁的。
  2. 「把 HTML 缓存全部关掉最安全」——过犹不及。完全禁缓存会让抓取配额消耗在全量回源上,高频抓取反而变慢。短缓存加条件请求才是兼顾新鲜度与配额的做法。
  3. 「配了 Cache-Control 就完事了」——还要验证 ETag/Last-Modified 真的输出去了,且中间层没有剥离它们。有些安全插件和旧的页面规则会顺手把这些头删掉,304 就永远触发不了。

Cache-Control 配置属于网站优化(SEO)里偏基础但收益稳定的工程项,它的价值不在单点排名,而在保证「源站改了什么,搜索引擎就能多快看到什么」。这个能力往后也是 AI 搜索场景的地基——生成式引擎优化(Generative Engine Optimization, GEO)同样依赖引擎能抓到最新页面,缓存头配错了,面向 AI 引擎做再多语义优化也是白搭。这一层先夯实,后面的路才好走。

如果你也在做外贸独立站,欢迎在评论区聊聊你们站点 HTML 的缓存策略是怎么配的,尤其是 Cloudflare 免费版用户的 Cache Rules 实践。

参考与延伸

  • Cloudflare 官方文档:Cache Control(缓存控制头与 Edge Cache TTL 行为说明)—— https://developers.cloudflare.com/cache/concepts/cache-control/
  • Nginx 官方文档:ngx_http_proxy_module 的 proxy_cache 指令(含 proxy_cache_valid 与 $upstream_cache_status)—— https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache
  • MDN Web Docs:HTTP 缓存(Cache-Control、ETag、条件请求与 stale-while-revalidate 详解)—— https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching

SEO、网站优化、独立站自然流量、CDN 缓存、Cache-Control、ETag、收录更新延迟、条件请求

🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。