2011年9月25日 星期日

[ Java 文章收集 ] 使用HttpClient 和HtmlParser 實現簡易爬蟲


轉載自 這裡
前言 :
這篇文章介紹了HtmlParser 開源包和 HttpClient 開源包的使用,在此基礎上實現了一個簡易的網絡爬蟲(Crawler),來說明如何使用HtmlParser 根據需要處理Internet 上的網頁,以及如何使用HttpClient 來簡化Get 和Post 請求操作,構建強大的網絡應用程序.

HttpClient 與HtmlParser 簡介 :
- HttpClient 簡介
HTTP 協議是現在的因特網最重要的協議之一。除了​​WEB瀏覽器之外, WEB服務,基於網絡的應用程序以及日益增長的網絡計算不斷擴展著HTTP協議的角色,使得越來越多的應用程序需要HTTP協議的支持。雖然JAVA類庫.net包提供了基本功能,來使用HTTP協議訪問網絡資源,但是其靈活性和功能遠不能滿足很多應用程序的需要。而Jakarta Commons HttpClient 組件尋求提供更為靈活,更加高效的HTTP協議支持,簡化基於HTTP協議的應用程序的創建. HttpClient 提供了很多的特性,支持最新的HTTP標準,可以訪問這裡了解更多關於HttpClinet的詳細信息。目前有很多的開源項目都用到了HttpClient提供的HTTP功能,登陸網址可以查看這些項目. 本文中使用HttpClinet提供的類庫來訪問和下載Internet上面的網頁,在後續部分會詳細介紹到其提供的兩種請求網絡資源的方法: Get請求和Post請求.

- HtmlParser 簡介
當今的Internet上面有數億記的網頁,越來越多應用程序將這些網頁作為分析和處理的數據對象。這些網頁多為半結構化的文本,有著大量的標籤和嵌套的結構。當我們自己開發一些處理網頁的應用程序時,會想到要開發一個單獨的網頁解析器,這一部分的工作必定需要付出相當的精力和時間。事實上,做為JAVA應用程序開發者, HtmlParser為其提供了強大而靈活易用的開源類庫,大大節省了寫一個網頁解析器的開銷。HtmlParser 是http://sourceforge.net上活躍的一個開源項目,它提供了線性和嵌套兩種方式來解析網頁,主要用於html網頁的轉換(Transformation)以及網頁內容的抽取(Extraction) 。HtmlParser有如下一些易於使用的特性:過濾器(Filters),訪問者模式(Visitors),處理自定義標籤以及易於使用的JavaBeans。正如HtmlParser首頁所說:它是一個快速,健壯以及嚴格測試過的組件;以它設計的簡潔,程序運行的速度以及處理Internet上真實網頁的能力吸引著越來越多的開發者。本文中就是利用HtmlParser裡提取網頁裡的鏈接,實現簡易爬蟲裡的關鍵部分。HtmlParser最新的版本是 HtmlParser1.6,可以登陸這裡下載其源碼、 API參考文檔以及JAR包.

HttpClient 基本類庫使用 :
HttpClinet 提供了幾個類來支持HTTP 訪問。下面我們通過一些示例代碼來熟悉和說明這些類的功能和使用。HttpClient 提供的HTTP 的訪問主要是通過GetMethod 類和PostMethod 類來實現的,他們分別對應了HTTP Get 請求與Http Post 請求 :
- GetMethod
使用GetMethod 來訪問一個URL 對應的網頁,需要如下一些步驟 :
* 生成一個 HttpClinet 對象並設置相應的參數。
* 生成一個 GetMethod 對象並設置響應的參數。
* 用HttpClinet 生成的對象來執行GetMethod 生成的Get 方法。
* 處理響應狀態碼。
* 若響應正常,處理HTTP 響應內容。
* 釋放連接

