因爲工做中有個項目須要爬取第三方網站的內容,因此在Linux下使用Perl寫了個簡單的爬蟲。html
1. HttpWatch/瀏覽器開發人員工具正則表達式
通常狀況下這個工具是用不到的,可是若是你發現要爬取的內容在頁面的HTML源碼裏找不到,若有的頁面是經過AJAX異步請求數據的,這時候就須要HttpWatch之類的工具來找到實際的HTTP請求的URL了,固然如今不少瀏覽器都有開發人員工具(如Chrome, Firefox等),這樣能夠更方便查看全部請求的URL了。數據庫
2. curl/wget數組
這是爬蟲中最重要的工具了,做用就是模擬瀏覽器的HTTP請求,從而獲取數據。通常來講就是請求一個URL來獲取相應的Web頁面的HTML源碼,還能夠下載文件。使用curl和wget均可以很方便完成這個任務。瀏覽器
3. Perlbash
把頁面爬下來後,就要從HTML中提取所須要的信息,這時候就要用到正則表達式了。我用的是Perl來編寫爬蟲腳本。之因此不使用Shell,是由於Shell的正則匹配功能仍是太弱了。固然有不少腳本語言在正則匹配方面都很強大,如Python,若是你對這些腳本語言都不熟悉,相對而言,Perl會更容易上手一些。服務器
4. 正則表達式curl
正則表達式的語法大部分都是通用的,可是不用的語言又會有些小的差異,下面列舉Perl中正則表達式的一些重要的語法:異步
元字符 .工具
錨位 ^ $ \b
字符集 \d \w \s
量詞 * ? + {m,n}
分組 () (abc)*
擇一匹配 | (ab|bc)
捕獲變量 ()
修飾符 /i /s /g
下面以爬取某網站的手機App爲例,說明一下爬取的步驟。
1. 爬取目錄
假設我須要爬取的目錄頁是http://www.anzhi.com/sort_39_1_new.html,首先找到頁索引和URL的規律,這個是很簡單的,只須要把http://www.anzhi.com/sort_39_[i]_new.html中的[i]替換爲頁索引便可。接下來,須要知道一共有多少頁,才能知道爬取目錄頁何時時候完成。通常頁面上會顯示共多少頁,但這裏要爬的頁面是沒有的,那怎麼辦呢?能夠經過人工的方式去看一共有多少頁,還有一個方法是,爬取到某一頁發現沒有匹配的目錄項了,就說明全部目錄頁已經爬完了。
把目錄頁爬取下來後,把二級頁面的ULR經過正則匹配提取出來,寫到數據庫中,URL能夠標識一個頁面的惟一性,因此要保證寫入數據的URL不重複。須要注意的是在HTML中的URL多是相對路徑,須要把URL補全。
大部分狀況是須要增量爬取的,如天天只爬取新增的目錄項,爲了防止重複無效的爬取,選擇的目錄頁最好是按更新時間排序的,這樣只須要爬取有更新的前幾頁就能夠了。那怎麼知道哪些目錄頁是有更新的呢?若是目錄項有更新時間的話,能夠經過比較這個時間來肯定。還有一種更簡單的方法是,若是某一頁全部的URL在數據庫都存在了,說明這一頁沒有新的目錄項了,能夠中止爬取了。
2. 爬取詳細信息
在第一步中已經把二級頁面的URL爬取下來了,接下來就是要爬取詳細信息了,如手機App的各類信息,截圖和安裝包的URL。對於文字信息是很容易在HTML中提取的,但對於安裝包URL就不是一眼能找到的,這裏的下載地址隱藏在JS中,以下圖,在頁面裏提取到id後,就能拼出安裝包URL。對於爬取完成的URL,在數據庫中應該用狀態字段標示其爬取完成,避免重複爬取。
3. 文件下載
有時候咱們不只要爬取文字信息,還須要下載圖片或文件,好比這裏咱們還須要下載截圖和安裝包,在前一步中已經爬取了截圖和安裝包的URL,使用curl或wget能夠很方便地進行文件下載。一樣也須要狀態字段來標示文件的下載狀態。
1. 通用爬取接口
爲了減小一些爬取的重複代碼,這裏提取了一些公共代碼,寫了一個比較通用的爬取接口,須要注意的是,因爲頁面的編碼和數據編碼可能不一致,因此須要把頁面的編碼轉化成數據庫編碼,不然寫入數據就可能出現亂碼。接口說明和代碼以下:
調用方式:@results=&CrawlUrl($url, $page_charset, $expect_charset, \@regexs, \$crawl_result)
參數:URL,頁面編碼,指望編碼,正則表達式數組,爬取是否成功(0成功,不然失敗)
返回值:匹配結果二維數組(一個正則表達式能夠匹配一組數據)
1 #!/usr/bin/perl 2 3 sub ParseUrl 4 { 5 my $url=$_[0]; 6 $url=~s/\[/\\\[/g; 7 $url=~s/\]/\\\]/g; 8 return $url; 9 } 10 11 sub CrawlUrl 12 { 13 my $url=$_[0]; 14 my $page_charset=$_[1]; 15 my $expect_charset=$_[2]; 16 my $regex_ref=$_[3]; 17 my $crawl_result_ref=$_[4]; 18 my @regexs=@$regex_ref; 19 my @results; 20 21 my $file=`echo -n "$url" | md5sum | awk '{print \$1".htm"}'`; 22 chomp($file); 23 $url=&ParseUrl($url); 24 `curl -o "$file" "$url"`; 25 my $curl_result=`echo $?`; 26 chomp($curl_result); 27 if($curl_result!=0) 28 { 29 $$crawl_result_ref=1; 30 return @results; 31 } 32 33 my $html=""; 34 if($page_charset ne "" && $expect_charset ne "" && $page_charset ne $expect_charset) 35 { 36 $html=`iconv -f $page_charset -t $expect_charset "$file"`; 37 } 38 else 39 { 40 $html=`cat "$file"`; 41 } 42 `rm -f $file`; 43 44 for(my $i=0;$i<=$#regexs;$i++) 45 { 46 my $reg=@regexs[$i]; 47 my @matches=($html=~/$reg/sg); 48 $results[$i]=\@matches; 49 } 50 51 $$crawl_result_ref=0; 52 return @results; 53 }
2. 爬蟲通用性
咱們可能須要爬去同一個類型的多個網站,好比我須要爬取數十個來源的手機App,若是每一個網站都寫一個特定的爬蟲,會帶來大量的編碼工做,這時候就要考慮爬蟲的通用性,如何讓一套代碼可以適應一類網站。這裏採用的方法是把各個網站的差別化信息做爲配置存儲在數據庫,如目錄頁URL、網站編碼、各字段正則表達式等,這樣爬蟲經過讀取這些配置就能夠去適配不一樣的網站,達到必定的通用性。若是要新增一個網站的爬取,只須要增長相應的配置,而不須要修改任何代碼。
3. 多進程爬取
若是要爬取的頁面或要下載的文件數量比較大,會比較耗時,這時候能夠考慮多個進程同時進行爬取。寫一個進程控制模塊,經過查詢數據庫中未爬取的URL和檢測當前啓用爬取的進程數,來肯定是否啓用新的進程,達到對多進程爬取的控制。
4. 代理
有些網站可能會限制IP的訪問頻率,若是對網站的爬取頻率比較高,可能就會致使IP被封了,能夠經過在多個代理服務器隨機切換的方式來規避這個問題。爲了不代碼重複,寫了一個使用代理的wget封裝的Shell工具。
1 #!/bin/bash 2 3 PROXY_HOST=(代理服務器列表) 4 5 function GetProxyStr() 6 { 7 rand=$(($RANDOM%(${#PROXY_HOST[*]}+1))) 8 if [ $rand -lt ${#PROXY_HOST[*]} ] 9 then 10 PROXY_STR="-e http_proxy=${PROXY_HOST[$rand]}" 11 fi 12 } 13 14 PROXY_STR="" 15 PATH_TYPE="$1" 16 FILE_PATH="$2" 17 URL="$3" 18 19 GetProxyStr 20 GetPath 21 22 wget --user-agent="Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092416 Firefox/3.0.3" $PROXY_STR $PATH_TYPE "$FILE_PATH" "$URL"
5. 監控
還有一個問題就是,若是爬蟲是天天定時運行的,在網站目錄頁URL發生變化或頁面改版了,爬取就會失敗。這就要求對這些失敗進行監控,在爬取頁面失敗或者正則匹配失敗時,經過短信、郵件等方式進行告警。