PHP cURL應用實現模擬登錄與采集使用方法詳解


對於做過數據采集的人來說,cURL一定不會陌生。雖然在PHP中有file_get_contents函數可以獲取遠程鏈接的數據,但是它的可控制性太差了,對於各種復雜情況的采集情景,file_get_contents顯得有點無能為力。因此,本文將為你介紹采集神器cURL的使用。

工具

火狐瀏覽器(FireFox) + Firebug

“工欲善其事,必先利其器。” 在分析案例之前,先讓我們學習一下如何利用神器Firebug獲取我們必要的信息。
使用F12打開Firebug,我們可以得到如圖(一)界面:
Firebug

  1. 箭頭圖標是“元素選擇”工具,單擊一次會高亮圖標,同時,鼠標在頁面內的移動會同時在HTML菜單中選定相應的內容,此時單擊內容則表示選定了該元素,圖標高亮取消。如圖(二)所示:
    Firebug查看元素

  2. 控制台
    JS里面的console.log系列函數的打印就是在這里輸出。

  3. HTML
    HTML內容,注意這里看到的不一定是采集要解析的內容,采集時候對內容的分析,一律以查看源碼(Ctrl+U)為准,這里只是能快速定位元素的結構,然后再選擇一個比較特殊的參照,在源碼中定位相應的位置。
    比如,你在HTML里面看到一個標簽是<div id="demo" class="demo">Demo</div>,但是你查看源碼時候看到的內容可能是<div class="demo" id="demo">Demo</div>,如果你對采集內容按照前者去做正則匹配,那么你會得不到結果。

  4. CSS
    這里是CSS文件內容

  5. 腳本
    這里是Javascript文件內容

  6. DOM
    Dom節點內容

  7. 網絡
    每一個請求鏈接的數據,這里是我們采集要關注和分析的地方,它能夠顯示每一個請求的參數、請求頭、Cookie數據等。在頁面提交會刷新的情況下,需要使用保持,使得頁面請求內容在刷新后仍然留着控制台中,如圖(三)所示:
    Firebug網絡保持
    另外,火狐還有一款 Tamper data 擴展也能得到請求數據,必要時可以安裝使用。

  8. Cookies
    Cookie數據

圖(一)中還看到下面有很多可選的小菜單項,其中保持是我們要關注的,當選擇它的時候,即使提交表單刷新了頁面,下面內容區域的數據還是會保留,這個對於分析提交數據特別關鍵。

總結

我們在分析采集請求的時候,主要關心“網絡”菜單里的請求數據,必要時候使用“保持”以查看刷新頁面的請求數據,請求前可以使用“清除”先清除下面的內容。

案例解析

一、簡單的采集

這里所指的簡單采集,是指一個單一頁面GET請求的采集,它簡單得即使通過file_get_contents函數也能輕松獲得頁面返回結果。


  • 代碼片段之file_get_contents
<?php
    $url = 'http://demo.zjmainstay.cn/php/curl/simple.html';
    $content = file_get_contents($url);
    echo $content;


  • 代碼片段之cURL
<?php
    $url = 'http://demo.zjmainstay.cn/php/curl/simple.html';
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回數據不直接輸出
    $content = curl_exec($ch);                    //執行並存儲結果
    curl_close($ch);
    
    echo $content;
    


二、需要參數的采集

這種情況,頁面請求需要傳入一些參數,可以是GET請求,也可以是POST請求。這種情況的采集,使用file_get_contents外帶一些參數還是可以實現的,但是這里我們將不再展示。

  • 代碼片段之cURL GET
    這種請求,我們可以選擇搜索引擎作為演示,比如我百度搜索一個詞語“PHP cURL”,在輸入回車后,我們會得到一個類似http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&ch=&tn=baidu&bar=&wd=PHP%20cURL的鏈接,注意這里的鏈接可能不同瀏覽器、不同入口方式訪問得到不一樣結果,因此不必介意鏈接是否一樣。通過輸入多個關鍵詞並觀察鏈接,我們可以確定 wd 參數就是我們要傳入的動態參數,而其他參數則可以不變,因此得到我們下面的采集代碼。
<?php
    $keyword    = 'PHP cURL';
    $url        = 'http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&ch=&tn=baidu&bar=&wd=' . urlencode($keyword);
    
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回數據不直接輸出
    $content = curl_exec($ch);                    //執行並存儲結果
    curl_close($ch);
    
    echo $content;

