Last-Modified:谁还在发,发了又省下了什么

Last-Modified 让爬虫先问一句而不是整页重下。2026-08-18 读的 27 个首页里,13 个连它带 ETag 一起都没有;按标签测 10 次成 9 次,按日期测 7 次只成 3 次。

改写与发布5 分钟读完2849 次阅读
Last-Modified:谁还在发,发了又省下了什么

实测 · 2026-08-18 · 30 个站 · 单次快照

样本与口径:与我们做 robots.txt、sitemap、首页结构三次调查完全相同的 30 个站。每站取一次首页响应头,再发两次条件请求,浏览器 User-Agent,2026-08-18,出口在日本。3 个站拒绝了我们,分母是 27。同一次抓取供本批另外四篇用。

爬虫手里已经有你这一页了,它可以先问一句「变了没有」,而不是整页再下一遍。前提是你得给它一个能拿来问的东西。27 个首页里,13 个既不发 Last-Modified 也不发 ETag——条件请求根本无从发起。有 ETag 的,10 次里 9 次真的回了 304;只有日期的,7 次里只成了 3 次。

怎么测的

跑数前写死,中途改过一次口径,那次弯路留在记录里。

  1. 每个首页取一次,记下 Last-ModifiedETagCache-Control
  2. 把该站刚发的 ETag 原样放进 If-None-Match 再请求一次。
  3. 把该站刚发的 Last-Modified 原样放进 If-Modified-Since 再请求一次。

第三步是改过的那一步。第一版我们给所有站发同一个固定时间戳,跑出来「27 个里只有 1 个回 304」——这个数字毫无意义,因为内容确实在那个时间点之后变过的站,回 200 是对的。只有把每个站自己的值原样送回去,这个测试才成立。

27 个首页各发了什么

一半的样本,没给爬虫留任何可校验的东西。

发了什么站数是哪些
都不发13stripe、linear、notion、slack、canva、railway、cloudflare、substack、wired、arstechnica、techcrunch、hacker news、nextjs
只有 ETag7vercel、figma、supabase、netlify、github、theverge、bbc
只有日期4discord、webflow、shopify、nytimes
两个都有3mozilla、framer、wikipedia

这跟公司体量没关系。Cloudflare、Stripe、Notion 什么都不发,Wikipedia 和 MDN 两个都发。真正决定它的,是页面怎么构建、前面挂了几层缓存。

哪些条件请求真的换回了 304

发出这个头只是个承诺。爬虫再来时认不认账,才是真正省下东西的那一步。

测什么成了没成
ETag 对 If-None-Match10 中 9bbc.com 回 200,626,140 字节
日期对自己的值7 中 3discord、framer、shopify、nytimes

回了 304 的三个是 mozilla、webflow、wikipedia,响应体都是空的。这正是它的意义:爬虫知道了「没变」,而为此几乎没付什么。

回 200 的四个里,有两个说得通。纽约时报的首页时间戳是几分钟前打的,它一整天都在变;framer 的时间戳也很新。discord 是别扭的那个——它的时间戳写着 8 月 14 日,离我们请求隔了四天,它还是发了 170,022 字节过来。

我们没法证明这些服务器忽略了这个头。站在外面看,「两次请求之间内容真的变了」和「这台服务器压根不实现条件请求」,返回的东西一模一样。要分开它俩,得有服务器日志,我们没有。

Google 说优先用 ETag 而不是 Last-Modified,我们的数字站在同一边

Google 的抓取文档把偏好写得很直接:We strongly recommend using ETag because it's less prone to errors and mistakes (the value is not structured unlike the Last-Modified value). And, if you have the option, set them both.(强烈建议用 ETag,它更不容易出错,因为它的值不像 Last-Modified 那样有结构。如果条件允许,两个都设上。)2026-08-18 读自 developers.google.com 的 Crawling December 缓存篇,该文发布于 2024-12-09。

同一篇里还有一个数字,能给我们这 27 个站一个坐标。Google 写道,它的抓取里能命中缓存的比例 has decreased: 10 years ago about 0.026% of the total fetches were cacheable, which is already not that impressive; today that number is 0.017%.(十年前约 0.026% 的抓取是可缓存的,本来就不算好看;今天这个数字是 0.017%。)

万分之一点七。这就是整个互联网,对一个礼貌发问的爬虫说「没变」的频率。

我们这个小样本从另一个角度落在同一边:按日期测,7 个里败了 4 个;按标签测,10 个里只败了 1 个。日期是一个服务器要在它前面每一层缓存上都保持一致的声明,而一个不透明的令牌不需要。

🔴 多层 CDN 串联:出海站要多查一跳

如果你的站是「国内 CDN 回源到海外源站」这种串联结构,或者在 Cloudflare 前面又套了一层加速,这两个头要在最外层那一跳还活着才算数。源站配得再对,只要中间任何一层把它剥掉、或者自己重新生成了一个对不上的值,爬虫拿到的就是「无从校验」。

验证方法只有一个:从公网直接请求你的线上域名,不要在源站上本地测。两边的结果不一样,问题就在中间那几层,不在你的代码里。

这次抓取回答不了什么

每站一对请求,一天,一个国家的出口。我们没有隔几小时再测一遍,所以「闲时回 304、忙时回 200」的服务器,和「一直回 304」的服务器,在我们的数据里长得一样。

我们也没测 Googlebot 自己会不会被同样对待。有些边缘配置按 User-Agent 分流,而我们全程用的是浏览器 UA。上面这些描述的是一个普通客户端拿到什么,不是一个具名爬虫拿到什么。

今天能查的三件事

按这个顺序。

  1. 对首页跑 curl -I,看有没有 ETagLast-Modified。两个都没有,就是 27 个里那 13 个的情况,也是最便宜的一种。
  2. 把拿到的值原样回送成 If-None-MatchIf-Modified-Since,确认能收到 304 且响应体为空。
  3. 如果是 CDN 把这两个头剥了或改了,就去那一层修。源站对了没用,边缘先答的。

这件事对「页多、但大部分很久不变」的站最值钱——文档站、归档页、产品目录。五个页面的站,省下来的只是理论值。你在 sitemap 里声明的更新日期是另一个信号、另一堆麻烦,测在sitemap 的 lastmod 值多少那篇。爬虫决定要抓的页面最后拿到什么,在不存在的地址返回了什么那篇。把这两个头在全站范围内看一遍,而不是只看一个首页,是 QueryWin 正在做的事。

常见问题

你们是怎么测的

每站先取一次首页读响应头,再按头的类型各发一次条件请求,用该站自己的值。浏览器 User-Agent,跟随重定向,2026-08-18。每次都记状态码和响应体长度。

ETag 和 Last-Modified 要不要都设

Google 建议都设,并说两个都在时它用 ETag。我们的样本给出同样的排序,理由更朴素:按标签测,成的次数多得多。

回 304 能帮排名吗

没测过,Google 也没这么说。文档里写明的收益在抓取和带宽上。把它当成一个效率问题,不是排名问题。

为什么按日期测只成了 3 个

败掉的四个里有两个时间戳确实很新,发整页是对的。另外两个从站外解释不了,我们没有硬猜。

Last-Modified:谁还在发,发了又省下了什么