蜘蛛爬到一个入口页时,最先读到的并不是页面里的文字,而是服务器返回的响应头。状态码决定这一趟值不值得继续,响应头则决定下次还要不要来、以什么方式缓存、把这段字节当成什么来解析。很多入口页在正文上花了不少功夫,响应头却全是服务器默认值,爬虫对这页的更新节奏和内容类型只能靠猜,猜错的代价就是白跑一趟。
响应头是爬虫读取的第二道信息
响应行之后,每一个字段都会被解析器按顺序读走。不同字段的权重不一样,但有一条规律是共通的:字段缺失或前后矛盾时,爬虫倾向于走保守路径——少来、慢来、按旧内容处理。所以响应头不一定要写得多花哨,但至少要自洽。
Content-Type:别把 HTML 发成别的类型
- 入口页应当返回 text/html 并带上正确的 charset。charset 缺失时,爬虫要靠内容嗅探来猜编码,猜错就是乱码。
- 如果服务器把页面返成 text/plain 或 application/octet-stream,解析器可能根本不当它是一个网页来处理。
- 响应头里声明的编码要和页面 meta 里写的一致。两处打架时,不同爬虫的取舍顺序并不统一。
Last-Modified 与 ETag:告诉爬虫内容变了没有
这两个字段的核心用途是让爬虫判断资源有没有变化,从而决定是重新拉取全文,还是只做一次轻量确认。
- Last-Modified 精度到秒。批量生成的入口页如果所有页面的时间戳完全一致,这个信号基本就废了;反过来,如果每次请求时间都在变但内容没变,爬虫会认为你在制造假更新。
- ETag 建议基于内容摘要生成,而不是随机数或进程 ID。基于内容生成时,内容不变 ETag 就不变,爬虫的比对才有意义。
- 两个字段同时存在且不冲突时,效果最好。只剩一个也能用,但不要出现 ETag 变了而内容没变的反复抖动。
Cache-Control 与 Expires:缓存时间不是越长越好
缓存字段在这里会经过两层:一层是爬虫自身的缓存策略,一层是 CDN 或反向代理的中间缓存。
- 更新频繁的入口页,如果设了很长的 max-age,爬虫和中间层都可能拿到旧版本,你改了正文,外面的视图还是老的。
- 长期不变的入口页可以设长一些,减少重复传输。
- 需要每次确认的场景,用 no-cache 比 no-store 更合适——允许缓存,但要求回源校验。no-store 会让任何一层都不留副本,抓取开销反而变大。
X-Robots-Tag 与其他容易被漏掉的字段
- X-Robots-Tag 的效果和页面里的 meta 指令类似,但它藏在响应头里,改模板时最容易忘记同步。排查入口页为什么没被继续处理时,这个字段要优先看一眼。
- Content-Length 与实际字节数不一致,会让部分客户端提前截断或等待超时。
- 3xx 场景下的 Location 应当指向真实可达的地址,避免链条过长。跳转本身怎么做是另一个话题,但响应头里至少要保证目标稳定。
- Vary 字段如果用得随意,会让缓存命中率忽高忽低,间接影响爬虫拿到的版本。
一份可以照着过的检查清单
- 状态码与响应头是否自洽,正文长度和 Content-Length 能不能对上。
- Content-Type 是否为 text/html,charset 是否和页面 meta 一致。
- Last-Modified 是否反映了真实的最近一次内容修改,而不是部署时间或进程启动时间。
- ETag 是否随内容变化,且不会无故抖动。
- Cache-Control 是否与这页的更新频率匹配。
- X-Robots-Tag 是否和模板里的 meta 指令保持一致,没有互相矛盾。
- 批量抽查几台机器、几条线路,确认响应头是同一套,而不是被某层代理改写过。
响应头能让爬虫更准确地理解入口页的类型和变化,但它只解决“看得懂”的问题,不解决“会不会收录”的问题。把响应头理顺是基本功,不是捷径。