首页 > PHP > PHP批量请求api并缓存到本地服务器

PHP批量请求api并缓存到本地服务器

前段时间从新浪的接口获取数据一直采用单线程请求《读:我用爬虫一天时间“偷了”知乎一百万用户,只为证明PHP是世界上最好的语言》

量比较小的时候速度还不错。当请求量增大之后开始考虑使用curl_multi_init获取数据,于是参照RollingCurl.php写了这样一个多线程的函数。注意:post批量请求注意参数和返回值。

<?php
/** 
 * 批量多线程发送http/https请求,支持post批量请求
 * @param $url_arr 请求地址一维数组
 * @param $postFields post参数二维数组
 * @param $http_headers 附加头部参数二维数组
 * @param $cache_dir 本地缓存路径
 * @param $cache_time 本地缓存过期时间
 * @return array 关联数组,其键code为http状态码,键data为请求返回值
 */
function get_multi_api_contents($url_arr, $postFields = array(), $http_headers = array(), $cache_dir = '', $cache_time = 0)
{
    $responses = $lost_urls = $map = array();
    //仅当设置了$cache_time时才从缓存中读取
    foreach($url_arr as $key => $url)
    {
        if($cache_time)
        {
            if(empty($postFields))
            {
                $cache_path = $cache_dir.'/'.md5($url);
            }else{
                $cache_path = $cache_dir.'/'.md5($url.serialize($postFields));
            }
            if(file_exists($cache_path) &&  filemtime($cache_path) + $cache_time > time() && filesize($cache_path) > 0)
            {
                //如果需要区分从本地缓存和从网络请求可以设置为304或其他http_code
                $responses[$url] = array('code'=>200, 'data'=>file_get_contents($cache_path));
            }else{
                //缓存失效的url缓存
                $lost_urls[$key] = array('url' => $url, 'cache' => $cache_path);
            }
        }else{
            $lost_urls[$key] = array('url' => $url, 'cache' => null);
        }
    }
    
    $queue = curl_multi_init();
    foreach($lost_urls as $key => $info)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $info['url']);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        if(isset($postFields[$key]))
        {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields[$key]);
        }
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_NOSIGNAL, true);
        curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
        //和设置单个curl请求方式一样,例如
        //$default_browser = '';
        //curl_setopt($ch, CURLOPT_USERAGENT, $default_browser);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
                curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
        //https 请求
        if(strtolower(substr($info['url'], 0, 5)) == 'https')
        {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }
        if(isset($http_headers[$key]))
        {
            $headers = array();
            foreach($http_headers[$key] as $k => $v)
            {
                $headers[] = "$k: $v";
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }
        curl_multi_add_handle($queue, $ch);
        $map[(string) $ch] = $info['url'];
    }
    
    do {
        while(($code = curl_multi_exec($queue, $active)) == CURLM_CALL_MULTI_PERFORM);
        if($code != CURLM_OK)
        {
            break;
        }
        while($done = curl_multi_info_read($queue))
        {
            $getinfo = curl_getinfo($done['handle']);
            $data = curl_multi_getcontent($done['handle']);
            $responses[$map[(string) $done['handle']]] = array('code'=>$getinfo['http_code'], 'data'=>$data);
            curl_multi_remove_handle($queue, $done['handle']);
            curl_close($done['handle']);
        }
        if($active > 0)
        {
            curl_multi_select($queue, 0.5);
        }
    }while($active);
    curl_multi_close($queue);
    
    //更新缓存
    if($cache_time && !empty($lost_urls))
    {
        //填补丢失的缓存
        foreach($lost_urls as $info)
        {
            //仅当http_code为200时写入本地缓存
            if($responses[$info['url']]['code'] == 200)
            {
                file_put_contents($info['cache'], $responses[$info['url']]['data']);
            }
        }
    }
    return $responses;
}

测试案例:

//api链接地址数组,支持https请求,使用post请求相同url时请添加无意义参数,例如?r=1
$url_arr = array(
    0 => 'https://img.alicdn.com/tps/TB1sXGYIFXXXXc5XpXXXXXXXXXX.jpg?r=1',
    1 => 'https://img.alicdn.com/tps/TB1pfG4IFXXXXc6XXXXXXXXXXXX.jpg?r=2',
    2 => 'https://img.alicdn.com/tps/TB1h9xxIFXXXXbKXXXXXXXXXXXX.jpg?r=3',
);
//使用post参数时按照$url_arr对应键名提供
$postFields = array();
//使用自定义http头部的时候按照$url_arr对应键名提供
$http_headers = array();
//api请求缓存路径
$cache_dir = __DIR__ .'/cache';
//api请求结果在本地缓存时间,使用post方式请求时需考虑是否设置缓存时间(默认支持post缓存)
$cache_time = 3600;

$results = get_multi_api_contents($url_arr, $postFields, $http_headers, $cache_dir, $cache_time);

主要是为了其他的多线程代码(非curl多并发)中重复请求相同的url时优先从缓存中读取,减小访问新浪接口次数。

考虑到并发不能太高,获得请求结果之后可以从code中判断http响应值是否为200再决定是否重新请求,而且缓存之间相互独立。不过,该方法的性能肯定没有网上的传的《Curl多线程 | X》那么强大,那篇文章给出的curl类请求号称是充分利用CPU和带宽,且支持回调函数,以后需要的时候肯定能派上用场。



本文地址:http://blog.zhengshuiguang.com/php/curl_multi.html

转载随意,但请附上文章地址:-)

标签:采集 http请求 curl并发 curl多线程

相关文章

评论已关闭