搜尋抓取

搜尋蜘蛛抓取: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 做回归,同时覆盖压缩與不压缩两種請求。這样下次出問题,能快速分清是压缩、分块還是缓存引起的。