最近幾天在做一個多搜索引擎關鍵字排名查詢工具,用於及時方便的了解關鍵詞在各大搜索引擎的排名。
在抓取360搜索的時候,發現360搜索每頁只支持顯示10個搜索結果,如果想獲取100個搜索結果數據,就得搜索10次,十分影響用戶體驗,沒有人會為了查詢一次關鍵字排名而願意等待打開10次的網頁時間。
這時我想到了用多線程做並發抓取,正好php curl的curl_multi系列函數能實現此功能。
一、curl_multi系列的函數介紹:
1. curl_multi_init:
用來初始化一個"curl_multi"句柄,然后將多個"curl_init"函數生成的"curl"句柄傳遞給"curl_multi"句柄;此函數不需要然后參數。
2. curl_multi_add_handle:
"curl_multi_add_handle"函數用來將"curl_init"生成的"curl"句柄添加到上面"curl_multi_init"函數生成的"curl_multi"句柄。"curl_multi_add_handle"函數的第一個參數為"curl_multi"句柄,第二個參數為"curl"句柄。
3. curl_multi_exec:
"curl_multi_exec"用於發起curl_multi請求。"curl_multi_add_handle"函數的第一個參數為"curl_multi"句柄,第二個參數是一個"引用參數",它記錄未處理完成的請求數,當第二個參數值變為0時,代表所有的請求都已經處理完成(所有請求成功返回或者到達超時時間)。
4. curl_multi_info_read:
"curl_multi_info_read"函數用來讀取curl_multi句柄是否有curl返回信息,如果有則返回最先的"curl返回值(數組形式)",否則返回"false",循環調用此函數,直到其返回"false";"curl_multi_info_read"的參數為"curl_mulit"句柄。
5. curl_multi_getcontent:
當所有curl句柄處理完成,這時我們就可以使用"curl_multi_getcontent"函數讀取"curl"的返回內容了。"curl_multi_getcontent"的參數為"curl"句柄。
6. curl_multi_remove_handle:
讀取完內容后,使用"curl_multi_remove_handle"函數從"curl_mulit"句柄中移出所有"curl"句柄。"curl_multi_remove_handle"函數的第一個參數為"curl_multi"句柄,第二個參數為"curl"句柄。
7. curl_multi_close:
"curl_multi_close"函數用於關閉"curl_mulit"句柄,釋放占用的資源。"curl_multi_close"的參數為"curl_mulit"句柄。
二、curl_multi使用流程:
1、 "curl_multi_init"初始化"curl_multi"句柄;
2、 循環創建並添加"curl"句柄,並用"curl_multi_add_handle"函數將其添加到"curl_multi"句柄;
3、 使用"curl_multi_exec"發起請求,並等待所有請求處理完成;
4、 使用"curl_multi_info_read"函數讀取返回值;
5、 使用"curl_multi_getcontent"函數讀取返回內容;
6、 使用"curl_multi_remove_handle"函數移除curl句柄;
7、 使用"curl_multi_close"關閉curl_multi句柄。
三、下面是我使用curl_multi多線程並發抓取360搜索返回結果的代碼片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#多線程並發抓取函數mfetch:
function mfetch($params=array(), $method){
$mh = curl_multi_init(); #初始化一個curl_multi句柄
$handles = array();
foreach($params as $key=>$param){
$ch = curl_init(); #初始化一個curl句柄
$url = $param["url"];
$data = $param["params"];
if(strtolower($method)==="get"){
#根據method參數判斷是post還是get方式提交數據
$url = "$url?" . http_build_query( $data ); #get方式
}else{
curl_setopt( $ch, CURLOPT_POSTFIELDS, $data ); #post方式
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]);
curl_multi_add_handle($mh, $ch);
$handles[$ch] = $key;
#handles數組用來記錄curl句柄對應的key,供后面使用,以保證返回的數據不亂序。
}
$running = null;
$curls = array(); #curl數組用來記錄各個curl句柄的返回值
do { #發起curl請求,並循環等等1/100秒,直到引用參數"$running"為0
usleep(10000);
curl_multi_exec($mh, $running);
while( ( $ret = curl_multi_info_read( $mh ) ) !== false ){
#循環讀取curl返回,並根據其句柄對應的key一起記錄到$curls數組中,保證返回的數據不亂序
$curls[$handles[$ret["handle"]]] = $ret;
}
} while ( $running > 0 );
foreach($curls as $key=>&$val){
$val["content"] = curl_multi_getcontent($val["handle"]);
curl_multi_remove_handle($mh, $val["handle"]); #移除curl句柄
}
curl_multi_close($mh); #關閉curl_multi句柄
ksort($curls);
return $curls;
}
#調用參數:
$keword = "360";
$page = 1;
$params = array();
for($i=0;$i<10;$i++){
$params[$i] = array(
"url"=>"http://www.so.com/s",
"params"=>array('q'=>$keyword,'ie'=>"utf-8",'pn'=>($page-1)*10+$i+1)
);
}
$ret = mfetch($params, 'GET');
print_r($ret);
|
常見錯誤:
1、調用curl_multi系列函數時出現如下錯誤:
1
|
Fatal error: Call to undefined function curl_multi_init() in 文件.php on line 行號
|
解答:1、需要php5版本才支持curl_multi系列函數;2.需要開啟php curl擴展,windows: 在php.ini中把";extension=php_curl.dll"前面的分號去掉; linux:確認/etc/php.ini或者"/etc/php.d/curl.ini"中有類似"extension=curl.so"的行,並重啟apache;
2、調用curl系列函數時出現如下錯誤:
1
|
Call to undefined function curl_init() in 文件.php on line 行號
|
解答:同上。
3、用curl抓取網頁時,發現網頁的中文是亂碼:
解答:這是由於被抓取的網頁編碼與您的網頁編碼不一致引起的,設置"curl_setopt($ch, CURLOPT_HEADER, 1);"可以返回http頭,然后從中查看到類似"Content-Type: text/html; charset=gb2312"的行,從中可以看出其編碼為"gb2312"在使用$html = iconv("gb2312", "utf-8//ignore", $html); 將編碼轉為一致即可。