PHP批量请求多批 api接口或站点 的实现
前言(场景)
因为最近做一个功能,需要我将Python爬取好的数据同步到企业的 飞书文档 中,但是飞书开发有一个非常鸡肋的地方就是。。不想多说请看下图
单位时间内10QPS(QPS (每秒查询率) : 每秒钟接受的请求或者查询的数量,在互联网领域,指每秒响应请求数(指HTTP请求))。单次最多接收500条记录。。。
这很显然远远限制了我 数万条记录的同步进程。如果每一次请求都等待 飞书 返回执行结果,那么每次同步就会有大把的时间浪费在无意义的等待之上。。所以有没有一种方式?我们把所有请求一次性派发过去。再集中等待每条请求的返回结果统一放在一个变量中等待后续处理呢(⊙o⊙)?现在她来了!OK,废话少说,直接上代码!如果有不足或者更多需要改进的地方希望可以加以留言改进。如果觉得对你有帮助希望可以不吝啬点个红心支持一下吧~
代码封装
function BatchCURL($urls,$postDatas,$headers='',$timeout=30,$EatUpTime=FALSE){ // 定制$urls组,$Datas组和$headers组数据(repeat=>次数,) $repeat = function($Rvel,&$Res,$keyVal=[]){ if (isset($Res['repeat'])) { if (!isset($Res[$Rvel])) return FALSE; if($keyVal){ foreach($keyVal as $key=>$val){ $Res[$key] = $Res[$Rvel]; } }else{ for ($i=0; $i < $Res['repeat']; $i++) { $Res[$i] = $Res[$Rvel]; } } unset($Res['repeat'],$Res[$Rvel]); }elseif($Rvel!='url' && $Rvel!='data' && $Rvel!='header'){ return FALSE; } return $Res; }; $timing = function($type,$start=0){ if ($type == 'OFF') { return 'Eat up time: '.round(microtime(true)-$start,2).' s'; }else{ return microtime(true); } }; $start = $EatUpTime?$timing('ON'):0; $retRes = []; $conn = []; // 仅对数组格式数据进行批量处理 if (is_array($urls)) { $urls = $repeat('url',$urls); $postDatas = $repeat('data',$postDatas,$urls); $headers = $repeat('header',$headers,$urls); }else{ return FALSE; } // var_dump($urls); // var_dump($postDatas); // var_dump($headers); // return false; // 创建批处理cURL句柄 $mh = curl_multi_init(); foreach ($urls as $key => $url) { // 统一数据数据提交 if (is_array($postDatas[$key]) || is_object($postDatas[$key])) { $postData = json_encode($postDatas[$key]); }elseif(is_string($postDatas[$key])){ $postData=$postDatas[$key]; }else{ $postData = http_build_query($postDatas[$key], '', '&'); } // 批量创建cURL资源 $conn[$key] = curl_init(); // 设置URL和相应的选项 curl_setopt($conn[$key], CURLOPT_URL, $url); // 要访问的地址 curl_setopt($conn[$key], CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 // https请求不验证证书和hosts curl_setopt($conn[$key], CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在 curl_setopt($conn[$key], CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟⽤户使⽤的浏览器 curl_setopt($conn[$key], CURLOPT_FOLLOWLOCATION, 1); // 使⽤⾃动跳转 curl_setopt($conn[$key], CURLOPT_AUTOREFERER, 1); // ⾃动设置Referer curl_setopt($conn[$key], CURLOPT_POST, true); // 发送⼀个常规的Post请求 curl_setopt($conn[$key], CURLOPT_POSTFIELDS, $postData); // Post提交的数据包 curl_setopt($conn[$key], CURLOPT_CONNECTTIMEOUT, $timeout); // 尝试连接时等待的秒数,设置超时限制防⽌死循环 curl_setopt($conn[$key], CURLOPT_TIMEOUT, $timeout); // 允许cURL函数执行的最大秒数 curl_setopt($conn[$key], CURLOPT_HEADER, 0); // 显⽰返回的Header区域内容 curl_setopt($conn[$key], CURLOPT_RETURNTRANSFER, true); // 获取的信息以⽂件流的形式返回 if ($headers) { curl_setopt($conn[$key], CURLOPT_HTTPHEADER, $headers[$key]); //模拟的header头 } // 追加句柄 curl_multi_add_handle($mh, $conn[$key]); } $active = null; // 执行批处理句柄(while条件防卡死) do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); 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 $key => $url) { // // 获取当前解析的cURL的相关传输信息 // $info = curl_multi_info_read($mh); // // 获取请求头信息 // $heards = curl_getinfo($conn[$key]); // 获取输出的文本流 $retRes[$key] = curl_multi_getcontent($conn[$key]); // 移除curl批处理句柄资源中的某个句柄资源 curl_multi_remove_handle($mh, $conn[$key]); // 关闭指定cURL会话 curl_close($conn[$key]); } //关闭所有句柄 curl_multi_close($mh); if ($EatUpTime) { $end = $timing('OFF',$start); $retRes['time'] = $end; } return $retRes; }
参数解读
$urls // 请求的路径(api接口) // type > array (接受一个数组) // 如果是多次请求同一个路径 则接受两个固定参数 url,repeat // 'url'=>需要请求的路径 // 'repeat'=>重复请求的次数 $postDatas // 请求携带的数据 // type > array (接受一个数组)(注意当每次请求的参数都不相同时,每个数组的键应该和urls键一一对应) // 如果请求每次都携带同一批数据 则接受两个固定参数 data,repeat // 'data'=>携带的参数 // 'repeat'=>批量复制的份数 $headers='' // 为每个请求定制请求头 // type > array (接受一个数组)(注意当每次请求的请求头都不相同时,每个数组的键应该和urls键一一对应) // 如果请求请求头相同 则接受两个固定参数 data,repeat // 'header'=>携带的请求头信息(array 索引数组) // 'repeat'=>批量复制的份数 $timeout=30 // 为每个请求定制超时等待 $EatUpTime=FALSE // 是否统计耗时
简单示例
// 接口路径 $url = 'https://open.feishu.cn/open-apis/bitable/v1/apps/Youtable/tables/tbloaiT5HqFEBk42/records/batch_create?user_id_type=open_id'; $timeout = 15; // 定制请求头 $user_access_token = "u-19YVum1rZezX5uSdFv4iZu417Bbwg1sFp0w0khI805kH"; $header = ['Content-Type:application/json; charset=utf-8','Authorization:Bearer '.$user_access_token]; // 请求Post数据 $data = [ "order_id"=>"72680", "order_number"=>"KA07WEG-1000000010000160", "billing_name"=>"Ashli Stacy", "email"=>"ashlistacy@yahoo.com", "billing_country"=>"US", "payment"=>"plugins2spay",'......' ]; // 请求次数 $number = 5; $datas=[ 'url'=>$data, 'repeat'=>$number ]; $datas=[ 'data'=>$data, 'repeat'=>$number ]; $headers=[ 'header'=>$header, 'repeat'=>$number ]; $Res = BatchCURL($urls,$datas,$headers,$timeout,true); // 如果请求不同的接口,或携带不同数据 // $urls=[ // 'yuque' => "https://www.yuque.com/mangofish", // 'cnblogs' => "https://www.cnblogs.com/mangofish", // 'w3c' => "http://www.w3cschool.cc/", // ];
// 键必须和需要请求的 url 键值对应 // $datas=[ // 'yuque' => PostData1, // 'cnblogs' => PostData1, // 'w3c' => PostData1, // ]; var_dump($Res);
其他POST请求方式
官方手册=》https://www.php.net/manual/zh/function.curl-multi-exec.php
参考=》https://www.cnblogs.com/mangofish/p/16465457.html
参考=》https://www.yuque.com/mangofish/tp1uue
function BatchCURL($urls,$postDatas,$headers='',$timeout=30,$EatUpTime=FALSE){
// 定制$urls组,$Datas组和$headers组数据(repeat=>次数,)
$repeat = function($Rvel,&$Res,$keyVal=[]){
if (isset($Res['repeat'])) {
if (!isset($Res[$Rvel])) return FALSE;
if($keyVal){
foreach($keyVal as $key=>$val){
$Res[$key] = $Res[$Rvel];
}
}else{
for ($i=0; $i < $Res['repeat']; $i++) {
$Res[$i] = $Res[$Rvel];
}
}
unset($Res['repeat'],$Res[$Rvel]);
}elseif($Rvel!='url' && $Rvel!='data' && $Rvel!='header'){
return FALSE;
}
return $Res;
};
$timing = function($type,$start=0){
if ($type == 'OFF') {
return 'Eat up time: '.round(microtime(true)-$start,2).' s';
}else{
return microtime(true);
}
};
$start = $EatUpTime?$timing('ON'):0;
$retRes = [];
$conn = [];
// 仅对数组格式数据进行批量处理
if (is_array($urls)) {
$urls = $repeat('url',$urls);
$postDatas = $repeat('data',$postDatas,$urls);
$headers = $repeat('header',$headers,$urls);
}else{
return FALSE;
}
// var_dump($urls);
// var_dump($postDatas);
// var_dump($headers);
// return false;
// 创建批处理cURL句柄
$mh = curl_multi_init();
foreach ($urls as $key => $url) {
// 统一数据数据提交
if (is_array($postDatas[$key]) || is_object($postDatas[$key])) {
$postData = json_encode($postDatas[$key]);
}elseif(is_string($postDatas[$key])){
$postData=$postDatas[$key];
}else{
$postData = http_build_query($postDatas[$key], '', '&');
}
// 批量创建cURL资源
$conn[$key] = curl_init();
// 设置URL和相应的选项
curl_setopt($conn[$key], CURLOPT_URL, $url); // 要访问的地址
curl_setopt($conn[$key], CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 // https请求不验证证书和hosts
curl_setopt($conn[$key], CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
curl_setopt($conn[$key], CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟⽤户使⽤的浏览器
curl_setopt($conn[$key], CURLOPT_FOLLOWLOCATION, 1); // 使⽤⾃动跳转
curl_setopt($conn[$key], CURLOPT_AUTOREFERER, 1); // ⾃动设置Referer
curl_setopt($conn[$key], CURLOPT_POST, true); // 发送⼀个常规的Post请求
curl_setopt($conn[$key], CURLOPT_POSTFIELDS, $postData); // Post提交的数据包
curl_setopt($conn[$key], CURLOPT_CONNECTTIMEOUT, $timeout); // 尝试连接时等待的秒数,设置超时限制防⽌死循环
curl_setopt($conn[$key], CURLOPT_TIMEOUT, $timeout); // 允许cURL函数执行的最大秒数
curl_setopt($conn[$key], CURLOPT_HEADER, 0); // 显⽰返回的Header区域内容
curl_setopt($conn[$key], CURLOPT_RETURNTRANSFER, true); // 获取的信息以⽂件流的形式返回
if ($headers) {
curl_setopt($conn[$key], CURLOPT_HTTPHEADER, $headers[$key]); //模拟的header头
}
// 追加句柄
curl_multi_add_handle($mh, $conn[$key]);
}
$active = null;
// 执行批处理句柄(while条件防卡死)
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
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 $key => $url) {
// // 获取当前解析的cURL的相关传输信息
// $info = curl_multi_info_read($mh);
// // 获取请求头信息
// $heards = curl_getinfo($conn[$key]);
// 获取输出的文本流
$retRes[$key] = curl_multi_getcontent($conn[$key]);
// 移除curl批处理句柄资源中的某个句柄资源
curl_multi_remove_handle($mh, $conn[$key]);
// 关闭指定cURL会话
curl_close($conn[$key]);
}
//关闭所有句柄
curl_multi_close($mh);
if ($EatUpTime) {
$end = $timing('OFF',$start);
$retRes['time'] = $end;
}
return $retRes;
}