這段時間比較忙,已經好久沒有寫博客了。今天我就來聊聊我關於curl_multi_*函數集的使用心得,關於http請求的問題。php
當咱們用戶php發起一個http請求的時候。咱們會首先想到用什麼?沒錯,咱們會建立curl來請求。當咱們在一次執行中須要發起多個http請求呢。這簡單,對每個URL發起一次url請求。請求玩第1個再請求第2個....這就完了?哪咱們還說個啥。git
官網連接:http://php.net/manual/zh/book.curl.phpgithub
咱們舉個栗子。如今有三個http請求。每一個請求耗時2s。若是按照簡單的curl請求(圖1-(1))。耗時6s.這是不能容忍的。若是請求的個數越多耗時約多。框架
有沒有一種方式來縮小查詢時間?能不能三個http請求同時執行(如圖1-(1))?有不少方法來解決這個問題,將耗時減小到2s。如:多進程、線程、事件循環、curl_multi_*等等。最簡單的方式就是經過curl_multi_*函數來完成。事實上curl_multi_*內部實現就是用的事件循環。curl
<?php /** * * curl_multi_*簡單運用 * * @author: rudy * @date: 2016/07/12 */ /** * 根據url,postData獲取curl請求對象,這個比較簡單,能夠看官方文檔 */ function getCurlObject($url,$postData=array(),$header=array()){ $options = array(); $url = trim($url); $options[CURLOPT_URL] = $url; $options[CURLOPT_TIMEOUT] = 10; $options[CURLOPT_USERAGENT] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36'; $options[CURLOPT_RETURNTRANSFER] = true; // $options[CURLOPT_PROXY] = '127.0.0.1:8888'; foreach($header as $key=>$value){ $options[$key] =$value; } if(!empty($postData) && is_array($postData)){ $options[CURLOPT_POST] = true; $options[CURLOPT_POSTFIELDS] = http_build_query($postData); } if(stripos($url,'https') === 0){ $options[CURLOPT_SSL_VERIFYPEER] = false; } $ch = curl_init(); curl_setopt_array($ch,$options); return $ch; } // 建立三個待請求的url對象 $chList = array(); $chList[] = getCurlObject('https://www.baidu.com'); $chList[] = getCurlObject('http://www.jd.com'); $chList[] = getCurlObject('http://www.jianshu.com/'); // 建立多請求執行對象 $downloader = curl_multi_init(); // 將三個待請求對象放入下載器中 foreach ($chList as $ch){ curl_multi_add_handle($downloader,$ch); } // 輪詢 do { while (($execrun = curl_multi_exec($downloader, $running)) == CURLM_CALL_MULTI_PERFORM) ; if ($execrun != CURLM_OK) { break; } // 一旦有一個請求完成,找出來,處理,由於curl底層是select,因此最大受限於1024 while ($done = curl_multi_info_read($downloader)) { // 從請求中獲取信息、內容、錯誤 $info = curl_getinfo($done['handle']); $output = curl_multi_getcontent($done['handle']); $error = curl_error($done['handle']); // 將請求結果保存,我這裏是打印出來 print $output; // print "一個請求下載完成!\n"; // 把請求已經完成了得 curl handle 刪除 curl_multi_remove_handle($downloader, $done['handle']); } // 當沒有數據的時候進行堵塞,把 CPU 使用權交出來,避免上面 do 死循環空跑數據致使 CPU 100% if ($running) { $rel = curl_multi_select($downloader, 1); if($rel == -1){ usleep(1000); } } if( $running == false){ break; } } while (true); // 下載完畢,關閉下載器 curl_multi_close($downloader); echo "全部請求下載完成!";
在該例子中,首先建立三個或多個要請求的url請求對象。經過curl_multi_*函數建立下載器。將請求寫入下載器中。最後輪詢。等待三個請求如今完成。作處理。ide
這就是curl_multi_*用法?too yong too simple!在上面的例子中。下載器$downloader中的請求是一開始就添加好了的。咱們能不能動態的向下載器中添加請求。動態的從下載器中取出已經完成了的請求。想一想。這是什麼?這不就是爬蟲的核心部分-動態下載器。如何動態的添加?咱們能夠用多進程經過IPC添加。咱們能夠經過協程經過隊列添加等待。函數
curl_multi_*函數實現的HTTP壓測工具:
https://github.com/hirudy/phplib/blob/master/phpAb.php。工具curl_multi_*實現的http請求類:post
https://github.com/hirudy/phplibui
我這實現了一個經過協程+curl_multi_*的爬蟲框架。
Tspider:https://github.com/hirudy/Tspider。單進程可處理請求2000-5000/min。