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
转载随意,但请附上文章地址:-)
评论已关闭