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

实测 · 2026-08-24 · 27 个首页 · 单次抓取 · 只读响应头
样本 / 口径:2026-08-15 以来一直在用的那批 30 个站,2026-08-24 当天每个首页抓一次,浏览器 UA。只记 HTML 文档本身的响应头 —— Cache-Control、ETag、Last-Modified、Age、Vary,子资源一概不取。
27 个首页里 23 个发了 http 缓存指令,但有 13 个既不发 ETag 也不发 Last-Modified。少了校验器,爬虫的条件请求就没有可比对的东西,永远换不回一个 304 —— 而那正是 Google 抓取预算文档点名要的。八个站声明 max-age=0,还有一个用分号分隔指令。
怎么测的
每站一次请求,不渲染、不重试。纳入规则跑数前写死:状态 200、响应体不小于 10,000 字节、含 <body。30 个里 3 个没过 —— stackoverflow.com 与 medium.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。出去的时候两个都没有,回来就无从谈起。
| 发了什么 | 站数 |
|---|---|
只发 ETag | 7 |
只发 Last-Modified | 4 |
| 两个都发 | 3 |
| 一个都不发 | 13 |
两个都发的是 developer.mozilla.org、www.framer.com、www.wikipedia.org。一个都不发的 13 个里有 stripe.com、linear.app、www.notion.com、www.cloudflare.com、railway.com、substack.com、arstechnica.com、techcrunch.com、news.ycombinator.com。
缓存指令说的是这份东西能新鲜多久,校验器才是让爬虫问出「它还新鲜吗」的那件东西。多数站发了前者,忘了后者。
四个站一条 http 缓存指令都没发
stripe.com、slack.com、webflow.com、arstechnica.com 的 HTML 响应里根本没有这个头。这和「禁止缓存」不是一回事,它只是把决定权交给了启发式规则,但确实等于下游谁都没被告知过什么。
四个里 webflow.com 最有意思:没有 Cache-Control,却发了 Last-Modified,而且 Age 是 72,331 秒。前面那层缓存已经自作主张把这份响应握了大约 20 个小时。
max-age=0 是这批样本里最常见的值
八个站这么写:nextjs.org、vercel.com、www.notion.com、supabase.com、www.framer.com、www.netlify.com、github.com、news.ycombinator.com。其中六个还带 must-revalidate,这是「每次请求都现渲染、但在边缘缓存」这类页面的框架默认值。
| max-age | 站数 | 例子 |
|---|---|---|
| 0 | 8 | vercel.com、github.com |
| 10 到 30 | 3 | www.cloudflare.com、www.bbc.com |
| 300 到 900 | 2 | techcrunch.com、www.shopify.com |
| 3,600 | 2 | developer.mozilla.org |
| 86,400 | 1 | railway.com |
| 没声明 | 11 | stripe.com、discord.com |
六个站用 s-maxage 把浏览器和边缘分开算,五个另加 stale-while-revalidate。railway.com 是全样本写得最明白的一个:public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800。两个站反着来,直接发 no-store:www.canva.com 和 www.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 谁还在发。