下面代碼展示了這些步驟,其中的註釋對代碼進行了較詳細的說明 :
- GetMethodDemp.java :
  1. package john.crawler.test;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5.   
  6. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;  
  7. import org.apache.commons.httpclient.Header;  
  8. import org.apache.commons.httpclient.HttpClient;  
  9. import org.apache.commons.httpclient.HttpException;  
  10. import org.apache.commons.httpclient.HttpStatus;  
  11. import org.apache.commons.httpclient.methods.GetMethod;  
  12. import org.apache.commons.httpclient.params.HttpMethodParams;  
  13.   
  14. public class GetMethodDemo {  
  15.     public static void main(String args[]) {  
  16.         String url = "";  
  17.         /* 1 生成HttpClinet 對象並設置參數 */  
  18.         HttpClient httpClient = new HttpClient();  
  19.         // 設置Http 連接超時為5秒  
  20.         httpClient.getHttpConnectionManager().getParams()  
  21.                 .setConnectionTimeout(5000);  
  22.   
  23.         /* 2 生成GetMethod 對象並設置參數 */  
  24.         GetMethod getMethod = new GetMethod(url);  
  25.         // 設置get 請求超時為5 秒  
  26.         getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);  
  27.         // 設置請求重試處理,用的是默認的重試處理:請求三次  
  28.         getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  
  29.                 new DefaultHttpMethodRetryHandler());  
  30.   
  31.         /* 3 執行HTTP GET 請求 */  
  32.         try {  
  33.             int statusCode = httpClient.executeMethod(getMethod);  
  34.             /* 4 判斷訪問的狀態碼 */  
  35.             if (statusCode != HttpStatus.SC_OK) {  
  36.                 System.err.println("Method failed: "  
  37.                         + getMethod.getStatusLine());  
  38.                 return;  
  39.             }  
  40.   
  41.             /* 5 處理HTTP 響應內容 */  
  42.             // HTTP響應頭部信息,這裡簡單打印  
  43.             Header[] headers = getMethod.getResponseHeaders();  
  44.             for (Header h : headers)  
  45.                 System.out.println(h.getName() + " " + h.getValue());  
  46.             // 讀取HTTP 響應內容,這裡簡單打印網頁內容  
  47.             byte[] responseBody = getMethod.getResponseBody();// 讀取為字節數組  
  48.             System.out.println(new String(responseBody));  
  49.             // 讀取為InputStream,在網頁內容數據量大時候推薦使用  
  50.             InputStream response = getMethod.getResponseBodyAsStream();//  
  51.             // 後續處理  
  52.             response.close();  
  53.         } catch (HttpException e) {  
  54.             // 發生致命的異常,可能是協議不對或者返回的內容有問題  
  55.             System.out.println("Please check your provided http address!");  
  56.             e.printStackTrace();  
  57.         } catch (IOException e) {  
  58.             // 發生網絡異常  
  59.             e.printStackTrace();  
  60.         } finally {  
  61.             /* 6 .釋放連接 */  
  62.             getMethod.releaseConnection();  
  63.         }  
  64.     }  
  65. }  

這裡值得注意的幾個地方是 :
1. 設置連接超時和請求超時,這兩個超時的意義不同,需要分別設置.
2. 響應狀態碼的處理. (RFC 2616 : Http/1.1 status code def)
3. 返回的結果可以為字節數組,也可以為InputStream,而後者在網頁內容數據量較大的時候推薦使用!

在處理返回結果的時候可以根據自己的需要,進行相應的處理. (如保存網頁)

