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上。