1. 程式人生 > >PHP爬蟲最全總結1

PHP爬蟲最全總結1

 爬蟲是我一直以來躍躍欲試的技術,現在的爬蟲框架很多,比較流行的是基於python,nodejs,java,C#,PHP的的框架,其中又以基於python的爬蟲流行最為廣泛,還有的已經是一套傻瓜式的軟體操作,如八爪魚,火車頭等軟體。

 今天我們首先嚐試的是使用PHP實現一個爬蟲程式,首先在不使用爬蟲框架的基礎上實踐也是為了理解爬蟲的原理,然後再利用PHP的lib,框架和擴充套件進行實踐。

所有程式碼掛在我的github上。

1.PHP簡單的爬蟲–原型

爬蟲的原理:

  • 給定原始的url;
  • 分析連結,根據設定的正則表示式獲取連結中的內容;
  • 有的會更新原始的url再進行分析連結,獲取特定內容,周而復始。
  • 將獲取的內容儲存在資料庫中(mysql)或者本地檔案中

下面是網上一個例子,我們列下來然後分析
main函式開始

<?php
/**
 * 爬蟲程式 -- 原型
 *
 * 從給定的url獲取html內容
 * 
 * @param string $url 
 * @return string 
 */
function _getUrlContent($url) {
    $handle = fopen($url, "r");
    if ($handle) {
        $content = stream_get_contents($handle, -1);
        //讀取資源流到一個字串,第二個引數需要讀取的最大的位元組數。預設是-1(讀取全部的緩衝資料)
// $content = file_get_contents($url, 1024 * 1024); return $content; } else { return false; } } /** * 從html內容中篩選連結 * * @param string $web_content * @return array */ function _filterUrl($web_content) { $reg_tag_a = '/<[a|A].*?href=[\'\"]{0,1}([^>\'\"\ ]*).*?>/'
; $result = preg_match_all($reg_tag_a, $web_content, $match_result); if ($result) { return $match_result[1]; } } /** * 修正相對路徑 * * @param string $base_url * @param array $url_list * @return array */ function _reviseUrl($base_url, $url_list) { $url_info = parse_url($base_url);//解析url $base_url = $url_info["scheme"] . '://'; if ($url_info["user"] && $url_info["pass"]) { $base_url .= $url_info["user"] . ":" . $url_info["pass"] . "@"; } $base_url .= $url_info["host"]; if ($url_info["port"]) { $base_url .= ":" . $url_info["port"]; } $base_url .= $url_info["path"]; print_r($base_url); if (is_array($url_list)) { foreach ($url_list as $url_item) { if (preg_match('/^http/', $url_item)) { // 已經是完整的url $result[] = $url_item; } else { // 不完整的url $real_url = $base_url . '/' . $url_item; $result[] = $real_url; } } return $result; } else { return; } } /** * 爬蟲 * * @param string $url * @return array */ function crawler($url) { $content = _getUrlContent($url); if ($content) { $url_list = _reviseUrl($url, _filterUrl($content)); if ($url_list) { return $url_list; } else { return ; } } else { return ; } } /** * 測試用主程式 */ function main() { $file_path = "url-01.txt"; $current_url = "http://www.baidu.com/"; //初始url if(file_exists($file_path)){ unlink($file_path); } $fp_puts = fopen($file_path, "ab"); //記錄url列表 $fp_gets = fopen($file_path, "r"); //儲存url列表 do { $result_url_arr = crawler($current_url); if ($result_url_arr) { foreach ($result_url_arr as $url) { fputs($fp_puts, $url . "\r\n"); } } } while ($current_url = fgets($fp_gets, 1024)); //不斷獲得url } main(); ?>

2.使用crul lib

Curl是比較成熟的一個lib,異常處理、http header、POST之類都做得很好,重要的是PHP下操作MySQL進行入庫操作比較省心。關於curl的說明具體可以檢視PHP官方文件說明http://php.net/manual/zh/book.curl.php
不過在多執行緒Curl(Curl_multi)方面比較麻煩。

開啟crul
針對winow系統:
- php.in中修改(註釋;去掉即可)

extension=php_curl.dll

  • php資料夾下的libeay32.dll, ssleay32.dll, libssh2.dll 還有 php/ext下的php_curl4個檔案移入windows/system32

使用crul爬蟲的步驟:
- 使用cURL函式的基本思想是先使用curl_init()初始化一個cURL會話;
- 接著你可以通過curl_setopt()設定你需要的全部選項;
- 然後使用curl_exec()來執行會話;
- 當執行完會話後使用curl_close()關閉會話。

例子

<?php
$ch = curl_init("http://www.example.com/");
$fp = fopen("example_homepage.txt", "w");

curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);

curl_exec($ch);
curl_close($ch);
fclose($fp);
?>

一個完整點的例子:

<?php
/**
 * 將demo1-01換成curl爬蟲
 * 爬蟲程式 -- 原型
 * 從給定的url獲取html內容
 * @param string $url 
 * @return string 
 */
function _getUrlContent($url) {
    $ch=curl_init();  //初始化一個cURL會話
    /*curl_setopt 設定一個cURL傳輸選項*/
    //設定需要獲取的 URL 地址
    curl_setopt($ch,CURLOPT_URL,$url);
    //TRUE 將curl_exec()獲取的資訊以字串返回,而不是直接輸出
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    //啟用時會將標頭檔案的資訊作為資料流輸出
    curl_setopt($ch,CURLOPT_HEADER,1);
    // 設定瀏覽器的特定header
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Host: www.baidu.com",
        "Connection: keep-alive",
        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Upgrade-Insecure-Requests: 1",
        "DNT:1",
        "Accept-Language: zh-CN,zh;q=0.8,en-GB;q=0.6,en;q=0.4,en-US;q=0.2",
        /*'Cookie:_za=4540d427-eee1-435a-a533-66ecd8676d7d; */    
        ));
    $result=curl_exec($ch);//執行一個cURL會話
    $code=curl_getinfo($ch,CURLINFO_HTTP_CODE);// 最後一個收到的HTTP程式碼
    if($code!='404' && $result){
       return $result;
    }
    curl_close($ch);//關閉cURL
} 
/**
 * 從html內容中篩選連結
 * @param string $web_content 
 * @return array 
 */