- PostMethod
PostMethod 方法與GetMethod 方法的使用步驟大體相同. 但是由於PostMethod 使用的是HTTP 的Post 請求,因而請求參數的設置與GetMethod 有所不同. 在GetMethod 中,請求的參數直接寫在URL 裡,一般以這樣形式出現:http://hostname:port//file?name1=value1&name2=value …。請求參數是name,value 對。比如我想得到百度搜索 "Thinking In Java" 的結果網頁,就可以使GetMethod 的構造方法中的url 為:http://www.baidu.com/s?wd=Thinking+In+Java . 而PostMethod 則可以模擬網頁裡表單提交的過程,通過設置表單裡post 請求參數的值,來動態的獲得返回的網頁結果. 以下代碼展示瞭如何創建一個Post 對象,並設置相應的請求參數 :
  1. PostMethod postMethod = new PostMethod("http://dict.cn/");  
  2. postMethod.setRequestBody(new NameValuePair[]{new NameValuePair("q","java")});  

HtmlParser 基本類庫使用 :
HtmlParser 提供了強大的類庫來處理Internet 上的網頁,可以實現對網頁特定內容的提取和修改。下面通過幾個例子來介紹HtmlParser 的一些使用. 這些例子其中的代碼,有部分用在了後面介紹的簡易爬蟲中. 網頁是一個半結構化的嵌套文本文件,有類似XML 文件的樹形嵌套結構. 使用HtmlParser 可以讓我們輕易的迭代遍歷網頁的所有節點 :
- HtmpParserDemo 部分範例代碼 :
  1. // 循環訪問所有節點,輸出包含關鍵字的值節點  
  2. public static void extractKeyWordText(String url, String keyword) {  
  3.     try {  
  4.         // 生成一個解析器對象,用網頁的url 作為參數  
  5.         Parser parser = new Parser(url);  
  6.         // 設置網頁的編碼,這裡只是請求了一個gb2312 編碼網頁  
  7.         parser.setEncoding("gb2312");  
  8.         // 迭代所有節點, null 表示不使用NodeFilter  
  9.         NodeList list = parser.parse(null);  
  10.         // 從初始的節點列表跌倒所有的節點  
  11.         processNodeList(list, keyword);  
  12.     } catch (ParserException e) {  
  13.         e.printStackTrace();  
  14.     }  
  15. }  
  16.   
  17. private static void processNodeList(NodeList list, String keyword) {  
  18.     // 迭代開始  
  19.     SimpleNodeIterator iterator = list.elements();  
  20.     while (iterator.hasMoreNodes()) {  
  21.         Node node = iterator.nextNode();  
  22.         // 得到該節點的子節點列表  
  23.         NodeList childList = node.getChildren();  
  24.         // 孩子節點為空,說明是值節點  
  25.         if (null == childList) {  
  26.             // 得到值節點的值  
  27.             String result = node.toPlainTextString();  
  28.             // 若包含關鍵字,則簡單打印出來文本  
  29.             if (result.indexOf(keyword) != -1)  
  30.                 System.out.println(result);  
  31.         } // end if  
  32.             // 孩子節點不為空,繼續迭代該孩子節點  
  33.         else {  
  34.             processNodeList(childList, keyword);  
  35.         }// end else  
  36.     }// end wile  
  37. }  

上面的中有兩個方法 :
private static void processNodeList(NodeList list, String keyword)
該方法是用類似深度優先的方法來迭代遍歷整個網頁節點,將那些包含了某個關鍵字的值節點的值打印出來.

public static void extractKeyWordText(String url, String keyword)
該方法生成針對String 類型的url 變量代表的某個特定網頁的解析器,調用上面方法實現簡單的遍歷!

上述代碼展示如何迭代網頁,更多的工作可以在此基礎上展開。比如找到網頁某個特定的內部節點,其實就可以在遍歷所有的節點基礎上來判斷,看被迭代的節點是否滿足特定的需要.
- 使用NodeFilter
NodeFilter 是一個接口,任何一個自定義的Filter 都需要實現這個接口中的boolean accept() 方法。如果希望迭代網頁節點的時候保留當前節點,則在節點條件滿足的情況下返回true;否則返回false。HtmlParse 裡提供了很多實現了NodeFilter 接口的類,下面就一些筆者所用到的,以及常用的Filter 做一些介紹 :
* 對Filter做邏輯操作的Fitler有 AndFilterNotFilterOrFilter,XorFilter
這些Filter 來組合不同的Filter,形成滿足兩個Filter 邏輯關係結果的Filter.

