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

实测 · 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 次。
怎么测的
跑数前写死,中途改过一次口径,那次弯路留在记录里。
- 每个首页取一次,记下
Last-Modified、ETag、Cache-Control。 - 把该站刚发的
ETag原样放进If-None-Match再请求一次。 - 把该站刚发的
Last-Modified原样放进If-Modified-Since再请求一次。
第三步是改过的那一步。第一版我们给所有站发同一个固定时间戳,跑出来「27 个里只有 1 个回 304」——这个数字毫无意义,因为内容确实在那个时间点之后变过的站,回 200 是对的。只有把每个站自己的值原样送回去,这个测试才成立。
27 个首页各发了什么
一半的样本,没给爬虫留任何可校验的东西。
| 发了什么 | 站数 | 是哪些 |
|---|---|---|
| 都不发 | 13 | stripe、linear、notion、slack、canva、railway、cloudflare、substack、wired、arstechnica、techcrunch、hacker news、nextjs |
| 只有 ETag | 7 | vercel、figma、supabase、netlify、github、theverge、bbc |
| 只有日期 | 4 | discord、webflow、shopify、nytimes |
| 两个都有 | 3 | mozilla、framer、wikipedia |
这跟公司体量没关系。Cloudflare、Stripe、Notion 什么都不发,Wikipedia 和 MDN 两个都发。真正决定它的,是页面怎么构建、前面挂了几层缓存。
哪些条件请求真的换回了 304
发出这个头只是个承诺。爬虫再来时认不认账,才是真正省下东西的那一步。
| 测什么 | 成了 | 没成 |
|---|---|---|
| ETag 对 If-None-Match | 10 中 9 | bbc.com 回 200,626,140 字节 |
| 日期对自己的值 | 7 中 3 | discord、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。上面这些描述的是一个普通客户端拿到什么,不是一个具名爬虫拿到什么。
今天能查的三件事
按这个顺序。
- 对首页跑
curl -I,看有没有ETag和Last-Modified。两个都没有,就是 27 个里那 13 个的情况,也是最便宜的一种。 - 把拿到的值原样回送成
If-None-Match或If-Modified-Since,确认能收到 304 且响应体为空。 - 如果是 CDN 把这两个头剥了或改了,就去那一层修。源站对了没用,边缘先答的。
这件事对「页多、但大部分很久不变」的站最值钱——文档站、归档页、产品目录。五个页面的站,省下来的只是理论值。你在 sitemap 里声明的更新日期是另一个信号、另一堆麻烦,测在sitemap 的 lastmod 值多少那篇。爬虫决定要抓的页面最后拿到什么,在不存在的地址返回了什么那篇。把这两个头在全站范围内看一遍,而不是只看一个首页,是 QueryWin 正在做的事。
常见问题
你们是怎么测的
每站先取一次首页读响应头,再按头的类型各发一次条件请求,用该站自己的值。浏览器 User-Agent,跟随重定向,2026-08-18。每次都记状态码和响应体长度。
ETag 和 Last-Modified 要不要都设
Google 建议都设,并说两个都在时它用 ETag。我们的样本给出同样的排序,理由更朴素:按标签测,成的次数多得多。
回 304 能帮排名吗
没测过,Google 也没这么说。文档里写明的收益在抓取和带宽上。把它当成一个效率问题,不是排名问题。
为什么按日期测只成了 3 个
败掉的四个里有两个时间戳确实很新,发整页是对的。另外两个从站外解释不了,我们没有硬猜。


