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

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
curl和curl_multi都是用于进行网络请求的工具。 curl是一个命令行工具,可以通过发送HTTP请求来获取网页内容或发送其他类型的网络请求。它是单线程的,在发送一个请求时会阻塞程序的执行直到请求完成并返回结果。这意味着如果要进行高并发的请求,需要启动多个curl进程来同时发送多个请求,但会造成系统资源的浪费。 而curl_multi是一个C语言库,可以实现多个网络请求的并发执行。它通过将多个curl实例放入一个集合中,并使用事件循环来处理多个请求的同时执行。这样,在发送一个请求时,程序不会阻塞,而是可以继续执行其他任务,提高了并发处理能力。当所有的请求都完成时,可以一次性获取所有的结果。这种方式减少了系统资源的浪费,提高了程序的效率。 使用curl_multi进行高并发需要注意以下几点: 1. 创建curl_multi实例,并向其中添加需要执行的curl请求。 2. 使用curl_multi_exec函数来开始执行多个请求。 3. 使用curl_multi_select函数等待请求完成。 4. 使用curl_multi_getcontent函数获取每个请求的结果。 需要注意的是,高并发的网络请求对服务器压力较大,也容易出现网络超时等问题,因此需要合理控制并发请求数量,使用适当的技术手段,如连接池、负载均衡等,来保证系统的稳定性和性能。 总之,curl和curl_multi都可以用于高并发的网络请求,但curl_multi具有更好的并发性能和资源利用率,适合在程序中进行大量请求的同时执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值