* 判斷節點的孩子,兄弟,以及父親節點情況的Filter有 HasChildFilterHasParentFilterHasSiblingFilter.
* 判斷節點本身情況的Filter有 HasAttributeFilter : 判讀節點是否有特定屬性;LinkStringFilter : 判斷節點是否是具有特定模式(pattern) url的節點;TagNameFilter : 判斷節點是否具有特定的名字;NodeClassFilter : 判讀節點是否是某個HtmlParser定義好的Tag類型.
org.htmlparser.tags包下有對應Html標籤的各種Tag,例如LinkTag,ImgeTag等.

還有其他的一些Filter 在這裡不一一列舉了,可以在 org.htmlparser.filters 下找到. 下面代碼展示瞭如何使用上面提到過的一些 filter 來抽取網頁中的 a 標籤的 href 屬性值, img 標籤與 frame 標籤中的 src 屬性值 :


- HttpParserDemo 部分代碼 :
  1. // 獲取一個網頁上所有的鏈接和圖片鏈接  
  2. public static void extracLinks(String url) {  
  3.     try {  
  4.         Parser parser = new Parser(url);  
  5.         parser.setEncoding("gb2312");  
  6.         // 過濾 frame  標籤的filter,用來提取frame 標籤裡的src 屬性所、表示的鏈接  
  7.         NodeFilter frameFilter = new NodeFilter() {  
  8.   
  9.             @Override  
  10.             public boolean accept(Node node) {  
  11.                 if (node.getText().startsWith("frame src=")) {  
  12.                     return true;  
  13.                 }  
  14.                 return false;  
  15.             }  
  16.   
  17.         };  
  18.         // OrFilter 來設置過濾 a 標籤,img 標籤和 frame 標籤,三個標籤是or 的關係  
  19.         OrFilter orFilter = new OrFilter(  
  20.                 new NodeClassFilter(LinkTag.class), new NodeClassFilter(  
  21.                         ImageTag.class));  
  22.         OrFilter linkFilter = new OrFilter(orFilter, frameFilter);  
  23.         // 得到所有經過過濾的標籤  
  24.         NodeList list = parser.extractAllNodesThatMatch(linkFilter);  
  25.         for (int i = 0; i < list.size(); i++) {  
  26.             Node tag = list.elementAt(i);  
  27.             if (tag instanceof LinkTag)// a 標籤  
  28.             {  
  29.                 LinkTag link = (LinkTag) tag;  
  30.                 String linkUrl = link.getLink();// url  
  31.                 String text = link.getLinkText();// 鏈接文字  
  32.                 System.out.println(linkUrl + "**********" + text);  
  33.             } else if (tag instanceof ImageTag)// img 標籤  
  34.             {  
  35.                 ImageTag image = (ImageTag) list.elementAt(i);  
  36.                 System.out.print(image.getImageURL() + "********");// 圖片地址  
  37.                 System.out.println(image.getText());// 圖片文字  
  38.             } else// frame 標籤  
  39.             {  
  40.                 // 提取frame 裡src 屬性的鏈接如 frame src="test.html"  
  41.                 String frame = tag.getText();  
  42.                 int start = frame.indexOf("src=");  
  43.                 frame = frame.substring(start);  
  44.                 int end = frame.indexOf(" ");  
  45.                 if (end == -1)  
  46.                     end = frame.indexOf(">");  
  47.                 frame = frame.substring(5, end - 1);  
  48.                 System.out.println(frame);  
  49.             }  
  50.         }  
  51.     } catch (ParserException e) {  
  52.         e.printStackTrace();  
  53.     }  
  54. }  


