file_get_contents 直接讀取 GZIP 壓縮回應會得到亂碼。本文提供三種解決方案:zlib 串流封裝、cURL 自動解壓、手動解壓判斷。
伺服器回應 Content-Encoding: gzip,file_get_contents 返回壓縮後的二進位資料,直接輸出即為亂碼。
<?php
$url = 'https://example.com/page';
// 關鍵:加上 compress.zlib:// 前綴
$content = file_get_contents('compress.zlib://' . $url);
// 無論是否壓縮都能正常工作
echo $content;
✅ 優點:零配置、自動偵測、相容非壓縮回應
⚠️ 需求:PHP 編譯時啟用 --with-zlib (預設啟用)
function curlGet($url, $gzip = true) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Client)',
]);
// 關鍵:接受所有支援的編碼 (gzip, deflate, br 等)
if ($gzip) {
curl_setopt($ch, CURLOPT_ENCODING, '');
}
$content = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno) {
throw new Exception("cURL 錯誤 [$errno]: " . curl_error($ch));
}
return $content;
}
// 使用
$html = curlGet('https://example.com');
function fetchWithGzipHandling($url) {
// 1. 先用 HEAD 請求檢查 Content-Encoding
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
]);
$headResponse = curl_exec($ch);
curl_close($ch);
$isGzipped = stripos($headResponse, 'Content-Encoding: gzip') !== false;
// 2. 正式請求
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => $isGzipped ? 'gzip' : 'identity',
]);
$content = curl_exec($ch);
curl_close($ch);
return $content;
}
// 或:讀取前 2 bytes 判斷 GZIP 魔數 (0x1f8b)
function isGzippedContent($content) {
return strlen($content) >= 2
&& ord($content[0]) === 0x1f
&& ord($content[1]) === 0x8b;
}
function autoDecompress($content) {
if (isGzippedContent($content)) {
return gzdecode($content); // PHP 5.4+ 內建
// 舊版:return gzinflate(substr($content, 10, -8));
}
return $content;
}
<?php
function fetchPage($url) {
// 1. 獲取內容 (自動解壓)
$content = file_get_contents('compress.zlib://' . $url);
// 2. 編碼偵測與轉換
$encoding = mb_detect_encoding($content, ['UTF-8', 'GBK', 'BIG5', 'ISO-8859-1'], true);
if ($encoding && $encoding !== 'UTF-8') {
$content = iconv($encoding, 'UTF-8//IGNORE', $content);
}
// 3. 強制宣告 UTF-8 (防止瀏覽器誤判)
header('Content-Type: text/html; charset=UTF-8');
return $content;
}
// 使用
echo fetchPage('https://example.com');
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| compress.zlib:// | 簡單 GET、允許 url_fopen | 最簡潔、自動偵測 | 無法控制逾時、標頭、Cookie |
| cURL + ENCODING | 生產環境、複雜需求 | 功能完整、支援 brotli (br) | 需 cURL 擴充 |
| 手動 gzdecode | 已獲取壓縮資料、特殊處理 | 完全控制 | 需自行偵測 |
| 現象 | 可能原因 | 解決 |
|---|---|---|
| (鑽石問號) | UTF-8 被當作 ISO-8859-1 輸出 | header('Content-Type: ... charset=UTF-8') |
| 亂碼 (中文變亂碼) | GBK/Big5 未轉 UTF-8 | iconv('GBK', 'UTF-8//IGNORE', $str) |
| 空白/截斷 | GZIP 未解壓 | compress.zlib:// 或 CURLOPT_ENCODING |
| (單一替換字元) | iconv 遇到非法字元 | 加 //IGNORE 或 //TRANSLIT |