實際業務中常需模擬瀏覽器抓取頁面、繞過簡單防盜鏈、提交表單。本文提供生產可用的 cURL 封裝模板,涵蓋偽造 IP/Referer、POST 數據、超時、重定向、編碼處理與錯誤診斷。
fetchUrl()<?php
/**
* 通用 cURL 抓取函數
*
* @param string $url 目標 URL
* @param array $postData POST 數據,null 為 GET
* @param array $headers 額外請求頭
* @param int $timeout 總超時(秒)
* @param int $connectTimeout 連接超時(秒)
* @param bool $followLocation 是否跟隨重定向
* @param string $cookieFile Cookie 文件路徑(保持會話)
* @param string $proxy 代理 'host:port'
* @param string $userAgent 自定義 UA
* @return array ['code'=>http_code, 'body'=>string, 'headers'=>array, 'error'=>string|null, 'info'=>array]
*/
function fetchUrl(string $url, array $postData = null, array $headers = [], int $timeout = 30, int $connectTimeout = 10, bool $followLocation = true, string $cookieFile = '', string $proxy = '', string $userAgent = ''): array {
$ch = curl_init();
$defaultHeaders = [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control: no-cache',
'Pragma: no-cache',
];
if ($userAgent) $defaultHeaders[] = 'User-Agent: ' . $userAgent;
// 偽造 IP(部分防火牆檢查 X-Forwarded-For / Client-IP)
$defaultHeaders[] = 'X-Forwarded-For: ' . (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '127.0.0.1');
$defaultHeaders[] = 'Client-IP: ' . (isset($_SERVER['HTTP_CLIENT_IP']) ? $_SERVER['HTTP_CLIENT_IP'] : '127.0.0.1');
$opts = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true, // 包含響應頭,稍後分離
CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers),
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $connectTimeout,
CURLOPT_FOLLOWLOCATION => $followLocation,
CURLOPT_MAXREDIRS => 10,
CURLOPT_ENCODING => 'gzip, deflate', // 自動解壓
CURLOPT_SSL_VERIFYPEER => true, // 生產必開
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => $userAgent ?: 'Mozilla/5.0 (compatible; PHP-cURL/'.PHP_VERSION.')',
];
if ($postData !== null) {
$opts[CURLOPT_POST] = true;
$opts[CURLOPT_POSTFIELDS] = is_array($postData) ? http_build_query($postData) : $postData;
}
if ($cookieFile) {
$opts[CURLOPT_COOKIEJAR] = $cookieFile;
$opts[CURLOPT_COOKIEFILE] = $cookieFile;
}
if ($proxy) {
$opts[CURLOPT_PROXY] = $proxy;
}
// IPv4 強制(解決 IPv6 環境超時)
if (defined('CURLOPT_IPRESOLVE') && defined('CURL_IPRESOLVE_V4')) {
$opts[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
}
curl_setopt_array($ch, $opts);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = $errno ? curl_error($ch) : null;
$info = curl_getinfo($ch);
curl_close($ch);
// 分離頭與體
$headerSize = $info['header_size'];
$respHeaders = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
// 解析響應頭為關聯數組
$headersArr = [];
foreach (explode("\r\n", $respHeaders) as $line) {
if (strpos($line, ':') !== false) {
[$k, $v] = explode(':', $line, 2);
$headersArr[trim($k)] = trim($v);
}
}
return [
'code' => $info['http_code'],
'body' => $body,
'headers' => $headersArr,
'error' => $error,
'info' => $info,
];
}
<?php
require 'fetchUrl.php';
// 1. 簡單 GET
$res = fetchUrl('https://httpbin.org/get');
echo $res['code'] === 200 ? 'OK: ' . strlen($res['body']) : 'ERR: ' . $res['error'];
// 2. POST 表單 + 自定義 Referer
$res = fetchUrl('https://httpbin.org/post', ['username' => 'test', 'pwd' => '123'], [
'Referer: https://example.com/login',
]);
// 3. 搶 Cookie 會話(登錄後訪問受保護頁)
$cookieFile = sys_get_temp_dir() . '/curl_cookie_' . md5('session') . '.txt';
$login = fetchUrl('https://site.com/login', ['user'=>'a','pass'=>'b'], [], 30, 10, true, $cookieFile);
if ($login['code'] === 200) {
$profile = fetchUrl('https://site.com/profile', null, [], 30, 10, true, $cookieFile);
// $profile['body'] 包含登錄後內容
}
// 4. 代理抓取
$res = fetchUrl('https://httpbin.org/ip', null, [], 30, 10, true, '', '127.0.0.1:7890');
// 5. 強制 IPv4(雲服務器常見 IPv6 連不上目標)
$res = fetchUrl('https://api.ipify.org?format=json'); // 內部已啟用 CURLOPT_IPRESOLVE_V4
| 參數 | 作用 | 建議值 |
|---|---|---|
CURLOPT_HEADER => true | 獲取響應頭(狀態碼、Set-Cookie、Content-Type) | true |
CURLOPT_ENCODING | 自動處理 gzip/deflate 壓縮響應 | 'gzip, deflate' |
CURLOPT_IPRESOLVE_V4 | 強制 IPv4,避免 IPv6 環境超時 | PHP 5.3+ / cURL 7.10.8+ |
CURLOPT_COOKIEJAR/FILE | 持久化 Cookie,支援跨請求會話 | 臨時文件路徑 |
CURLOPT_FOLLOWLOCATION | 自動處理 301/302 重定向 | true(注意 open_basedir/safe_mode 限制) |
| curl_errno | 常見原因 | 排查方向 |
|---|---|---|
| 6 | DNS 解析失敗 | 檢查 /etc/resolv.conf、域名拼寫、內網 DNS |
| 7 | 無法連接主機 | IP/端口、防火牆、安全組、目標服務是否啟動 |
| 28 | 操作超時 | 調大 timeout、檢查網絡延遲、目標響應慢 |
| 35 | SSL 握手失敗 | 證書過期、CA 不信任、TLS 版本不匹配、SNI 缺失 |
| 51 | SSL 證書驗證失敗 | CA 文件缺失、自簽證書需 CURLOPT_CAINFO、或開發環境暫時關閉 VERIFYPEER |
| 60 | CA 證書問題 | 更新 ca-certificates、指定 CURLOPT_CAINFO |
curl_multi 簡介需同時抓取多個 URL 時,使用 curl_multi_* 族函數可顯著降低總耗時。參考 《解析 php 中 curl_multi 的應用》。
將 fetchUrl() 放入公共庫,統一超時、UA、IPv4、錯誤處理,能解決 90% 以上抓取場景,剩餘 10% 複雜登錄/JS 渲染請轉 Headless Chrome (Puppeteer/Playwright)。