- 簡單強大的StringBean
如果你想要網頁中去掉所有的標籤後剩下的文本,那就是用StringBean 吧。以下簡單的代碼可以幫你解決這樣的問題 :


  1. StringBean sb = new StringBean();  
  2. sb.setLinks(false);//設置結果中去點鏈接  
  3. sb.setURL(url);//設置你所需要濾掉網頁標籤的頁面url  
  4. System.out.println(sb.getStrings());//打印結果  
HtmlParser 提供了強大的類庫來處理網頁,由於本文旨在簡單的介紹,因此只是將與筆者後續爬蟲部分有關的關鍵類庫進行了示例說明. 感興趣的可以專門來研究一下HtmlParser 更為強大的類庫!

簡易爬蟲的實現流程說明 :
HttpClient 提供了便利的HTTP 協議訪問,使得我們可以很容易的得到某個網頁的源碼並保存在本地;HtmlParser 提供瞭如此簡便靈巧的類庫,可以從網頁中便捷的提取出指向其他網頁的超鏈接。筆者結合這兩個開源包,構建了一個簡易的網絡爬蟲 :

- 爬蟲(Crawler) 原理
學過數據結構的讀者都知道有向圖這種數據結構。如下圖所示,如果將網頁看成是圖中的某一個節點,而將網頁中指向其他網頁的鏈接看成是這個節點指向其他節點的邊,那麼我們很容易將整個Internet 上的網頁建模成一個有向圖。理論上,通過遍曆算法遍歷該圖,可以訪問到Internet 上的幾乎所有的網頁。最簡單的遍歷就是寬度優先以及深度優先。以下筆者實現的簡易爬蟲就是使用了寬度優先的爬行策略 :


- 簡易爬蟲實現流程
在看簡易爬蟲的實現代碼之前,先介紹一下簡易爬蟲爬取網頁的流程 :


各個類的源碼以及說明 :
對應上面的流程圖,簡易爬蟲由下面幾個類組成,各個類職責如下 :
- Crawler.java : 爬蟲的主方法入口所在的類,實現爬取的主要流程。
- LinkDb.java : 用來保存已經訪問的url 和待爬取的url 的類,提供url出對入隊操作。
- Queue.java : 實現了一個簡單的隊列,在LinkDb.java 中使用了此類。
- FileDownloader.java : 用來下載url 所指向的網頁。
- HtmlParserTool.java : 用來抽取出網頁中的鏈接。
- LinkFilter.java : 一個接口,實現其accept() 方法用來對抽取的鏈接進行過濾

下面是各個類的源碼,代碼中的註釋有比較詳細的說明 :
- Crawler.java :
  1. package john.crawler;  
  2.   
  3. import java.util.Set;  
  4.   
  5. public class Crawler {  
  6.     private String acceptHost = "";  
  7.       
  8.     public Crawler(){this("http://www.twt.edu.cn");}  
  9.     public Crawler(String ahost)  
  10.     {  
  11.         this.acceptHost = ahost;  
  12.     }  
  13.       
  14.     /* 使用種子url 初始化URL 隊列*/  
  15.     private void initCrawlerWithSeeds(String[] seeds)  
  16.     {  
  17.         for(int i=0;i
  18.             LinkDB.addUnvisitedUrl(seeds[i]);  
  19.     }  
  20.       
  21.     /* 爬取方法*/  
  22.     public void crawling(String[] seeds)  
  23.     {  
  24.         LinkFilter filter = new LinkFilter(){  
  25.             //提取以http://www.twt.edu.cn開頭的鏈接  
  26.             public boolean accept(String url) {  
  27.                 if(url.startsWith(acceptHost))  
  28.                     return true;  
  29.                 else  
  30.                     return false;  
  31.             }  
  32.         };  
  33.         //初始化URL 隊列  
  34.         initCrawlerWithSeeds(seeds);  
  35.         //循環條件:待抓取的鏈接不空且抓取的網頁不多於1000  
  36.         while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)  
  37.         {  
  38.             //隊頭URL 出對  
  39.             String visitUrl=LinkDB.unVisitedUrlDeQueue();  
  40.             if(visitUrl==null)  
  41.                 continue;  
  42.             FileDownLoader downLoader=new FileDownLoader();  
  43.             //下載網頁  
  44.             downLoader.downloadFile(visitUrl);  
  45.             //該url 放入到已訪問的URL 中  
  46.             LinkDB.addVisitedUrl(visitUrl);  
  47.             //提取出下載網頁中的URL  
  48.               
  49.             Set links=HtmlParserTool.extracLinks(visitUrl,filter);  
  50.             //新的未訪問的URL 入隊  
  51.             for(String link:links)  
  52.             {  
  53.                     LinkDB.addUnvisitedUrl(link);  
  54.             }  
  55.         }  
  56.     }  
  57.     //main 方法入口  
  58.     public static void main(String[]args)  
  59.     {  
  60.         Crawler crawler = new Crawler("http://www.twt.edu.cn");  
  61.         crawler.crawling(new String[]{"http://www.twt.edu.cn"});  
  62.     }  
  63. }  

