搜尋抓取

压缩與分块传輸:蜘蛛讀頁面时先過的一道解碼

頁面被蜘蛛抓走前,先要经過压缩與分块传輸這一层。响應头声明了 gzip 或 br,正文却對不上,蜘蛛拿到的就是一堆乱碼;chunked 传輸中途卡住,日誌里狀態碼是 200,内容却迟迟讀不完。本文梳理传輸编碼對抓取的實际影响,以及压缩层級、缓存键和自查顺序上的常见坑。

搜尋抓取

压缩與分块传輸:蜘蛛讀頁面时先過的一道解碼

蜘蛛来抓頁面,表面上看是一次 HTTP 請求換回一份 HTML。但在這两者之間,還夹着一层容易被忽略的東西:传輸编碼。頁面往往是先被压缩、再被切成若干块發出去的,蜘蛛要先完成解碼,才能看到一個标簽閉合完整的文档。這一层出問题,通常不會报错,只會让抓取變得比预想的更費劲。

蜘蛛在請求头里说的那句话

抓取請求一般會带上 Accept-Encoding: gzip, deflate, br 這類声明,意思是這些解碼方式我都能處理。服務器可以選擇压缩,也可以選擇不压缩,压缩本身是可選項。但一旦响應头里声明了某種编碼,正文就必须真的按那個格式發出来。

声明與實現不一致的後果

如果响應头寫着 Content-Encoding: br,正文却是普通的 gzip 流或者干脆是明文,解碼就會失敗。蜘蛛拿到的是乱碼,這一次抓取基本只能作废,等下次重訪再来。麻烦在于,服務器日誌里這次請求是成功的 200,传輸也完成了,两邊记錄對不上,排查时最容易走偏。

压缩省下的是字节,不是請求數

压缩不改變蜘蛛發起的請求次數,它影响的是每趟能带回去多少内容。抓取资源有限时,單位時間内能拉取的頁面數,和單頁的字节數直接相關。

  • 文本類頁面经過 gzip 後,常见体积會降到原来的两三成,收益明顯。
  • 已经压過的资源,比如图片、视频、woff2 字体,再压几乎没有效果,還白耗 CPU。
  • 压缩級別從最高档降到中档,体积差別通常不大,CPU 占用却會明顯下降。

所以真正值得優化的是 HTML、CSS、JS 和接口返回的 JSON,而不是把所有响應無差別地压一遍。

分块传輸:Content-Length 缺失意味着什么

開了動態压缩之後,响應長度在發送前是不确定的,服務器往往會改用 chunked 传輸,不再给出 Content-Length。這對蜘蛛来说不算問题,它按块讀取,直到讀到結束标记為止。真正需要留意的是——块什么时候来。

有些後端會在渲染出一部分内容後就 flush 一次,如果中途某個环节卡住,蜘蛛會一直挂着這條连接等下一块。等不到,超时後這次抓取就白跑了。日誌里表現為响應時間很長、狀態碼却仍是 200,單看狀態碼很难發現。

動態压缩與缓存:別让蜘蛛每次都触發一遍

每次請求都現场压缩,CPU 會上去,首字节時間也會變長。常见做法是在前置层做压缩,並让缓存键包含编碼方式這一维度。

如果缓存只按 URL 存一份,压缩又按請求头临时决定,就可能把 br 编碼的响應發给只支持 gzip 的客戶端,或者反過来。前者直接解碼失敗,後者只是多传了些字节,影响程度完全不同。

几個容易踩的坑

  1. 双重压缩:源站已经压過一次,前置层又压了一次,解碼出来仍是压缩流。
  2. 對 304 响應重新压缩,導致 ETag 與實际内容對不上,蜘蛛只能重新拉全量。
  3. 给小体积頁面開最高压缩級別,收益有限,却拖長了首字节時間。
  4. 只在測試环境開了 br,线上没開,两邊行為對不上,問题难以复現。

可以這样自查

  1. 用 curl --compressed 拉一次頁面,看返回的是否為可讀的 HTML。
  2. 對比压缩前後的字节數,確認压缩真的生效,而不是只寫了响應头。
  3. 检查 Content-Encoding 與實际内容是否一致,尤其是有多层代理时。
  4. 看日誌中同一批 URL 的响應時間分布,是否存在明顯的長尾超时。
  5. 確認压缩發生在哪一层,缓存键是否包含编碼方式。
传輸编碼不是抓取策略的核心,但它决定了蜘蛛每次来能带回去多少東西。压缩對得上、分块發得干净、首字节別拖太久,後面的抓取安排才有讨论的余地。

反過来也成立:頁面结构、内鏈、Sitemap 都做得不错,却卡在传輸這一环,抓取量上不去,排查时往往要绕很大一圈才能找到真正的原因。