實際項目中需下載大量圖片/頁面,單線程 foreach + curl_exec 或 file_get_contents 效率極低。本文提供封裝好的 remote() 函數,實測抓取 10 頁面:**curl_multi 4.5 秒 vs file_get_contents 33 秒,效率提升 7.3 倍**。
<?php
/**
* 並發抓取多個 URL
* @param array $urls 目標 URL 數組
* @return array|false 成功返回 [url => content],失敗返回 false
*/
function remote(array $urls)
{
if (empty($urls)) return false;
$curl = $text = [];
$mh = curl_multi_init();
foreach ($urls as $k => $v) {
// URL 中文編碼處理(僅路徑部分)
$nurl = preg_replace_callback('~([^:/\\.]+)~', function ($m) {
return rawurlencode($m[1]);
}, $v);
$curl[$k] = curl_init($nurl);
curl_setopt($curl[$k], CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl[$k], CURLOPT_HEADER, 0);
curl_setopt($curl[$k], CURLOPT_TIMEOUT, 15); // 總超時
curl_setopt($curl[$k], CURLOPT_CONNECTTIMEOUT, 5); // 連接超時
curl_multi_add_handle($mh, $curl[$k]);
}
$active = null;
// 驅動階段
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
// 事件循環階段
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
// 收集結果
foreach ($curl as $k => $ch) {
if (curl_error($ch) === '') {
$text[$k] = (string)curl_multi_getcontent($ch);
} else {
$text[$k] = ['error' => curl_error($ch), 'code' => curl_errno($ch)];
}
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $text;
}
// 測試
$text = remote(['https://www.jb51.net/', 'https://www.baidu.com/']);
print_r($text);
?>
preg_replace_callback 僅編碼路徑段非保留字符,保留協議、域名、端口。CURLOPT_TIMEOUT + CURLOPT_CONNECTTIMEOUT 防止單個慢請求阻塞整批。curl_close 釋放句柄資源。| 方式 | 耗時(秒) | 倍數 |
|---|---|---|
| curl_multi (並發) | 4.52 | 1x (基準) |
| file_get_contents (串行) | 33.00 | 7.3x 慢 |
| curl 串行 foreach | ~30+ | 接近 file_get_contents |