- LinkDB.java :
  1. package john.crawler;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5.   
  6. /** 
  7. * 用來保存已經訪問過Url 和待訪問的Url 的類 
  8. */  
  9. public class LinkDB {  
  10.     // 已訪問的url 集合  
  11.     private static Set visitedUrl = new HashSet();  
  12.     // 待訪問的url 集合  
  13.     private static Queue unVisitedUrl = new Queue();  
  14.   
  15.     public static Queue getUnVisitedUrl() {  
  16.         return unVisitedUrl;  
  17.     }  
  18.   
  19.     public static void addVisitedUrl(String url) {  
  20.         visitedUrl.add(url);  
  21.     }  
  22.   
  23.     public static void removeVisitedUrl(String url) {  
  24.         visitedUrl.remove(url);  
  25.     }  
  26.   
  27.     public static String unVisitedUrlDeQueue() {  
  28.         return unVisitedUrl.deQueue();  
  29.     }  
  30.   
  31.     // 保證每個url 只被訪問一次  
  32.     public static void addUnvisitedUrl(String url) {  
  33.         if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)  
  34.                 && !unVisitedUrl.contians(url))  
  35.             unVisitedUrl.enQueue(url);  
  36.     }  
  37.   
  38.     public static int getVisitedUrlNum() {  
  39.         return visitedUrl.size();  
  40.     }  
  41.   
  42.     public static boolean unVisitedUrlsEmpty() {  
  43.         return unVisitedUrl.empty();  
  44.     }  
  45. }  