有些時候,一些參數並不是必須的,這時候我們可以刪掉它,比如上面的鏈接可以只保留http://www.baidu.com/s?ie=utf-8&wd=PHP%20cURLie=utf-8 這個參數可能影響結果的編碼,所以暫且留着它。就這樣簡單的代碼,我們就可以采集到百度搜索的結果了。

  • 代碼片段之cURL POST
    對於POST類型的請求,我們平時並不少見,比如有些搜索就是使用POST方式提交,這時候我們就需要使用POST類型來提交參數了。這個在PHP cURL里面有相應的參數:CURLOPT_POST 和 CURLOPT_POSTFIELDS , CURLOPT_POST 的設置可以指定當前提交是否為POST方式,CURLOPT_POSTFIELDS則用於設定提交的參數,可以是參數串,也可以是參數數組,比如:
curl_setopt($ch, CURLOPT_POSTFIELDS, 'ie=utf-8&wd=PHP%20cURL');

或

curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'ie'    => 'utf-8',
    'wd'    => 'PHP%20cURL',
));

下面是我做的一個POST模擬搜索PHP POST 搜索,后端是使用了前面的百度關鍵詞搜索,基本原理就是,客戶端提交一個關鍵詞到我服務器,我服務器使用該關鍵詞請求百度的搜索,然后得到結果,返回到客戶端。
如圖(四)是利用Firebug對請求數據的分析,得到我們需要提交的請求鏈接和請求參數:
cURL POST 參數分析

然后下面是我們的代碼:

<?php
    $keyword    = 'PHP cURL';
    //參數方法一
    // $post       = 'wd=' . urlencode($keyword);
    
    //參數方法二
    $post       = array(
        'wd'        => urlencode($keyword),
    );
    $url        = 'http://demo.zjmainstay.cn/php/curl/search.php';
    
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回數據不直接輸出
    curl_setopt($ch, CURLOPT_POST, 1);            //發送POST類型數據
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);  //POST數據,$post可以是數組,也可以是拼接
    $content = curl_exec($ch);                    //執行並存儲結果
    curl_close($ch);
    
    var_dump($content);


三、需要Referer的采集

對於一些程序,它可能判斷來源網址,如果發現referer不是自己的網站,則拒絕訪問,這時候,我們就需要添加CURLOPT_REFERER參數,模擬來路,使得程序能夠正常采集。

<?php
    $keyword    = 'PHP cURL';
    //參數方法一
    // $post       = 'wd=' . urlencode($keyword);
    
    //參數方法二
    $post       = array(
        'wd'        => urlencode($keyword),
    );
    $url        = 'http://demo.zjmainstay.cn/php/curl/search_refer.php';
    $refer      = 'http://demo.zjmainstay.cn/';    //來路地址
    
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回數據不直接輸出
    curl_setopt($ch, CURLOPT_REFERER, $refer);    //來路模擬
    curl_setopt($ch, CURLOPT_POST, 1);            //發送POST類型數據
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);  //POST數據,$post可以是數組,也可以是拼接
    $content = curl_exec($ch);                    //執行並存儲結果
    curl_close($ch);
    
    var_dump($content);


search_refer.php的源碼如下,做了簡單的Referer判斷攔截:

<?php
    if(empty($_POST['wd'])) {
        exit('Deny empty params.');
    }
    
    //Referer判斷
    if(stripos($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST']) === false) {
        exit('Deny');
    }
    
    $keyword    = addslashes(trim(strip_tags($_POST['wd'])));
    $url        = 'http://www.baidu.com/s?ie=utf-8&wd=' . urlencode($keyword);
    
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回數據不直接輸出
    $content = curl_exec($ch);                    //執行並存儲結果
    curl_close($ch);
    
    echo $content; 


四、需要cookie支持的采集

對於模擬登錄的應用,單單提交參數和模擬來路並不能解決問題,這時候我們就需要保存或者提交相應的Cookie參數,這個在PHP cURL里面也提供了相應的參數:
CURLOPT_COOKIE: 直接使用字符串方式提交cookie參數
CURLOPT_COOKIEFILE: 使用文件方式提交cookie參數
CURLOPT_COOKIEJAR: 保存提交后反饋的cookie數據

下面是PHP100的模擬登錄示例:

<?php 
header("content-Type: text/html; charset=UTF-8");
$cookie_file = tempnam('./temp', 'cookie');
$login_url="http://bbs.php100.com/login.php";
$post_fields="cktime=36000&step=2&pwuser=username&pwpwd=password";

//提交登錄表單請求
$ch=curl_init($login_url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_POST,1);
curl_setopt($ch,CURLOPT_POSTFIELDS,$post_fields);
curl_setopt($ch,CURLOPT_COOKIEJAR,$cookie_file); //存儲提交后得到的cookie數據
curl_exec($ch);
curl_close($ch);

//登錄成功后,獲取bbs首頁數據
$url="http://bbs.php100.com/index.php";
$ch=curl_init($url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_COOKIEFILE,$cookie_file); //使用提交后得到的cookie數據做參數
$contents=curl_exec($ch);
curl_close($ch);
//轉碼顯示
echo iconv('gbk', 'UTF-8', $contents);

五、壓縮網頁采集(gzip)

有些沒有接觸過壓縮頁面的朋友估計會在這里被坑死,因為他們會發現采集回來的內容是亂碼,並且無論使用iconv還是強大的mb_convert_encoding都無法還原數據,然后又沒有概念,各種抓狂卻找不到方法,哈哈,我曾經也是這樣~
如圖(五)是亂碼表現形式:
cURL 亂碼 解決
還好最后功夫不負有心人,還是找到了,它就是CURLOPT_ENCODING參數。
比如,采集搜狐的新聞時候就遇到gzip壓縮問題,下面是示例:

<?php
    $url = 'http://news.sohu.com/';
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //返回數據不直接輸出
    curl_setopt($ch, CURLOPT_ENCODING, "gzip"); //指定gzip壓縮
    $content = curl_exec($ch); //執行並存儲結果
    curl_close($ch);
    echo $content;

手冊說明:支持的編碼有"identity","deflate"和"gzip"。如果為空字符串"",請求頭會發送所有支持的編碼類型。
后面一句表明,使用curl_setopt($ch, CURLOPT_ENCODING, "");也是可以的,但是不能不加這個參數。

六、SSL鏈接的采集

有些請求鏈接是https類型的,這時候使用cURL采集可能會失敗,這時候,我們可以使用 var_dump(curl_error($ch));的方法打印錯誤提示,然后根據錯誤提示查找相應的解決方案。比如SSL錯誤常見提示: SSL certificate problem: unable to get local issuer certificate,這時候,我們就需要利用參數:CURLOPT_SSL_VERIFYPEER 和 CURLOPT_SSL_VERIFYHOST 來禁用SSL證書的驗證,我嘗試過只使用CURLOPT_SSL_VERIFYPEER參數禁用失敗,所以大家最好同時使用兩個參數。
下面是代碼示例:

<?php
    $searchStr = 'RC376981638HK';
    $post   = 'accion=LocalizaUno&numero='.$searchStr.'&ecorreo=&numeros=';

    $url    = 'https://aplicacionesweb.correos.es/localizadorenvios/track.asp';

    $ch                 = curl_init($url);              //初始化curl
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);        //返回數據不直接輸出
    curl_setopt($ch, CURLOPT_POST, 1);                  //發送POST類型數據
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);        //POST數據,$post可以是數組,也可以是拼接參數串
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);    //SSL 報錯時使用
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);    //SSL 報錯時使用
    $contents = curl_exec($ch);                              //執行並存儲結果
    // var_dump(curl_error($ch));                       //獲取失敗是使用(采集錯誤提示)
    curl_close($ch);
    echo $contents;

七、代理采集

大家都知道,國內存在萬惡的牆,所以,假如我們需要獲取某些被牆數據時,就需要用到國外代理服務器;又或者我們需要采集大量數據時,需要不斷切換IP,也會用到代理。
使用代理在PHP cURL里面有幾個相對應的參數:CURLOPT_PROXY、CURLOPT_PROXYPORT 和 CURLOPT_PROXYUSERPWD,還有另外幾個,這里不列舉。
CURLOPT_PROXY 指定代理IP參數
CURLOPT_PROXYPORT 指定代理端口參數
CURLOPT_PROXYUSERPWD 指定需要驗證的代理的賬號密碼,"[username]:[password]"格式的字符串

