PHP使用curl_multi_exec多線程抓取數據
使用curl_multi_exec併發請求外部接口
有時候在一個PHP方法中要多次調用外部的接口,爲了優化代碼,提高效率,我們不妨使用curl_multi_exec併發處理多個請求,這樣可以明顯地提高獲取響應數據的速度,減少程序執行的時間,下面是一個實際運行的例子。
源碼 curl_multi.php文件
<?php
/**
* 使用curl並行發送多個請求獲取數據
* @param array $urls 多個請求數組
* @return array
*/
function sendMultiRequest(array $urls)
{
$conn = [];
$res = [];
//創建批處理curl句柄
$mh = curl_multi_init();
foreach ($urls as $k => $item) {
$conn[$k] = curl_init(); //初始化各個子連接
//設置url和相應的選項
curl_setopt($conn[$k], CURLOPT_URL, $item['url']);
curl_setopt($conn[$k], CURLOPT_HEADER, 0);
curl_setopt($conn[$k], CURLOPT_RETURNTRANSFER, 1); //不直接輸出到瀏覽器,而是返回字符串
curl_setopt($conn[$k], CURLOPT_TIMEOUT, 10);
if ($item['method'] == 'post') {
curl_setopt( $conn[$k], CURLOPT_POST, true );
$params = $item['params'];
if (is_array($item['params'])) {
$flag = true;
foreach ($params as $key => $val) {
if (strpos($val, '@') === 0) {
$flag = false;
break;
}
}
if ($flag) {
$params = http_build_query($params);
}
}
curl_setopt($conn[$k], CURLOPT_POSTFIELDS, $params);
}
//處理302跳轉
curl_setopt($conn[$k], CURLOPT_FOLLOWLOCATION, 1);
//增加句柄
curl_multi_add_handle($mh, $conn[$k]); //加入多處理句柄
}
$active = null; //連接數
//防卡死寫法:執行批處理句柄
do {
$mrc = curl_multi_exec($mh, $active);
//這個循環的目的是儘可能地讀寫,直到無法繼續讀寫爲止
//返回 CURLM_CALL_MULTI_PERFORM 表示還能繼續向網絡讀寫
} while($mrc == CURLM_CALL_MULTI_PERFORM);
// var_dump($mrc);
// echo '<hr/>';
// var_dump($active);
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 ($urls as $k => $url) {
$info = curl_multi_info_read($mh);
// var_dump($info);
$headers = curl_getinfo($conn[$k]);
// var_dump($headers);
$res[$k] = curl_multi_getcontent($conn[$k]);
//移除curl批處理句柄資源中的某一個句柄資源
curl_multi_remove_handle($mh, $conn[$k]);
//關閉curl會話
curl_close($conn[$k]);
}
//關閉全部句柄
curl_multi_close($mh);
return $res;
}
/**
* curl get請求
* @param string $sUrl 請求url地址
* @return 響應內容
*/
function sendGetRequest($sUrl)
{
$oCurl = curl_init();
// 設置請求頭, 有時候需要,有時候不用,看請求網址是否有對應的要求
$header[] = "Content-type: application/x-www-form-urlencoded";
$user_agent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";
curl_setopt($oCurl, CURLOPT_URL, $sUrl);
curl_setopt($oCurl, CURLOPT_HTTPHEADER, $header);
// 返回 response_header, 該選項非常重要,如果不爲 true, 只會獲得響應的正文
curl_setopt($oCurl, CURLOPT_HEADER, false);
// 是否不需要響應的正文,爲了節省帶寬及時間,在只需要響應頭的情況下可以不要正文
curl_setopt($oCurl, CURLOPT_NOBODY, false);
// 使用上面定義的 ua
curl_setopt($oCurl, CURLOPT_USERAGENT, $user_agent);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
// 不用 POST 方式請求, 意思就是通過 GET 請求
curl_setopt($oCurl, CURLOPT_POST, false);
$sContent = curl_exec($oCurl);
curl_close($oCurl);
return $sContent;
}
function sendPostRequest($sUrl, $params)
{
$oCurl = curl_init();
// 設置請求頭, 有時候需要,有時候不用,看請求網址是否有對應的要求
$header[] = "Content-type: application/x-www-form-urlencoded";
$user_agent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";
curl_setopt($oCurl, CURLOPT_URL, $sUrl);
curl_setopt($oCurl, CURLOPT_HTTPHEADER, $header);
// 返回 response_header, 該選項非常重要,如果不爲 true, 只會獲得響應的正文
curl_setopt($oCurl, CURLOPT_HEADER, false);
// 是否不需要響應的正文,爲了節省帶寬及時間,在只需要響應頭的情況下可以不要正文
curl_setopt($oCurl, CURLOPT_NOBODY, false);
// 使用上面定義的 ua
curl_setopt($oCurl, CURLOPT_USERAGENT, $user_agent);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
//用 POST 方式請求
curl_setopt($oCurl, CURLOPT_POST, true);
if (is_array($params)) {
$flag = true;
foreach ($params as $key => $val) {
if (strpos($val, '@') === 0) {
$flag = false;
break;
}
}
if ($flag) {
$params = http_build_query($params);
}
}
curl_setopt($oCurl, CURLOPT_POSTFIELDS, $params);
$sContent = curl_exec($oCurl);
curl_close($oCurl);
return $sContent;
}
$params = ['orderid'=>24875, 'sign_timestamp'=>'1567478095',
'sign'=>'06c2bf1422db1875e043ba3163e9c377'];
$urls = ['baidu' => ['url'=>'https://www.baidu.com/', 'method'=>'get', 'params'=>[]],
'sina'=>['url'=>'https://www.sina.com.cn/', 'method'=>'get', 'params'=>[]],
'tencent'=>['url'=>'https://www.qq.com/', 'method'=>'get', 'params'=>[]],
'jctest'=>['url'=>'http://local.ceshi.com/api/enquiryStep/updateEnquiryStatus', 'method'=>'post', 'params'=>$params]];
$begin = microtime(true);
$res = sendMultiRequest($urls);
echo "use curl_multi_exec(), time interval:".(microtime(true)-$begin)."s \n";
//檢測獲取到的內容的編碼格式
$encoding = mb_detect_encoding($res['tencent'], array("ASCII","GB2312","GBK",'BIG5', "UTF-8"));
//強制轉換爲utf-8格式
$res['tencent'] = iconv($encoding, "UTF-8//TRANSLIT",$res['tencent']);
//echo $res['tencent'];
// echo '<hr/>';
$begin = microtime(true);
$res = [];
foreach($urls as $k => $item) {
if ($item['method'] == 'get') {
$res[$k] = sendGetRequest($item['url']);
} elseif ($item['method'] == 'post') {
$res[$k] = sendPostRequest($item['url'], $item['params']);
}
}
echo "use multi curl_exec(), time interval:".(microtime(true)-$begin)."s \n";
// var_dump($res['jctest']);
// echo $res['tencent'];
測試結果
use curl_multi_exec(), time interval:0.13784718513489s
use multi curl_exec(), time interval:0.31491589546204s
經過測試發現,如果多個請求比較費時的話,使用併發請求多個url是以最後獲取到響應的那個請求的時間爲準的,所以耗時也比串行執行多個curl請求要少。