27 个首页的 http 缓存头:13 个既不发 ETag 也不发 Last-Modified

http 缓存指令 27 个首页里 23 个发了,但 13 个一个校验器都不发 —— Google 抓取预算文档点名要的那个 304 在这 13 个站上根本发生不了。八个站声明 max-age=0,还有一个用分号分隔指令。

抓取与收录4 分钟读完2107 次阅读
27 个首页的 http 缓存头:13 个既不发 ETag 也不发 Last-Modified

实测 · 2026-08-24 · 27 个首页 · 单次抓取 · 只读响应头

样本 / 口径:2026-08-15 以来一直在用的那批 30 个站,2026-08-24 当天每个首页抓一次,浏览器 UA。只记 HTML 文档本身的响应头 —— Cache-ControlETagLast-ModifiedAgeVary,子资源一概不取。

27 个首页里 23 个发了 http 缓存指令,但有 13 个既不发 ETag 也不发 Last-Modified。少了校验器,爬虫的条件请求就没有可比对的东西,永远换不回一个 304 —— 而那正是 Google 抓取预算文档点名要的。八个站声明 max-age=0,还有一个用分号分隔指令。

怎么测的

每站一次请求,不渲染、不重试。纳入规则跑数前写死:状态 200、响应体不小于 10,000 字节、含 <body。30 个里 3 个没过 —— stackoverflow.commedium.com 返回 403,www.reddit.com 返回 8,393 字节空壳 —— 剩 27 个。

要说清楚:我们读到的是边缘节点在某一刻、从某一个位置回给我们的字符串。CDN 完全可以对另一个地区的爬虫回得不一样,而单次抓取也分不出这个头是源站发的还是前面那层缓存发的。我们没有再发一次条件请求去看谁真的会回 304。

Google 要的是一个具体动作,13 个站做不出来

Google 的抓取预算文档(2026-08-24 读)写的是一条指令:「Use HTTP caching: Support 304 (Not Modified) HTTP status codes. If a page hasn't changed since Google last crawled it, returning a 304 code tells Google to reuse the cached version, saving your server bandwidth and resources.」

304 需要一个校验器。爬虫得能拿你的 ETag 发回一个 If-None-Match,或者拿你的 Last-Modified 发回 If-Modified-Since。出去的时候两个都没有,回来就无从谈起。

发了什么站数
只发 ETag7
只发 Last-Modified4
两个都发3
一个都不发13

两个都发的是 developer.mozilla.orgwww.framer.comwww.wikipedia.org。一个都不发的 13 个里有 stripe.comlinear.appwww.notion.comwww.cloudflare.comrailway.comsubstack.comarstechnica.comtechcrunch.comnews.ycombinator.com

缓存指令说的是这份东西能新鲜多久,校验器才是让爬虫问出「它还新鲜吗」的那件东西。多数站发了前者,忘了后者。

四个站一条 http 缓存指令都没发

stripe.comslack.comwebflow.comarstechnica.com 的 HTML 响应里根本没有这个头。这和「禁止缓存」不是一回事,它只是把决定权交给了启发式规则,但确实等于下游谁都没被告知过什么。

四个里 webflow.com 最有意思:没有 Cache-Control,却发了 Last-Modified,而且 Age 是 72,331 秒。前面那层缓存已经自作主张把这份响应握了大约 20 个小时。

max-age=0 是这批样本里最常见的值

八个站这么写:nextjs.orgvercel.comwww.notion.comsupabase.comwww.framer.comwww.netlify.comgithub.comnews.ycombinator.com。其中六个还带 must-revalidate,这是「每次请求都现渲染、但在边缘缓存」这类页面的框架默认值。

max-age站数例子
08vercel.com、github.com
10 到 303www.cloudflare.com、www.bbc.com
300 到 9002techcrunch.com、www.shopify.com
3,6002developer.mozilla.org
86,4001railway.com
没声明11stripe.com、discord.com

六个站用 s-maxage 把浏览器和边缘分开算,五个另加 stale-while-revalidaterailway.com 是全样本写得最明白的一个:public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800。两个站反着来,直接发 no-storewww.canva.comwww.theverge.com

那个用分号的

news.ycombinator.com 回的是 cache-control: private; max-age=0。2026-08-24 这天我们用两个不同的客户端各查了一次,字符串一模一样。

MDN 的 Cache-Control 参考页(同日读)把规则写得很清楚:「Multiple directives are permitted and must be comma-separated (e.g., Cache-control: max-age=180, public).」分号在这里不是分隔符。各家缓存实现拿后半截怎么办,我们没测;这一处的实际代价也不高 —— 那个页面本来就是 private。但它是个好提醒:响应头是字符串,没有人替你校验。

这对你意味着什么

这个检查一条命令就够,不用装任何工具。对自己的页面跑一遍,看三行。

curl -sI https://example.com/your-page | grep -i 'cache-control\|etag\|last-modified'
  • 发一个校验器。只发 ETag 就够了,它是 Google 要的那个 304 能发生的前提。
  • 页面是动态的就保留 max-age=0, must-revalidate —— 这是实话,而且它并不妨碍条件请求。
  • 想被常抓的公开页面别发 no-store。那等于把便宜那条路对所有人关掉。
  • 别默认框架替你发了校验器。这里八个 max-age=0 的站里,五个发了 ETag,三个一个都没有。

缓存只对爬虫能到达的页面有意义。不确定自己的页面能不能被读到,先 查一下 AI 爬虫能不能读到你的页面 再调响应头;改完之后那一步,见 页面改完之后怎么推送收录

常见问题

你们是怎么测的

2026-08-24 当天每个首页一次请求,浏览器 UA,只保留 HTML 文档的响应头。没有条件请求,没有第二次抓取,不取子资源。这篇里每一个值都是某一刻边缘服务器回来的字符串。

http 缓存会影响排名吗

没有谁把它写成排名输入。它影响的是抓取效率,Google 是在大站与抓取预算的语境下讲这件事的。一个四十页的站,这件事基本不相干,我们宁可直说,也不想把一个响应头卖成优化项。

HTML 该不该设一个很长的 max-age

只有页面真的不变才值得,而即便如此,发个校验器也比它管用。这批样本里只有一个站在首页上写了 86,400 秒。那是知道自己发布节奏的人做的选择,不是可以照抄的默认值。

no-cache 和 no-store 差在哪

no-cache 允许存,但复用前必须回来验一次。no-store 是根本不许存。这批里四个站发前者、两个发后者,说明写它们的人是分得清的。相邻的另一个问题 —— 谁还在发修改时间 —— 见 Last-Modified 谁还在发

27 个首页的 http 缓存头:13 个既不发 ETag 也不发 Last-Modified