- FileDownLoader.java :
  1. package john.crawler;  
  2.   
  3. import java.io.DataOutputStream;  
  4. import java.io.File;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7.   
  8. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;  
  9. import org.apache.commons.httpclient.HttpClient;  
  10. import org.apache.commons.httpclient.HttpException;  
  11. import org.apache.commons.httpclient.HttpStatus;  
  12. import org.apache.commons.httpclient.methods.GetMethod;  
  13. import org.apache.commons.httpclient.params.HttpMethodParams;  
  14.   
  15. public class FileDownLoader {  
  16.     /** 
  17.      * 根據url 和網頁類型生成需要保存的網頁的文件名 去除掉url 中非文件名字符 
  18.      */  
  19.     public String getFileNameByUrl(String url, String contentType) {  
  20.         url = url.substring(7);// remove http://  
  21.         if (contentType.indexOf("html") != -1)// text/html  
  22.         {  
  23.             url = url.replaceAll("[\\?/:*|<>\"]""_") + ".html";  
  24.             return url;  
  25.         } else// 如application/pdf  
  26.         {  
  27.             return url.replaceAll("[\\?/:*|<>\"]""_") + "."  
  28.                     + contentType.substring(contentType.lastIndexOf("/") + 1);  
  29.         }  
  30.     }  
  31.   
  32.     /** 
  33.      * 保存網頁字節數組到本地文件 filePath 為要保存的文件的相對地址 
  34.      */  
  35.     private void saveToLocal(byte[] data, String filePath) {  
  36.         try {  
  37.             DataOutputStream out = new DataOutputStream(new FileOutputStream(  
  38.                     new File(filePath)));  
  39.             for (int i = 0; i < data.length; i++)  
  40.                 out.write(data[i]);  
  41.             out.flush();  
  42.             out.close();  
  43.         } catch (IOException e) {  
  44.             e.printStackTrace();  
  45.         }  
  46.     }  
  47.   
  48.     /* 下載url 指向的網頁 */  
  49.     public String downloadFile(String url) {  
  50.         String filePath = null;  
  51.         /* 1.生成HttpClinet 對象並設置參數 */  
  52.         HttpClient httpClient = new HttpClient();  
  53.         // 設置Http 連接超時5s  
  54.         httpClient.getHttpConnectionManager().getParams()  
  55.                 .setConnectionTimeout(5000);  
  56.   
  57.         /* 2.生成GetMethod 對象並設置參數 */  
  58.         GetMethod getMethod = new GetMethod(url);  
  59.         // 設置get 請求超時5s  
  60.         getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);  
  61.         // 設置請求重試處理  
  62.         getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  
  63.                 new DefaultHttpMethodRetryHandler());  
  64.   
  65.         /* 3.執行HTTP GET 請求 */  
  66.         try {  
  67.             int statusCode = httpClient.executeMethod(getMethod);  
  68.             // 判斷訪問的狀態碼  
  69.             if (statusCode != HttpStatus.SC_OK) {  
  70.                 System.err.println("Method failed: "  
  71.                         + getMethod.getStatusLine());  
  72.                 filePath = null;  
  73.             }  
  74.   
  75.             /* 4.處理HTTP 響應內容 */  
  76.             byte[] responseBody = getMethod.getResponseBody();// 讀取為字節數組  
  77.             // 根據網頁url 生成保存時的文件名  
  78.             filePath = "temp\\"  
  79.                     + getFileNameByUrl(url,  
  80.                             getMethod.getResponseHeader("Content-Type")  
  81.                                     .getValue());  
  82.             saveToLocal(responseBody, filePath);  
  83.         } catch (HttpException e) {  
  84.             // 發生致命的異常,可能是協議不對或者返回的內容有問題  
  85.             System.out.println("Please check your provided http address!");  
  86.             e.printStackTrace();  
  87.         } catch (IOException e) {  
  88.             // 發生網絡異常  
  89.             e.printStackTrace();  
  90.         } finally {  
  91.             // 釋放連接  
  92.             getMethod.releaseConnection();  
  93.         }  
  94.         return filePath;  
  95.     }  
  96.   
  97.     // 測試的main 方法  
  98.     public static void main(String[] args) {  
  99.         FileDownLoader downLoader = new FileDownLoader();  
  100.         downLoader.downloadFile("http://www.twt.edu.cn");  
  101.     }  
  102. }  