關於代理賬號獲取,大家自己發揮,我這里提供網上搜索到的一個列表:cURL 高匿代理

下面是代理采集示例:

<?php
    $url = 'http://demo.zjmainstay.cn/php/curl/dump_ip.php?t=' . time();
    
    echo "本地IP:" . file_get_contents($url) . "\n偽造IP:";
    
    $ip     = '183.224.1.116';
    $port   = '80';
    
    //偽造請求頭參數,如果是高匿代理這里不需要提供
    $header = array(
        'X-FORWARDED-FOR: ' . $ip,
        'CLIENT-IP: ' . $ip,
    );
    
    $ch                 = curl_init($url);  //初始化curl
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    curl_setopt($ch, CURLOPT_PROXY, $ip);
    curl_setopt($ch, CURLOPT_PROXYPORT, $port);

    $content = curl_exec($ch);  //執行並存儲結果
    curl_close($ch);
    
    echo $content;

八、 多線程采集

對於大量采集工作,為了提高采集效率,使用PHP cURL提供的多線程采集是必不可少的。手冊上提供的多線程采集例子好像都不太好用,我剛開始也從里面測試了幾個例子,但是發現都是執行卡死,根本無法執行完成,前幾天突然又測試了一下,然后發現curl_multi_info_read函數下面的Example #1是可以執行的,它的內容在$res上,但是沒有打印出來,而且雅虎的請求比較慢,會卡住,前面兩個鏈接都能正常返回。
不過,還好當時的例子不好用,然后我經過搜索找到了一個很厲害的項目,CurlMulti ,它對PHP cURL Multi 進行了一個良性擴展的封裝,能夠很好地提供采集支持。
關於CurlMulti的使用我就不多介紹,官網上面提供了demo,使用過程有技術難題可以直接加入Q群討論,作者@Ares 和其他的采集大牛都會提供技術解答幫助。
下面是PHP cURL Multi的一個簡單示例:

<?php
$urls = array(
   "http://demo.zjmainstay.cn/php/curl/curl_multi_1.php",
   "http://demo.zjmainstay.cn/php/curl/curl_multi_2.php",
);

$mh = curl_multi_init();

foreach ($urls as $i => $url) {
    $conn[$i] = curl_init($url);
    curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, 1);  //不直接輸出結果
    curl_multi_add_handle($mh, $conn[$i]);
}

$active = null;
$res = array();
do {
    $status = curl_multi_exec($mh, $active);
    $info = curl_multi_info_read($mh);
    if (false !== $info) {
        //采集信息處理
        $res[] = array(
            'content'   => curl_multi_getcontent($info['handle']),
            'info'      => $info,
        );
        curl_close($info['handle']);
    }
} while ($status === CURLM_CALL_MULTI_PERFORM || $active);

curl_multi_close($mh);

var_dump($res);

九、302跳轉(301跳轉)

對於一些應用,比如模擬登錄,如果遇上302跳轉,會導致cookie丟失而使得模擬登錄失敗,請求現象如圖(六)所示:

curl 302跳轉

這個時候,可以使用:

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

關於CURLOPT_FOLLOWLOCATION,手冊說明是:

啟用時會將服務器服務器返回的"Location: "放在header中遞歸的返回給服務器,使用CURLOPT_MAXREDIRS可以限定遞歸返回的數量。

我個人理解,通俗點講就是后面的跳轉會繼續跟蹤訪問,而且cookie在header里面被保留了下來。

十、模擬上傳文件

在PHP手冊的curl_setopt函數中,關於CURLOPT_POSTFIELDS有如下描述:

全部數據使用HTTP協議中的"POST"操作來發送。要發送文件,在文件名前面加上@前綴並使用完整路徑。這個參數可以通過urlencoded后的字符串類似'para1=val1&para2=val2&...'或使用一個以字段名為鍵值,字段數據為值的數組。如果value是一個數組,Content-Type頭將會被設置成multipart/form-data。

對於上傳文件,這句話包含兩個信息:

1. 要上傳文件,post的數據參數必須使用數組,使得Content-Type頭將會被設置成multipart/form-data。
2. 要上傳文件,在文件名前面加上@前綴並使用完整路徑。

因此,模擬文件上傳可以按照如下實現:

