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請求要少。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章