蜘蛛重复抓取时,带的是一个“我上次见过”的凭证
搜索引擎爬虫在第二次、第三次抓取同一个 URL 时,通常会在请求头里带上 If-Modified-Since 或 If-None-Match。前者对应上一次响应里的 Last-Modified,后者对应 ETag。如果服务端判断“内容没变”,就返回 304 Not Modified,不带响应体。这套机制原本是为了省流量,但放在蜘蛛池入口页的场景里,它同时在向蜘蛛传递一个信号:这个页面还是老样子。
问题就在这里。带宽是省了,但如果你的入口页其实换过内容、加过链接,而服务端仍然回 304,蜘蛛不会重新解析页面,新加的 URL 也就失去了被发现的机会。所以条件请求不是一个“开了就好”的默认项,需要按入口页的性质来决定。
ETag 与 Last-Modified 各自代表什么
- Last-Modified:一个时间戳,表示资源最后修改时间。取值应该来自内容真正变更的时刻,而不是本次请求的时刻。
- ETag:一个标识串,代表资源的某个版本。可以是内容摘要、版本号,也可以是文件元信息,只要保证“内容变则值变、内容不变则值不变”。
- 两个都提供时,多数服务端和客户端会优先比对 ETag。只提供其中一个也能工作,但要注意一致性。
入口页常见的几个配置坑
1. Last-Modified 用了当前时间
有些程序在每次输出页面时把 Last-Modified 设为当前时间,结果蜘蛛每次带回来的 If-Modified-Since 都“过期”,永远拿不到 304。表面看是抓取很勤快,实际上是靠无意义的全量响应换来的,带宽和维护成本都上去了。更糟的是,如果反向代理把这个时间缓存下来,时间戳会变得毫无参考价值。
2. ETag 里带上了机器指纹
常见的默认实现是把文件的 inode、大小、修改时间拼成 ETag。在多机部署的蜘蛛池里,不同机器上同一份文件的 inode 往往不同,于是同一个 URL 在不同节点返回不同 ETag。蜘蛛拿到 A 节点的 ETag,下次请求落到 B 节点,就会一直判定为“有变化”,条件请求形同虚设,还可能让抓取预算浪费在重复内容上。稳妥的做法是基于内容摘要生成 ETag,或者干脆在多台机器上关掉自动 ETag,改用统一的版本号。
3. 动态渲染导致 ETag 每次都不同
入口页里如果混入了随机广告位、实时时间、随机排序的推荐列表,内容摘要每次都不一致,ETag 也就每次都变。这类元素对蜘蛛没有价值,却会让 304 永远不出现。处理办法是把这些动态片段剥离出去,让 spider 看到的入口页正文部分保持稳定。
304 不是越多越好,也不是越少越好
入口页大致可以分两类来看。一类是长期不变、只做链接中转的静态页,这类页面适合正确启用条件请求,让蜘蛛用最低成本确认“还活着”,把带宽留给真正需要抓取的内容。另一类是经常调整链接和文案的运营型入口页,更新时一定要让 Last-Modified 和 ETag 跟着变,否则蜘蛛根本没有理由重新解析。
另外要提醒一点:条件请求影响的是抓取阶段的带宽和判断依据,和页面最终是否被索引、能否获得排名没有直接因果关系。回 304 不代表页面健康,回 200 也不代表会被收录,两件事不要混在一起看。
一份可执行的检查清单
- 用 curl -I 看响应头,确认 Last-Modified、ETag、Cache-Control 是否都存在,值是否合理。
- 带上 If-None-Match 再请求一次,确认内容未变时确实返回 304。
- 多节点各请求一次,比对头部是否一致;不一致就改用内容摘要计算 ETag。
- 手动改一次入口页内容,再带旧 ETag 请求,确认返回 200 而不是 304。
- 检查页面里是否存在随机时间、随机排序等会让内容摘要抖动的内容,有就剥离。
- 把入口页的响应头配置写进部署脚本或模板,避免换服务器时配置漂移。
条件请求的正确用法不是“让蜘蛛少下载”,而是“让蜘蛛下载得有意义”。带宽省下来的部分如果换来的是更新信号丢失,那这笔账是亏的。