//上傳D盤下的test.jpg文件,文件必須存在,否則curl處理失敗且沒有任何提示
$data = array('name' => 'Foo', 'file' => '@d:/test.jpg');

$ch = curl_init('http://localhost/upload.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_exec($ch);

本地測試的時候,在upload.php文件中打印出$_POST$_FILES即可驗證是否上傳成功,如下:

<?php
    print_r($_POST);
    print_r($_FILES);

輸出結果類似:

Array ( [name] => Foo ) Array ( [file] => Array ( [name] => test.jpg [type] => application/octet-stream [tmp_name] => D:\xampp\tmp\php2EA0.tmp [error] => 0 [size] => 139999 ) ) 

關於CURLOPT_POSTFIELDS的賦值,另外補充一句描述:

傳遞一個數組到CURLOPT_POSTFIELDS,cURL會把數據編碼成 multipart/form-data,而然傳遞一個URL-encoded字符串時,數據會被編碼成 application/x-www-form-urlencoded。 

即:

curl_setopt($ch, CURLOPT_POSTFIELDS, 'param1=val1&param2=val2&...');
和
curl_setopt($ch, CURLOPT_POSTFIELDS, array('param1' => 'val1', 'param2' => 'val2', ...));

相當於html的form表單中:

<form method="post" action="upload.php">

<form method="post" action="upload.php" enctype="multipart/form-data">

的區別。

十一、發送與獲取json數據

發送json數據,在控制台中的表現主要如圖(七)所示:
ajax發送json的控制台信息
第一條發送的是json格式的數據,
第二條發送的是以\n分割的數據,
第三條發送的是以&分割的數據。
這個在ajax請求的時候,只需要添加contentType參數即可,如:

var data = ["name:Zjmainstay", "website:http://www.zjmainstay.cn"];
$.ajax({
    url: 'http://test.com/curl/testPostJsonData.php',
    type: 'post',
    data: data.join("\n"),
    contentType: 'text/plain',
    success: function(result) {
        console.log(result);
    },
    error: function(msg) {
        console.log(msg);
    }
});

對於這類發送json數據的請求,復制cURL命令時,你會發現其中根本沒有發送數據,如:

curl 'http://test.com/curl/testPostJsonData.php' -X POST -H 'Accept: */*' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3' -H 'Cache-Control: max-age=0' -H 'Connection: keep-alive' -H 'Content-Length: 48' -H 'Content-Type: text/plain' -H 'Cookie: __utma=99889051.942646074.1467634856.1467634856.1467636947.2' -H 'Host: test.com' -H 'Referer: http://test.com/curl/ajaxJsonData.html' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:47.0) Gecko/20100101 Firefox/47.0' -H 'X-Requested-With: XMLHttpRequest'

對於這種數據,我們要用PHP cURL模擬發送的話,需要發送相應的header參數,示例:

<?php
    #json數據
    $url = 'http://test.com/curl/testPostJsonData.php';
    $data = '{"a":"b"}';
    $length = strlen($data);
    $header = array(
        'Content-Length: ' . $length,   //不是必需的
        'Content-Type: text/json',
    );
    $ch                 = curl_init($url);  //初始化curl
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $content = curl_exec($ch);  //執行並存儲結果
    curl_close($ch);
    echo $content;
    
    #\n分割數據
    $data = [
        'name:Zjmainstay',
        'website:http://www.zjmainstay.cn',
    ];
    $data = implode("\n", $data);
    
    #&分割數據
    $data = 'name:Zjmainstay&website:http://www.zjmainstay.cn';

那么,對於這種給服務器端發送json數據的程序,服務器端是怎么得到請求數據的呢?
如果你們做過嘗試,一定會發現此時$_POST是空的,我們要使用php://input進行數據獲取,示例:

$postData = file_get_contents('php://input');

總結

PHP cURL是一個很強大的采集工具,curl_setopt里面還有很多參數,讀者可以抽空整體看一遍,雖然平時未必用得上,但是至少做到心里有底,知道都有哪些參數,必要時還能找出來使用。
采集是一項大工程,使用過程中遇到的問題還會不少,但是只要學會分析和資料搜索,一切都會迎刃而解的,大家加油!哈哈~~

文章首發自Zjmainstay學習筆記《PHP cURL實現模擬登錄與采集使用方法詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM