採集網頁時遇到亂碼常因 GZIP 壓縮。本文提供 HTTP 標頭檢查、內容魔數偵測、cURL 自動解壓三種方案,並整合為智慧型獲取函式。
<?php
function isGzippedByHeader($url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true, // HEAD 請求
CURLOPT_HEADER => true, // 返回標頭
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$response = curl_exec($ch);
curl_close($ch);
return stripos($response, 'Content-Encoding: gzip') !== false;
}
GZIP 檔案前 2 位元組固定為 0x1f 0x8b (十進位 31 139):
<?php
function isGzippedByMagic($content) {
if (strlen($content) < 2) return false;
$bytes = unpack('C2', $content);
return $bytes[1] === 0x1f && $bytes[2] === 0x8b;
}
// 實際應用:讀取前 2 bytes 判斷
$url = 'https://www.miercn.com';
$fp = fopen($url, 'rb');
$bin = fread($fp, 2);
fclose($fp);
$bytes = unpack('C2', $bin);
$typeCode = $bytes[1] * 256 + $bytes[2]; // 或直接比對陣列
$isGzip = ($typeCode === 0x1f8b); // 8063
if ($isGzip) {
$url = 'compress.zlib://' . $url;
}
$html = file_get_contents($url);
$html = iconv('GBK', 'UTF-8', $html);
echo $html;
function fetchAutoDecompress($url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_ENCODING => '', // 關鍵:接受所有編碼 (gzip, deflate, br)
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Client)',
]);
$content = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno) {
throw new Exception("獲取失敗: " . curl_error($ch));
}
return $content;
}
<?php
function smartFetch($url, $targetEncoding = 'UTF-8') {
// 策略 1: cURL 自動解壓 (最完整)
if (function_exists('curl_init')) {
return fetchAutoDecompress($url);
}
// 策略 2: file_get_contents + zlib 串流
if (ini_get('allow_url_fopen')) {
// 先嘗試 zlib 串流 (自動偵測)
$content = @file_get_contents('compress.zlib://' . $url);
if ($content !== false) {
return convertEncoding($content, $targetEncoding);
}
// 失敗則嘗試直接讀取
$content = @file_get_contents($url);
if ($content !== false) {
// 檢查是否為 GZIP 內容
if (isGzippedByMagic($content)) {
$content = gzdecode($content); // PHP 5.4+
}
return convertEncoding($content, $targetEncoding);
}
}
throw new Exception("無可用 HTTP 客戶端: allow_url_fopen=Off 且無 cURL");
}
function convertEncoding($content, $targetEncoding) {
$detected = mb_detect_encoding($content, ['UTF-8', 'GBK', 'BIG5', 'ISO-8859-1'], true);
if ($detected && $detected !== $targetEncoding) {
return iconv($detected, $targetEncoding . '//IGNORE', $content);
}
return $content;
}
function isGzippedByMagic($content) {
return strlen($content) >= 2
&& ord($content[0]) === 0x1f
&& ord($content[1]) === 0x8b;
}
<?php
// 完整流程:獲取 → 解壓 → 編碼偵測 → 轉換 → 輸出
$url = 'https://example.com/page';
try {
$html = smartFetch($url);
// 強制宣告輸出編碼
header('Content-Type: text/html; charset=UTF-8');
echo $html;
} catch (Exception $e) {
header('Content-Type: text/plain; charset=UTF-8');
echo '錯誤: ' . $e->getMessage();
}
| 格式 | 魔數 (Hex) | 魔數 (Decimal) | Content-Encoding |
|---|---|---|---|
| GZIP | 1F 8B | 31 139 | gzip |
| ZLIB (deflate) | 78 9C / 78 DA / 78 01 | 120 156 / 120 218 / 120 1 | deflate |
| Brotli | CE B2 / CF B2 | 206 178 / 207 178 | br |
| ZSTD | 28 B5 2F FD | 40 181 47 253 | zstd |
CURLOPT_ENCODING => '' 會發送 Accept-Encoding: gzip, deflate, br (視 cURL 版本支援)Content-Encoding: gzip 但內容未壓縮,cURL 會自動處理不報錯gzdecode() 需 PHP 5.4+,舊版用 gzinflate(substr($data, 10, -8))