在用curl抓取網頁內容的時候,常常要知道,網頁返回的請求頭信息,和請求的相關信息,特別是在請求過程當中存在重定向的時候獲取請求返回頭信息對分析請求內容頗有幫助
下面就是一個請求中存在重定向的例子,咱們的目的是要獲取最終實際請求的url地址
php
[php] view plaincopyhtml
$url='http://www.appchina.com/market/r/489267/com.appshare.android.ilisten.vapk?c=aplus.direct&uid=gAJ9cQEu1TlyZxsXN-aB4RaanvFL6t6Bj-vj0rIBs&p=aplus.detail&m=redirect'; android
$ch=curl_init(); nginx
curl_setopt($ch, CURLOPT_URL, $url); apache
//curl_setopt($ch, CURLOPT_POST, 1); 數組
//curl_setopt($ch, CURLOPT_POSTFIELDS, $params); 緩存
curl_setopt($ch, CURLOPT_HEADER, 1);//返回response頭部信息 服務器
curl_setopt($ch, CURLOPT_NOBODY, 1);//不返回response body內容 cookie
//curl_setopt($ch, CURLOPT_MAXREDIRS, 1);//設置請求最多重定向的次數 網絡
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);//不直接輸出response
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);//若是返回的response 頭部中存在Location值,就會遞歸請求
$content=curl_exec($ch);
$rinfo=curl_getinfo($ch);
echo $content,"</br>";
echo "<hr>";
print_r($rinfo);
下面是輸出的結果
[plain] view plaincopy
HTTP/1.1 200 OKServer: nginxDate: Sat, 22 Dec 2012 06:17:44 GMTContent-Type: application/vnd.android.package-archiveConnection: closeLast-Modified: Mon, 03 Dec 2012 16:00:00 GMTExpires: Tue, 03 Dec 2013 16:00:00 GMTCache-Control: max-age=31536000Content-Length: 2142149
Array( [url] => <a href="http://www.d.appchina.com/McDonald/r/489267/com.appshare.android.ilisten.vapk?c=aplus.direct&uid=gAJ9cQEu1TlyZxsXN-aB4RaanvFL6t6Bj-vj0rIBs&p=aplus.detail&m=redirect" target="_blank">http://www.d.appchina.com/McDonald/r/489267/com.appshare.android.ilisten.vapk?c=aplus.direct&uid=gAJ9cQEu1TlyZxsXN-aB4RaanvFL6t6Bj-vj0rIBs&p=aplus.detail&m=redirect</a> [content_type] => application/vnd.android.package-archive [http_code] => 200 [header_size] => 289 [request_size] => 196 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 0 [total_time] => 0.171621 [namelookup_time] => 0.135256 [connect_time] => 0.152913 [pretransfer_time] => 0.152916 [size_upload] => 0 [size_download] => 0 [speed_download] => 0 [speed_upload] => 0 [download_content_length] => 2142149 [upload_content_length] => 0 [starttransfer_time] => 0.171582 [redirect_time] => 0 [certinfo] => Array ( ))
能夠看到,通過遞歸請求後最終獲得一個200的response,可是這中方式不能獲得最後一次請求的url,也就是最終實際請求的url,要想獲得這個url就須要遞歸的分析每次請求返回的response
下面是我寫的一個獲取最後一次請求url的遞歸函數
[php] view plaincopy
$url='http://www.appchina.com/market/r/489267/com.appshare.android.ilisten.vapk?c=aplus.direct&uid=gAJ9cQEu1TlyZxsXN-aB4RaanvFL6t6Bj-vj0rIBs&p=aplus.detail&m=redirect';
[php] view plaincopy
$realUrl=getRedirectLocation($url);
echo "</br>--->",$realUrl;
function getRedirectLocation($url){
$realUrl=$url;
echo $url,"</br>";
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);curl_setopt($ch, CURLOPT_TIMEOUT, 3);//設置curl執行時間不超過3秒
//curl_setopt($ch, CURLOPT_NOBODY, 1);//這行不能要,若是添上,那麼在遇到302重定向的時候就會得不到真正的請求url
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$content=curl_exec($ch);
//echo $content;
$rinfo=curl_getinfo($ch);
$matches=array();
if(preg_match('/Location:\s+?(.+?)\s+?/', $content,$matches)){
//echo $matches[1],"</br>";
unset($content);
$realUrl=getRedirectLocation($matches[1]);
}
if(isset($content)){
unset($content);
}
return $realUrl;
}
給各位介紹一下Curl多線程實例與原理。不對之處請指教
相信許多人對php手冊中語焉不詳的curl_multi一族的函數頭疼不已,它們文檔少,給的例子 更是簡單的讓你無從借鑑,我也曾經找了許多網頁,都沒見一個完整的應用例子。
curl_multi_add_handle
curl_multi_close
curl_multi_exec
curl_multi_getcontent
curl_multi_info_read
curl_multi_init
curl_multi_remove_handle
curl_multi_select
通常來講,想到要用這些函數時,目的顯然應該是要同時請求多個url,而不是一個一個依次請求,不然不如本身循環去調curl_exec好了。
步驟總結以下:
第一步:調用curl_multi_init
第二步:循環調用curl_multi_add_handle
這一步須要注意的是,curl_multi_add_handle的第二個參數是由curl_init而來的子handle。
第三步:持續調用curl_multi_exec
第四步:根據須要循環調用curl_multi_getcontent獲取結果
第五步:調用curl_multi_remove_handle,併爲每一個字handle調用curl_close
第六步:調用curl_multi_close
這裏有PHP手冊上的例子:
[php] view plaincopy
<?php
// 建立一對cURL資源
$ch1 = curl_init();
$ch2 = curl_init();
// 設置URL和相應的選項
curl_setopt($ch1, CURLOPT_URL, "http://www.jb51.net/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);
// 建立批處理cURL句柄
$mh = curl_multi_init();
// 增長2個句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);
$active = null;
// 執行批處理句柄
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);
}
}
// 關閉所有句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);
?>
整個使用過程差很少就是這樣,可是,這個簡單代碼有個致命弱點,就是在do循環的那段,在整個url請求期間是個死循環,它會輕易致使CPU佔用100%。
如今咱們來改進它,這裏要用到一個幾乎沒有任何文檔的函數curl_multi_select了,雖然C的curl庫對select有說明,可是,php裏的接口和用法確與C中有不一樣。
把上面do的那段改爲下面這樣:
[php] view plaincopy
do {
$mrc = curl_multi_exec($mh,$active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active and $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
由於$active要等所有url數據接受完畢才變成false,因此這裏用到了curl_multi_exec的返回值判斷是否還有數據,當有數據的時候就不停調用curl_multi_exec,暫時沒有數據就進入select階段,新數據一來就能夠被喚醒繼續執行。這裏的好處就是CPU的無謂消耗沒有了。
另外:還有一些細節的地方可能有時候要遇到:
控制每個請求的超時時間,在curl_multi_add_handle以前經過curl_setopt去作:
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
判斷是否超時了或者其餘錯誤,在curl_multi_getcontent以前用:curl_error($conn[$i]);
本類的特色:
運行很是穩定。
設置一個併發就會始終以這個併發數進行工做,即便經過回調函數添加任務也不影響。
CPU佔用極低,絕大部分CPU消耗在用戶的回調函數上。
內存利用率高,任務數量較多(15W個任務佔用內存會超過256M)可使用回調函數添加任務,個數自定。
可以最大限度的佔用帶寬。
鏈式任務,好比一個任務須要從多個不一樣的地址採集數據,能夠經過回調一鼓作氣。
可以對CURL錯誤進行屢次嘗試,次數自定(大併發一開始容易產生CURL錯誤,網絡情況或對方服務器穩定性也有可能產生CURL錯誤)。
回調函數至關靈活,能夠多種類型任務同時進行(好比下載文件,抓取網頁,分析404能夠在一個PHP進程中同時進行)。
能夠很是容易的定製任務類型,好比檢查404,獲取redirect的最後url等。
能夠設置緩存,挑戰產品節操。
不足:
不能充分利用多核CPU(能夠開多個進程解決,須要本身處理任務分割等邏輯)。
最大併發500(或512?),通過測試是CURL 內部限制,超過最大併發會致使老是返回失敗。
目前沒有斷點續傳功能。
目前任務是原子性的,不能對一個大文件分爲幾部分分別開線程下載。
[php] view plaincopy
/*-----保存COOKIE-----*/
$url = 'www.xxx.com'; //url地址
$post = "id=user&pwd=123456"; //POST數據
$ch = curl_init($url); //初始化
curl_setopt($ch,CURLOPT_HEADER,1); //將頭文件的信息做爲數據流輸出
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); //返回獲取的輸出文本流
curl_setopt($ch,CURLOPT_POSTFIELDS,$post); //發送POST數據
$content = curl_exec($ch); //執行curl並賦值給$content
preg_match('/Set-Cookie:(.*);/iU',$content,$str); //正則匹配
$cookie = $str[1]; //得到COOKIE(SESSIONID)
curl_close($ch); //關閉curl
/*-----使用COOKIE-----*/
curl_setopt($ch,CURLOPT_COOKIE,$cookie);
簡單的cURL處理以下:
[php] view plaincopy
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://www.phpddt.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$con = curl_exec($ch);
curl_close($ch);
cURL還提供了批量處理會話,下面是cURL批量處理相關函數:
curl_multi_init — 返回一個新cURL批處理句柄
curl_multi_add_handle — 向curl批處理會話中添加單獨的curl句柄
curl_multi_exec — 解析一個cURL批處理句柄
curl_multi_getcontent — 若是設置了CURLOPT_RETURNTRANSFER,則返回獲取的輸出的文本流
curl_multi_select — 等待全部cURL批處理中的活動鏈接
curl_multi_info_read — 獲取當前解析的cURL的相關傳輸信息
curl_multi_remove_handle — 移除curl批處理句柄資源中的某個句柄資源
curl_multi_close — 關閉一組cURL句柄
看下面使用curl multi批處理的例子:
[php] view plaincopy
<?php
/**
* cURL multi批量處理
*
* @author mckee
* @link <a href="http://www.phpddt.com" target="_blank">http://www.phpddt.com</a> *
*/
$url_array = array(
'http://www.phpddt.com/',
'http://www.phpddt.com/php/627.html',
'/content/312816.html'
);
$handles = $contents = array();
//初始化curl multi對象
$mh = curl_multi_init();
//添加curl 批處理會話
foreach($url_array as $key => $url)
{
$handles[$key] = curl_init($url);
curl_setopt($handles[$key], CURLOPT_RETURNTRANSFER, 1);
curl_setopt($handles[$key], CURLOPT_TIMEOUT, 10);
curl_multi_add_handle($mh, $handles[$key]);
}
//======================執行批處理句柄=================================
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active and $mrc == CURLM_OK) {
if(curl_multi_select($mh) === -1){
usleep(100);
}
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
//====================================================================
//獲取批處理內容
foreach($handles as $i => $ch)
{
$content = curl_multi_getcontent($ch);
$contents[$i] = curl_errno($ch) == 0 ? $content : '';
}
//移除批處理句柄
foreach($handles as $ch)
{
curl_multi_remove_handle($mh, $ch);
}
//關閉批處理句柄
curl_multi_close($mh);
print_r($contents);
上面這段程序重點是執行批處理的那段,普通的處理:
[php] view plaincopy
do { $n=curl_multi_exec($mh,$active); } while ($active);
會形成CPU Loading太高,由於$active要等所有url數據接受完畢才變成false,因此這裏用到了curl_multi_exec的返回值判斷是否還有數據,當有數據的時候就不停調用curl_multi_exec,沒有執行數據就會sleep,如此就會避免CPU Loading 100%了。
[php] view plaincopy
/********************** curl 系列 ***********************/
//直接經過curl方式取得數據(包含POST、HEADER等)
/*
* $url: 若是非數組,則爲http;如是數組,則爲https
* $header: 頭文件
* $post: post方式提交 array形式
* $cookies: 0默認無cookie,1爲設置,2爲獲取
*/
public function curl_allinfo($urls, $header = FALSE, $post = FALSE, $cookies = 0) {
$url = is_array($urls) ? $urls['0'] : $urls;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//帶header方式提交
if($header != FALSE){
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
//post提交方式
if($post != FALSE){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
if($cookies == 1){
curl_setopt($ch, CURLOPT_COOKIEJAR, "cookiefile");
}else if($cookies == 2){
curl_setopt($ch, CURLOPT_COOKIEFILE, "cookiefile");
}
if(is_array($urls)){
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
cURL 是一個功能強大的PHP庫,使用PHP的cURL庫能夠簡單和有效地抓取網頁並採集內容,設置cookie完成模擬登陸網頁,curl提供了豐富的函數,開發者能夠從PHP手冊中獲取更多關於cURL信息。本文以模擬登陸開源中國(oschina)爲例,和你們分享cURL的使用。
PHP的curl()在抓取網頁的效率方面是比較高的,並且支持多線程,而file_get_contents()效率就要稍低些,固然,使用curl時須要開啓下curl擴展。
代碼實戰
先來看登陸部分的代碼:
[php] view plaincopy
//模擬登陸
function login_post($url, $cookie, $post) {
$curl = curl_init();//初始化curl模塊
curl_setopt($curl, CURLOPT_URL, $url);//登陸提交的地址
curl_setopt($curl, CURLOPT_HEADER, 0);//是否顯示頭信息
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 0);//是否自動顯示返回的信息
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie); //設置Cookie信息保存在指定的文件中
curl_setopt($curl, CURLOPT_POST, 1);//post方式提交
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));//要提交的信息
curl_exec($curl);//執行cURL
curl_close($curl);//關閉cURL資源,而且釋放系統資源
}
函數login_post()首先初始化curl_init(),而後使用curl_setopt()設置相關選項信息,包括要提交的url地址,保存的cookie文件,post的數據(用戶名和密碼等信息),是否返回信息等等,而後curl_exec執行curl,最後curl_close()釋放資源。注意PHP自帶的http_build_query()能夠將數組轉換成相鏈接的字符串。
接下來若是登陸成功後,咱們要獲取登陸成功後的頁面信息。
[php] view plaincopy
//登陸成功後獲取數據
function get_content($url, $cookie) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie); //讀取cookie
$rs = curl_exec($ch); //執行cURL抓取頁面內容
curl_close($ch);
return $rs;
}
函數get_content()中也是先初始化curl,而後設置相關選項,執行curl,釋放資源。其中咱們設置CURLOPT_RETURNTRANSFER爲1即自動返回信息,而CURLOPT_COOKIEFILE能夠讀取到登陸時保存的cookie信息,最後將頁面內容返回。
咱們的最終目的是要獲取到模擬登陸後的信息,也就是隻有正常登陸成功後才能獲取的有用信息。接下來咱們以登陸開源中國的移動版爲例,看看如何抓取到登陸成功後的信息。
[php] view plaincopy
//設置post的數據
$post = array (
'email' => 'oschina帳戶',
'pwd' => 'oschina密碼',
'goto_page' => '/my',
'error_page' => '/login',
'save_login' => '1',
'submit' => '如今登陸'
);
//登陸地址
$url = "http://m.jb51.net/action/user/login";
//設置cookie保存路徑
$cookie = dirname(__FILE__) . '/cookie_jb51.txt';
//登陸後要獲取信息的地址
$url2 = "http://m.jb51.net/my";
//模擬登陸
login_post($url, $cookie, $post);
//獲取登陸頁的信息
$content = get_content($url2, $cookie);
//刪除cookie文件
@ unlink($cookie);
//匹配頁面信息
$preg = "/<td class='portrait'>(.*)<\/td>/i";
preg_match_all($preg, $content, $arr);
$str = $arr[1][0];
//輸出內容
echo $str;
運行上述代碼後,咱們會看到最終獲取到登陸用戶的頭像圖片。
使用總結:
一、初始化curl;
二、使用curl_setopt設置目標url,和其餘選項;
三、curl_exec,執行curl;
四、執行後,關閉curl;
五、輸出數據。
網上的不少模擬登陸程序,大都是經過服務程序apache之類的運行,獲取到驗證碼以後顯示在網頁上,而後填上再POST出去,這樣雖然看起來很友好,可是既然模擬登陸,登陸後所幹的事情就不必定是短期完成的,因此這就要受到php最大執行時間的限制,並且有些操做還有可能權限不足。
本文提供了一個程序實例,思路就是獲取到驗證碼以後把驗證碼存儲爲一個圖片,而後程序休眠20秒,在20秒以後由用戶手動查看圖片,並把驗證碼填寫到code.txt文件中,20秒休眠完成後,程序會讀code.txt的驗證碼,這樣再帶着驗證碼進行登陸操做。具體代碼以下:
[php] view plaincopy
/**
* 模擬登陸
*/
//初始化變量
$cookie_file = "tmp.cookie";
$login_url = "http://xxx.com/logon.php";
$verify_code_url = "http://xxx.com/verifyCode.php";
echo "正在獲取COOKIE...\n";
$curlj = curl_init();
$timeout = 5;
curl_setopt($curl, CURLOPT_URL, $login_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie_file); //獲取COOKIE並存儲
$contents = curl_exec($curl);
curl_close($curl);
echo "COOKIE獲取完成,正在取驗證碼...\n";
//取出驗證碼
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $verify_code_url);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$img = curl_exec($curl);
curl_close($curl);
$fp = fopen("verifyCode.jpg","w");
fwrite($fp,$img);
fclose($fp);
echo "驗證碼取出完成,正在休眠,20秒內請把驗證碼填入code.txt並保存\n";
//中止運行20秒
sleep(20);
echo "休眠完成,開始取驗證碼...\n";
$code = file_get_contents("code.txt");
echo "驗證碼成功取出:$code\n";
echo "正在準備模擬登陸...\n";
$post = "username=maben&pwd=hahahaha&verifycode=$code";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
$result=curl_exec($curl);
curl_close($curl);
//這一塊根據本身抓包獲取到的網站上的數據來作判斷
if(substr_count($result,"登陸成功")){
echo "登陸成功\n";
}else{
echo "登陸失敗\n";
exit;
}
//OK,開始作你想作的事吧。。。。。