function _filterUrl($web_content) {
    $reg_tag_a = '/<[a|A].*?href=[\'\"]{0,1}([^>\'\"\ ]*).*?>/';
    $result = preg_match_all($reg_tag_a, $web_content, $match_result);
    if ($result) {
        return $match_result[1];
    } 
} 
/**
 * 修正相對路徑
 * @param string $base_url 
 * @param array $url_list 
 * @return array 
 */
function _reviseUrl($base_url, $url_list) {
    $url_info = parse_url($base_url);//解析url
    $base_url = $url_info["scheme"] . '://';
    if ($url_info["user"] && $url_info["pass"]) {
        $base_url .= $url_info["user"] . ":" . $url_info["pass"] . "@";
    } 
    $base_url .= $url_info["host"];
    if ($url_info["port"]) {
        $base_url .= ":" . $url_info["port"];
    } 
    $base_url .= $url_info["path"];
    print_r($base_url);
    if (is_array($url_list)) {
        foreach ($url_list as $url_item) {
            if (preg_match('/^http/', $url_item)) {
                // 已經是完整的url
                $result[] = $url_item;
            } else {
                // 不完整的url
                $real_url = $base_url . '/' . $url_item;
                $result[] = $real_url;
            } 
        } 
        return $result;
    } else {
        return;
    } 
} 
/**
 * 爬蟲
 * @param string $url 
 * @return array 
 */
function crawler($url) {
    $content = _getUrlContent($url);
    if ($content) {
        $url_list = _reviseUrl($url, _filterUrl($content));
        if ($url_list) {
            return $url_list;
        } else {
            return ;
        } 
    } else {
        return ;
    } 
} 
/**
 * 測試用主程式
 */
function main() {
    $file_path = "./url-03.txt";
    if(file_exists($file_path)){
        unlink($file_path);
    }
    $current_url = "http://www.baidu.com"; //初始url
    //記錄url列表  ab- 追加開啟一個二進位制檔案,並在檔案末尾寫資料
    $fp_puts = fopen($file_path, "ab"); 
    //儲存url列表 r-只讀方式開啟,將檔案指標指向檔案頭
    $fp_gets = fopen($file_path, "r"); 
    do {
        $result_url_arr = crawler($current_url);
        echo "<p>$current_url</p>";
        if ($result_url_arr) {
            foreach ($result_url_arr as $url) {
                fputs($fp_puts, $url . "\r\n");
            } 
        } 
    } while ($current_url = fgets($fp_gets, 1024)); //不斷獲得url
} 
main();
?>

要對https支援,需要在_getUrlContent函式中加入下面的設定:

curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ) ; 
curl_setopt($ch, CURLOPT_USERPWD, "username:password");    
curl_setopt($ch, CURLOPT_SSLVERSION,3); 
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

結果疑惑:
我們通過1和2部分得到的結果差異很大,第1部分能得到四千多條url資料,而第2部分卻一直是45條資料。

還有我們獲得url資料可能會有重複的,這部分處理在我的github上,對應demo2-01.php,或者demo2-02.php

3.file_get_contents/stream_get_contents與curl對比

3.1 file_get_contents/stream_get_contents對比

  • stream_get_contents — 讀取資源流到一個字串
    與 [file_get_contents()]一樣,但是 stream_get_contents() 是對一個已經開啟的資源流進行操作,並將其內容寫入一個字串返回
$handle = fopen($url, "r");
$content = stream_get_contents($handle, -1);//讀取資源流到一個字串,第二個引數需要讀取的最大的位元組數。預設是-1(讀取全部的緩衝資料)
  • file_get_contents — 將整個檔案讀入一個字串
$content = file_get_contents($url, 1024 * 1024);

【注】 如果要開啟有特殊字元的 URL (比如說有空格),就需要使用進行 URL 編碼。

3.2 file_get_contents/stream_get_contents與curl對比

php中file_get_contents與curl效能比較分析一文中有詳細的對比分析,主要的對比現在列下來:
- fopen /file_get_contents 每次請求都會重新做DNS查詢,並不對 DNS資訊進行快取。但是CURL會自動對DNS資訊進行快取。對同一域名下的網頁或者圖片的請求只需要一次DNS查詢。這大大減少了DNS查詢的次數。所以CURL的效能比fopen /file_get_contents 好很多。

  • fopen /file_get_contents 在請求HTTP時,使用的是http_fopen_wrapper,不會keeplive。而curl卻可以。這樣在多次請求多個連結時,curl效率會好一些。

  • fopen / file_get_contents 函式會受到php.ini檔案中allow_url_open選項配置的影響。如果該配置關閉了,則該函式也就失效了。而curl不受該配置的影響。

  • curl 可以模擬多種請求,例如:POST資料,表單提交等,使用者可以按照自己的需求來定製請求。而fopen / file_get_contents只能使用get方式獲取資料。

4.使用框架

使用框架這一塊打算以後單獨研究,並拿出來單寫一篇部落格

所有程式碼掛在我的github上。