搜索抓取

搜索蜘蛛抓取:Content-Encoding 与分块传输不一致造成的正文残缺排查

蜘蛛抓取时正文要经压缩解码与分块拼接才完整,响应头与数据流不一致会让它只拿到半截内容。本文按取原始响应、比对压缩前后字节、核对分块结尾、检查代理与源站压缩层级的顺序,梳理传输层异常的排查方法与修复后的观察指标。

搜索抓取

搜索蜘蛛抓取:Content-Encoding 与分块传输不一致造成的正文残缺排查

蜘蛛抓取页面时,最先拿到的是 HTTP 响应头和传输层的数据流,正文内容要等压缩解码和分块拼接全部完成后才算完整。服务器或 CDN 在这两个环节配置不一致,很容易让蜘蛛拿到半截内容,或者干脆判定这次抓取失败。

压缩与分块为什么会卡住抓取

传输方式大体有两种:一种是整段响应配 Content-Length,另一种是分块传输(Transfer-Encoding: chunked)边生成边发送。前者要求服务器先把内容长度算准,后者允许动态输出。压缩则分 gzip、deflate、br 等,声明在 Content-Encoding 里。

问题往往出在声明与实际不一致:响应头说压缩了,实际发的是明文,或者反过来;chunked 的分块长度写错、结尾的 0 块缺失;压缩流被中途截断又没有正确的结束标记。解码器遇到这类数据会放弃当次抓取,日志里表现为已请求但取不到正文。

常见异常表现

  • 状态码是 200,但抓取到的正文长度接近 0 或明显偏短。
  • 响应头与响应体不匹配:Content-Encoding 声明 br,内容却是未压缩的 HTML。
  • 同一 URL 换不同 User-Agent 请求,返回的传输方式不一样。
  • 页面在浏览器里正常,用带 --compressed 的命令行请求取回来却是乱码或截断。
  • 只有部分节点或部分机房出现,换 IP 重试就恢复正常。

排查顺序

  1. 先取原始响应头。用 curl -I 或带 --compressed 的完整请求,确认 Content-Encoding、Transfer-Encoding、Content-Length 三个字段是否互斥且自洽。Content-Length 与 chunked 同时出现,本身就是配置冲突。
  2. 比较压缩前后的字节数。关掉压缩取一次,开启压缩取一次,看解码后的长度是否一致。差异过大说明压缩流有截断。
  3. 检查分块结尾。chunked 必须以长度为 0 的块结束,后面可以跟 trailer。缺失结束块会让客户端一直等待,最终超时。
  4. 核对反向代理与源站的压缩层级。源站压一次、CDN 再压一次形成双重压缩,客户端只解一层就会得到乱码。
  5. 区分动态与静态路径。静态文件由 Nginx 直接压缩,动态接口由应用层输出,两边的缓冲区大小和 flush 时机可能不同。
  6. 固定小样本反复对比。挑几个有代表性的 URL(列表页、详情页、带参数的页),在多个节点上重复取样,而不是只看单次结果。

配置上容易踩的坑

缓冲区与 flush 时机

应用层如果设置了过大的输出缓冲,又依赖定时 flush,遇到进程回收或超时中断时,缓冲区里的内容就丢了。表现是响应看似完成,实际缺少尾部 HTML,而内链往往正好在尾部。

压缩级别的边界情况

最高压缩级别在部分实现上会加大内存占用,反而更容易触发超时。对蜘蛛访问的路径,用中等压缩级别通常更稳。

错误页的处理

出错时返回的页面如果还带着正常的压缩头,长度又对不上,客户端会把它当成正常响应的一部分。建议错误响应显式关闭压缩,并给出准确的 Content-Length。

不同抓取工具对传输错误的容忍度差异很大,同一种异常可能一个直接失败、一个拿到残缺内容。观察时以原始响应为准,不要只看渲染后的页面。

修复后的观察方式

改完之后,先看服务器访问日志里蜘蛛请求的响应字节数是否回到正常区间,再看抓取统计中内容为空的 URL 数量是否下降。这个过程通常需要几天到几周,取决于站点被抓取的频率,不要期待改完立刻见效。

建议把这条链路写进上线检查清单:任何涉及传输层配置的改动,发布后都用一条固定样本 URL 做回归,同时覆盖压缩与不压缩两种请求。这样下次出问题,能快速分清是压缩、分块还是缓存引起的。