- HtmlParserTool.java :
  1. package john.crawler;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5.   
  6. import org.htmlparser.Node;  
  7. import org.htmlparser.NodeFilter;  
  8. import org.htmlparser.Parser;  
  9. import org.htmlparser.filters.NodeClassFilter;  
  10. import org.htmlparser.filters.OrFilter;  
  11. import org.htmlparser.tags.LinkTag;  
  12. import org.htmlparser.util.NodeList;  
  13. import org.htmlparser.util.ParserException;  
  14.   
  15. public class HtmlParserTool {  
  16.     // 獲取一個網站上的鏈接,filter 用來過濾鏈接  
  17.     public static Set extracLinks(String url, LinkFilter filter) {  
  18.   
  19.         Set links = new HashSet();  
  20.         try {  
  21.             Parser parser = new Parser(url);  
  22.             parser.setEncoding("gb2312");  
  23.             // 過濾標籤的filter,用來提取frame 標籤裡的src 屬性所表示的鏈接  
  24.             NodeFilter frameFilter = new NodeFilter() {  
  25.   
  26.                 @Override  
  27.                 public boolean accept(Node node) {  
  28.                     if (node.getText().startsWith("frame src="))  
  29.                         return true;  
  30.                     return false;  
  31.                 }  
  32.             };  
  33.             // OrFilter 來設置過濾 標籤,和 標籤  
  34.             OrFilter linkFilter = new OrFilter(new NodeClassFilter(  
  35.                     LinkTag.class), frameFilter);  
  36.             // 得到所有經過過濾的標籤  
  37.             NodeList list = parser.extractAllNodesThatMatch(linkFilter);  
  38.             for (int i = 0; i < list.size(); i++) {  
  39.                 Node tag = list.elementAt(i);  
  40.                 if (tag instanceof LinkTag)// 標籤  
  41.                 {  
  42.                     LinkTag link = (LinkTag) tag;  
  43.                     String linkUrl = link.getLink();// url  
  44.                     if (filter.accept(linkUrl))  
  45.                         links.add(linkUrl);  
  46.                 } else// 標籤  
  47.                 {  
  48.                     // 提取frame 裡src 屬性的鏈接如  
  49.                     String frame = tag.getText();  
  50.                     int start = frame.indexOf("src=");  
  51.                     frame = frame.substring(start);  
  52.                     int end = frame.indexOf(" ");  
  53.                     if (end == -1)  
  54.                         end = frame.indexOf(">");  
  55.                     String frameUrl = frame.substring(5, end - 1);  
  56.                     if (filter.accept(frameUrl))  
  57.                         links.add(frameUrl);  
  58.                 }  
  59.             }  
  60.         } catch (ParserException e) {  
  61.             e.printStackTrace();  
  62.         }  
  63.         return links;  
  64.     }  
  65.   
  66.     // 測試的main 方法  
  67.     public static void main(String[] args) {  
  68.         Set links = HtmlParserTool.extracLinks("http://www.twt.edu.cn",  
  69.                 new LinkFilter() {  
  70.                     // 提取以http://www.twt.edu.cn開頭的鏈接  
  71.                     public boolean accept(String url) {  
  72.                         if (url.startsWith("http://www.twt.edu.cn"))  
  73.                             return true;  
  74.                         else  
  75.                             return false;  
  76.                     }  
  77.   
  78.                 });  
  79.         for (String link : links)  
  80.             System.out.println(link);  
  81.     }  
  82. }  



- Queue.java : 

  1. package john.crawler;  
  2.   
  3. import java.util.LinkedList;  
  4.   
  5. /** 
  6. * 數據結構隊列 
  7. */  
  8. public class Queue {  
  9.   
  10.     private LinkedList queue=new LinkedList();  
  11.       
  12.     public void enQueue(T t)  
  13.     {  
  14.         queue.addLast(t);  
  15.     }  
  16.       
  17.     public T deQueue()  
  18.     {  
  19.         return queue.removeFirst();  
  20.     }  
  21.       
  22.     public boolean isQueueEmpty()  
  23.     {  
  24.         return queue.isEmpty();  
  25.     }  
  26.       
  27.     public boolean contians(T t)  
  28.     {  
  29.         return queue.contains(t);  
  30.     }  
  31.       
  32.     public boolean empty()  
  33.     {  
  34.         return queue.isEmpty();  
  35.     }  
  36. }  

- LinkFilter.java :
  1. package john.crawler;  
  2.   
  3. public interface LinkFilter {  
  4.     public boolean accept(String url);  
  5. }  

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...