很多人在调入口页时,判断标准是“浏览器打开正常”。但蜘蛛抓取时拿到的第一手信息其实是 HTTP 响应头——页面还没开始解析,头信息已经决定了它用什么编码读这段字节、要不要重新抓一遍、以及把返回内容当成 HTML 还是纯文本。头信息出错,正文写得再规整也可能白费。
响应头是蜘蛛拿到的第一手信息
蜘蛛的流程通常是:建立连接、读响应头、根据头信息决定是否继续读正文、按声明的编码解码、再进入解析。也就是说,编码和缓存相关的判断都发生在正文解析之前。入口页数量多、模板复用度高的时候,一个模板的头写错,往往是整批页面一起出问题,而不是单页。
Content-Type 与字符集:乱码多半从这儿来
最常见的写法是 Content-Type: text/html; charset=utf-8。如果只写了 text/html 而没带 charset,蜘蛛就得靠页面里的 meta 声明或自行猜测,猜测失败就是乱码。乱码不只是“看着难看”,它会直接影响正文提取:分词失败、关键词匹配不上,蜘蛛可能判断这一页没有有效内容。
HTTP 头与 meta 冲突时听谁的
当 HTTP 头声明 UTF-8、页面 meta 却写着 GBK,两者冲突时,多数爬虫会优先采信 HTTP 头。所以出现“浏览器正常、蜘蛛乱码”的情况,往往是因为浏览器做了更宽松的容错,而蜘蛛严格按头信息解码。排查时先看头,再看 meta,两边保持一致最省事。
几个容易踩的写法
- charset 写成 utf8 或大写 UTF8:部分解析器能容错,但没有必要冒险,规范写法是 utf-8。
- 文件实际是 GBK,头却声明 utf-8:这是最典型的乱码来源,改头或改文件必须同步。
- 把 HTML 页面的 Content-Type 写成 text/plain:蜘蛛可能直接跳过解析,只当成一段文本。
- 动态页面忘记输出头,让服务器默认值接管:不同中间件默认值不一样,容易时好时坏。
Last-Modified 与 ETag:判断“变没变”的依据
蜘蛛不一定会每次重读全文。它会参考 Last-Modified 和 ETag 判断这一页是否值得重新抓取。如果模板更新了内容,但这两个字段没跟着变,蜘蛛就可能认为页面没动,继续沿用旧快照。
- Last-Modified:应该反映正文最后一次实际变化的时间。用服务器当前时间或固定写死,都会让这个字段失去意义。
- ETag:适合做内容指纹。注意多台后端机器生成的 ETag 如果算法不一致,同一页面在不同机器上会给出不同值,反而让蜘蛛频繁判定“已变化”。
- 两者不是必须都设,但至少要有一个能真实反映内容变化。
Cache-Control 与 Vary:别让蜘蛛拿到旧版本
入口页如果经过 CDN 或反向代理,Cache-Control 决定缓存多久、谁能缓存。把 HTML 页面设成长期强缓存,内容改了但缓存没到期,蜘蛛读到的就是旧版。Vary 则影响按什么维度区分缓存版本,配置不当可能出现给蜘蛛返回了带登录态或移动端版本的页面。
比较稳妥的思路是:能反映内容更新的页面用较短的缓存时间,或者配合 Last-Modified、ETag 让客户端和爬虫有机会拿到新版本;不要把整站 HTML 都设成一年过期。
一份自查清单
- 用 curl -I 直接看响应头,不要只看浏览器开发者工具里被加工过的版本。
- 确认 Content-Type 带 charset,且与文件真实编码一致。
- 确认 Last-Modified 会随正文变化而变化,而不是固定值或永远等于当前时间。
- 确认 HTML 的缓存策略不会让更新长期滞后。
- 抽查几台后端机器返回的 ETag 是否一致。
- 把入口页和普通内容页各抽一两个样本,避免只测了其中一类。
常见误区
- “浏览器正常就等于蜘蛛正常”——两者容错程度不同,浏览器会猜,蜘蛛更严格。
- “响应头是运维的事,和内容无关”——编码和缓存直接决定蜘蛛能不能正确读到内容。
- “字段越多越保险”——写错或互相矛盾的字段比不写更容易出问题。
响应头不决定内容质量,但它决定了蜘蛛能不能正确读到你的内容。先把这一层对齐,再谈结构和内容,排查效率会高很多。