網頁擷取回應被截斷時,先別急著歸咎於代理

SEO 標題: 網頁擷取回應截斷診斷指南

SEO 關鍵字: 網頁擷取回應截斷, 代理除錯, HTML 不完整, Content-Length 驗證, 壓縮回應

SEO 描述: 透過位元組計數、回應標頭、解壓驗證、對照路由與內容哨兵,區分代理故障、主動分段與回應截斷。

擷取程式收到 HTTP 200,並不代表正文一定完整。連線可能提前關閉,客戶端可能停止讀取,解壓過程可能失敗,閘道可能存在大小上限,目標站也可能主動回傳較小的頁面版本。在確認邊界之前就更換整個代理池,不只浪費時間,也可能掩蓋真正的故障點。

本指南建立一套可重現的回應完整性檢查,適用於已獲授權的資料蒐集、品質驗證、監控與研究。

網際網路資料封包經過多層閘道,同時由完整性監測器檢查回應是否完整

先定義什麼叫「完整」

不要只看狀態碼,至少要定義兩類訊號:

  • 傳輸訊號: 在回應存在可信長度宣告時,實際接收的同類位元組數與宣告一致。
  • 應用訊號: 頁面中存在穩定結尾標記、預期記錄數、校驗和、必要欄位或下一頁權杖。

HTML 的閉合標籤只能作為弱訊號,因為合法頁面也可能省略它。更可靠的做法是檢查穩定頁尾識別、結構化資料區塊或預期內容區。JSON 應完整解析並驗證必要欄位;壓縮檔和媒體則應使用格式自身的校驗和或結束規則。

解析之前先保存原始事實

每次請求至少記錄:最終 URL 與重新導向鏈、狀態碼和 HTTP 版本、Content-Length、Content-Encoding、Transfer-Encoding、Content-Range、線上接收位元組數、解碼後正文大小、首位元組時間、總耗時、連線重用狀態與路由編號。

保留原始回應標頭和正文雜湊,但必須移除 Cookie、授權標頭、權杖與個人資料。

不要比較錯誤的位元組數

當回應包含 Content-Encoding: gzip 時,Content-Length 通常描述線上傳輸的壓縮表示;很多客戶端程式庫回傳的卻是自動解壓後的正文。直接比較二者會製造「回應被截斷」的假警報。

應分別記錄:宣告的傳輸長度、實際壓縮或線上傳輸長度、解碼後的應用正文長度。如果客戶端隱藏線上位元組數,可在受控診斷中關閉自動解壓,或使用獲准的可觀測層蒐集資料。

執行四路對照測試

保持 URL、請求標頭、方法、時間視窗和解析器一致,分別測試:

  1. 已授權網路的直連對照;
  2. 一條已知穩定的代理路線;
  3. 疑似故障路線;
  4. 同一目標區域的另一條獨立出口。

每條路線只重複少量固定次數,比較狀態碼、回應標頭、線上位元組、解碼位元組、正文雜湊、內容哨兵和耗時。

  • 僅一條路線截斷:檢查該出口、上游閘道或連線重用。
  • 所有代理都在相同位元組邊界截斷:檢查共享客戶端、閘道或服務商上限。
  • 直連與代理表現完全一致:目標站、請求設定或客戶端更可疑。
  • 大小不同但內容哨兵均通過:可能是合法的個人化或壓縮版本。
  • 總在固定時間停止:先檢查讀取逾時或傳輸停滯。

增加串流完整性檢查

蒐集器應在讀取過程中累計位元組,並保存最後成功偏移量。結果分類至少包括 complete、truncated、intentional_partial 與 inconclusive,不要強迫所有情況只能通過或失敗。

async function collectWithIntegrity(response, expected) {
  const chunks = [];
  let decodedBytes = 0;
  for await (const chunk of response.body) {
    decodedBytes += chunk.byteLength;
    chunks.push(chunk);
  }
  const body = concat(chunks);
  return {
    body,
    result: {
      status: response.status,
      decodedBytes,
      declaredLength: parseLength(response.headers.get("content-length")),
      encoding: response.headers.get("content-encoding"),
      range: response.headers.get("content-range"),
      bodyHash: sha256(body),
      sentinelPresent: expected.sentinel ? body.includes(expected.sentinel) : null
    }
  };
}

識別主動分段回應

當請求使用 Range 或媒體客戶端恢復下載時,帶有有效 Content-Range 的 HTTP 206 可能完全正確。判斷前應檢查請求是否包含 Range、重新導向或重試是否加入了它、回傳區間是否符合、總大小是否明確,以及蒐集器能否正確拼接多段內容。

意外出現的 206、重疊區間、缺失區間,或提前結束的 200 回應才需要進一步調查。

區分客戶端上限與網路故障

只在自有或獲授權基礎設施上準備 256 KB、1 MB、2 MB、4 MB、8 MB 等可控測試物件,並分別測試易壓縮與不易壓縮資料。固定的解碼大小邊界通常指向客戶端或後處理上限;固定的線上位元組邊界更像傳輸或閘道策略;隨時間出現的邊界則更像逾時。

Google Search Central 曾公開說明,其擷取器會對單一資源設定位元組上限,並把已擷取的前綴作為可用文件。這提醒我們:連線看似正常,也可能是消費端主動限制讀取量。應同時記錄每個元件的文件上限和實測上限。

重試時不要破壞證據

  • 連線重設和逾時可使用帶抖動的指數退避。
  • 對確定性的哨兵或結構驗證失敗不要無限重試。
  • 只有在明確測試路線差異時才切換出口。
  • 對照請求的標頭、語言、Cookie 與時間視窗必須穩定。
  • 保存首次失敗正文、失敗偏移量、重試原因與最終分類。

操作檢查清單

  • 同時定義傳輸層與應用層完整性訊號。
  • 記錄重新導向、狀態、編碼、分段、位元組、雜湊與耗時。
  • 分開壓縮長度、線上長度與解碼長度。
  • 比較直連、穩定代理、疑似代理和同區域獨立出口。
  • 在獲授權基礎設施上測試固定大小物件。
  • 有效 206 預設視為主動分段,除非證據顯示異常。
  • 保存首次失敗正文和最後成功偏移量。
  • 使用有上限的重試和穩定請求設定。
  • 清除憑據、權杖、Cookie 與個人資料。
  • 用精簡證據表升級問題,而不是只交一張截圖。

常見問題

HTTP 200 能證明正文完整嗎?

不能。回應標頭送達後連線仍可能中斷。應驗證傳輸邊界,並檢查應用層哨兵或資料結構。

Content-Length 必須等於正文緩衝區大小嗎?

啟用自動解壓時不一定。回應標頭可能描述壓縮傳輸位元組,而客戶端提供的是解碼正文,必須比較同一層級的數字。

頁面更小就代表被攔截或偽裝嗎?

不代表。語言、裝置、工作階段、同意狀態、實驗與合法個人化都會改變大小。先比較語意哨兵和對照路線。

什麼時候應該隔離代理路線?

當受控重複測試顯示該路線獨有連線重設、截斷、資料區塊損壞或明顯更低的完整率,而相同請求在獨立對照中成功時,應隔離並調查。

合規說明

只蒐集已獲授權存取的資料,並遵守網站條款、適用的 robots 指引、隱私與資料保護要求、合約限制和合理請求頻率。不得藉助代理繞過身分驗證、存取控制、付費牆或執法措施。

後續可結合代理與目標站四路診斷方案和瀏覽器代理容量指南繼續定位問題。