使用HttpClient實現網絡爬蟲

1       什麼叫網絡爬蟲

網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱爲網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲

2       網絡爬蟲的分類

網絡爬蟲按照系統結構和實現技術,大致可以分爲以下幾種類型:

1、通用網絡爬蟲(General Purpose Web Crawler) ;

2、主題網絡爬蟲(Topical Web Crawler) ;

3、深層網絡爬蟲(Deep Web Crawler)。

實際應用中通常是將系統幾種爬蟲技術相互結合。

2.1   通用網絡爬蟲

通用網絡爬蟲根據預先設定的一個或若干初始種子URL開始,以此獲得初始網頁上的URL列表,在爬行過程中不斷從URL隊列中獲一個的URL,進而訪問並下載該頁面。頁面下載後頁面解析器去掉頁面上的HTML標記後得到頁面內容,將摘要、URL等信息保存到Web數據庫中,同時抽取當前頁面上新的URL,保存到URL隊列,直到滿足系統停止條件。其工作流程如圖1所示。

 

通用爬蟲主要存在以下幾方面的侷限性:

1)  由於抓取目標是儘可能大的覆蓋網絡,所以爬行的結果中包含大量用戶不需要的網頁;

2)  不能很好地搜索和獲取信息含量密集且具有一定結構的數據;

3)  通用搜索引擎大多是基於關鍵字的檢索,對於支持語義信息的查詢和索引擎智能化的要求難以實現。

由此可見,通用爬蟲想在爬行網頁時,既保證網頁的質量和數量,又要保證網頁的時效性是很難實現的。

2.2   主題網絡爬蟲

主題爬蟲並不追求大的覆蓋率,也不是全盤接受所有的網頁和URL,它根據既定的抓取目標,有選擇的訪問萬維網上的網頁與相關的鏈接,獲取所需要的信息,不僅客服了通用爬蟲存在的問題,而H-返回的數據資源更精確。主題爬蟲的基本工作原理是按照預先確定的主題,分析超鏈接和剛剛抓取的網頁內容,獲取下一個要爬行的URL,儘可能保證多爬行與主題相關的網頁,因此主題爬蟲要解決以下關鍵問題:

1)  如何判定一個已經抓取的網頁是否與主題相關;

2)  如何過濾掉海量的網頁中與主題不相關的或者相關度較低的網頁;

3)  如何有目的、有控制的抓取與特定主題相關的web頁面信息;

4)  如何決定待訪問URL的訪問次序;

5)  如何提高主題爬蟲的覆蓋度;

6)  如何協調抓取目標的描述或定義與網頁分析算法及候選URL排序算法之問的關係;

7)  如何尋找和發現高質量網頁和關鍵資源。高質量網頁和關鍵資源不僅可以大大提高主題爬蟲蒐集Web頁面的效率和質量,還可以爲主題表示模型的優化等應用提供支持

2.2.1  模板設計

主題爬蟲的目標是儘可能多的發現和蒐集與預定主題相關的網頁,其最大特點在於具備分析網頁內容和判別主題相關度的能力。根據主題爬蟲的工作原理,下面設計了一個主題爬蟲系統,主要有頁面採集模塊、頁面分析模塊、相關度計算模塊、頁面過濾模塊和鏈接排序模塊幾部分組成,其總體功能模塊結構如

圖2所示。

 

頁面採集模塊:主要是根據待訪問URL隊列進行頁面下載,再交給網頁分析模型處理以抽取網頁主題向量空間模型。該模塊是任何爬蟲系統都必不可少的模塊。頁面分析模塊:該模塊的功能是對採集到的頁面進行分析,主要用於連接超鏈接排序模塊和頁面相關度計算模塊。

頁面相關度計算模塊:該模塊是整個系統的核心模塊,主要用於評估與主題的相關度,並提供相關的爬行策略用以指導爬蟲的爬行過程。URL的超鏈接評價得分越高,爬行的優先級就越高。其主要思想是,在系統爬行之前,頁面相關度計算模塊根據用戶輸入的關鍵字和初始文本信息進行學習,訓練一個頁面相關度評價模型。當一個被認爲是主題相關的頁面爬行下來之後,該頁面就被送入頁面相關度評價器計算其主題相關度值,若該值大於或等於給定的某閡值,則該頁面就被存入頁面庫,否則丟棄。

頁面過濾模塊:過濾掉與主題無關的鏈接,同時將該URL及其所有隱含的子鏈接一併去除。通過過濾,爬蟲就無需遍歷與主題不相關的頁面,從而保證了爬行效率。

排序模塊:將過濾後頁面按照優先級高低加入到待訪問的URL隊列裏

2.2.2  主題爬蟲流程設計

主題爬蟲需要根據一定的網頁分析算法,過濾掉與主題無關的鏈接,保留有用的鏈接並將其放入等待抓取的URL隊列。然後,它會根據一定的搜索策略從待抓取的隊列中選擇下一個要抓取的URL,並重覆上述過程,直到滿足系統停止條件爲止。所有被抓取網頁都會被系統存儲,經過一定的分析、過濾,然後建立索引,以便用戶查詢和檢索;這一過程所得到的分析結果可以對以後的抓取過程提供反饋和指導。其工作流程如圖3所示。

 

 

2.3   深層網絡爬蟲

1994年Dr.jillEllsworth提出DeepWeb(深層頁面)的概念,即DeepWeb是指普通搜索引擎難以發現的信息內容的Web頁面¨。DeepWeb中的信息量比普通的網頁信息量多,而且質量更高。但是普通的搜索引擎由於技術限制而蒐集不到這些高質量、高權威的信息。這些信息通常隱藏在深度Web頁面的大型動態數據庫中,涉及數據集成、中文語義識別等諸多領域。如此龐大的信息資源如果沒有合理的、高效的方法去獲取,將是巨大的損失。因此,對於深度網爬行技術的研究具有極爲重大的現實意義和理論價值。

  常規的網絡爬蟲在運行中無法發現隱藏在普通網頁中的信息和規律,缺乏一定的主動性和智能性。比如需要輸入用戶名和密碼的頁面,或者包含頁碼導航的頁面均無法爬行。深度爬蟲的設計針對常規網絡爬蟲的這些不足,將其結構做以改進,增加了表單分析和頁面狀態保持兩個部分,其結構如圖4所示,通過分析網頁的結構並將其歸類爲普通網頁或存在更多信息的深度網頁,針對深度網頁構造合適的表單參數並且提交,以得到更多的頁面。深度爬蟲的流程圖如圖4所示。深度爬蟲與常規爬蟲的不同是,深度爬蟲在下載完成頁面之後並沒有立即遍歷其中的所有超鏈接,而是使用一定的算法將其進行分類,對於不同的類別採取不同的方法計算查詢參數,並將參數再次提交到服務器。如果提交的查詢參數正確,那麼將會得到隱藏的頁面和鏈接。深度爬蟲的目標是儘可能多地訪問和收集互聯網上的網頁,由於深度頁面是通過提交表單的方式訪問,因此爬行深度頁面存在以下三個方面的困難:

1)深度爬蟲需要有高效的算法去應對數量巨大的深層頁面數據;

2)很多服務器端DeepWeb要求校驗表單輸入,如用戶名、密碼、校驗碼等,如果校驗失敗,將不能爬到DeepWeb數據;

3)需要JavaScript等腳本支持分析客戶端DeepWeb。

3 爬蟲的技術實現

通過Apache Jakarta Common 中的子項目HttpClient與 純JAVA編寫的HtmlParser解析庫來共同實現網絡爬蟲

3.1   什麼是HttpClient

HttpClient提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本,能使得我們可以很容易的得到某個網頁的源碼並保存在本地。其主要的功能介紹有:

1)     實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

2)     支持自動轉向

3)     支持 HTTPS 協議

4)     支持代理服務器

3.2   什麼是HtmlParser

HtmlParser是一個純的java寫的html標準通用標記語言下的一個應用)解析的庫,它不依賴於其它的java庫文件,主要用於改造或提取Html。它能超高速解析html,並且不會出錯。或者毫不誇張地說,Htmlparser就是目前最好的html解析和分析的工具。無論你是想抓取網頁數據還是改造Html的內容。

它還提供了簡便靈巧的類庫,可以從網頁中便捷的提取出指向其他網頁的超鏈接。

3.3   HttpClient 簡介

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

3.4   HtmlParser 簡介

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

3.5   開發環境搭建

3.6   HttpClient 基本類庫使用

HttpClinet 提供了幾個類來支持 HTTP 訪問。通過一些示例代碼來熟悉和說明這些類的功能和使用。 HttpClient 提供的 HTTP 的訪問主要是通過 GetMethod 類和 PostMethod 類來實現的,他們分別對應了 HTTP Get 請求與 Http Post 請求。

GetMethod

使用 GetMethod 來訪問一個 URL 對應的網頁,需要如下一些步驟。

  1. 生成一個 HttpClinet 對象並設置相應的參數。
  2. 生成一個 GetMethod 對象並設置響應的參數。
  3. 用 HttpClinet 生成的對象來執行 GetMethod 生成的 Get 方法。
  4. 處理響應狀態碼。
  5. 若響應正常,處理 HTTP 響應內容。
  6. 釋放連接。

如下的代碼展示了這些步驟,其中的註釋對代碼進行了較詳細的說明。

代碼如下:

 

/* 1 生成 HttpClinet 對象並設置參數*/
		HttpClient httpClient=new HttpClient();
		  //設置 Http 連接超時爲5秒
		httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
		  /*2 生成 GetMethod 對象並設置參數*/
		GetMethod getMethod=new GetMethod(url);  
		  //設置 get 請求超時爲 5 秒
		getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);
		  //設置請求重試處理,用的是默認的重試處理:請求三次
		getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler());
		  /*3 執行 HTTP GET 請求*/
		try{
			int statusCode = httpClient.executeMethod(getMethod);
			/*4 判斷訪問的狀態碼*/
			if (statusCode != HttpStatus.SC_OK){
				System.err.println("Method failed: "+ getMethod.getStatusLine());
		     }
				/*5 處理 HTTP 響應內容*/
		      //HTTP響應頭部信息,這裏簡單打印
			Header[] headers=getMethod.getResponseHeaders();
			for(Header  h:  headers)
		        System.out.println(h.getName()+" "+h.getValue());*/
		      //讀取 HTTP 響應內容,這裏簡單打印網頁內容
		      byte[] responseBody = getMethod.getResponseBody();//讀取爲字節數組
		      System.out.println(new String(responseBody));
		      //讀取爲 InputStream,在網頁內容數據量大時候推薦使用
		      InputStream response = getMethod.getResponseBodyAsStream();//
		      …
		}catch (HttpException e){
			// 發生致命的異常,可能是協議不對或者返回的內容有問題
			System.out.println("Please check your provided http address!");
			e.printStackTrace();
		}catch (IOException e) {// 發生網絡異常
		     e.printStackTrace();
		} finally {
		    /*6 .釋放連接*/
			getMethod.releaseConnection();
		}

 

 

 

 

 

這裏值得注意的幾個地方是:

  1. 設置連接超時和請求超時,這兩個超時的意義不同,需要分別設置。
  2. 響應狀態碼的處理。
  3. 返回的結果可以爲字節數組,也可以爲 InputStream,而後者在網頁內容數據量較大的時候推薦使用。

在處理返回結果的時候可以根據自己的需要,進行相應的處理。如筆者是需要保存網頁到本地,因此就可以寫一個 saveToLocaleFile(byte[] data, String filePath) 的方法,將字節數組保存成本地文件。後續的簡易爬蟲部分會有相應的介紹。

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 對象,並設置相應的請求參數。

代碼:

PostMethod postMethod = new PostMethod("http://dict.cn/");

postMethod.setRequestBody(new NameValuePair[]{new NameValuePair("q","java")});

3.7   HtmlParser 基本類庫使用

HtmlParser 提供了強大的類庫來處理 Internet 上的網頁,可以實現對網頁特定內容的提取和修改。下面通過幾個例子來介紹 HtmlParser 的一些使用。這些例子其中的代碼,有部分用在了後面介紹的簡易爬蟲中。以下所有的代碼和方法都在在類 HtmlParser.Test.java 裏,這是筆者編寫的一個用來測試 HtmlParser 用法的類。

  • 迭代遍歷網頁所有節點

網頁是一個半結構化的嵌套文本文件,有類似 XML 文件的樹形嵌套結構。使用HtmlParser 可以讓我們輕易的迭代遍歷網頁的所有節點。如下代碼展示瞭如何來實現這個功能。

代碼:

// 循環訪問所有節點,輸出包含關鍵字的值節點

         public static void extractKeyWordText(String url, String keyword) {

                 try {

            //生成一個解析器對象,用網頁的 url 作爲參數

                          Parser parser = new Parser(url);

                          //設置網頁的編碼,這裏只是請求了一個 gb2312 編碼網頁

                          parser.setEncoding("gb2312");

                          //迭代所有節點, null 表示不使用 NodeFilter

                          NodeList list = parser.parse(null);

            //從初始的節點列表跌倒所有的節點

                          processNodeList(list, keyword);

                 } catch (ParserException e) {

                          e.printStackTrace();

                 }

         }

 

         private static void processNodeList(NodeList list, String keyword) {

                 //迭代開始

                 SimpleNodeIterator iterator = list.elements();

                 while (iterator.hasMoreNodes()) {

                          Node node = iterator.nextNode();

                          //得到該節點的子節點列表

                          NodeList childList = node.getChildren();

                          //孩子節點爲空,說明是值節點

                          if (null == childList)

                          {

                                   //得到值節點的值

                                   String result = node.toPlainTextString();

                                   //若包含關鍵字,則簡單打印出來文本

                                   if (result.indexOf(keyword) != -1)

                                            System.out.println(result);

                          } //end if

                          //孩子節點不爲空,繼續迭代該孩子節點

                          else

                          {

                                   processNodeList(childList, keyword);

                          }//end else

                 }//end wile

         }

上面的中有兩個方法:

  1. private static void processNodeList(NodeList list, String keyword)

該方法是用類似深度優先的方法來迭代遍歷整個網頁節點,將那些包含了某個關鍵字的值節點的值打印出來。

  1. public static void extractKeyWordText(String url, String keyword)

該方法生成針對 String 類型的 url 變量代表的某個特定網頁的解析器,調用 1中的方法實現簡單的遍歷。

以上的代碼展示瞭如何迭代所有的網頁,更多的工作可以在此基礎上展開。比如找到某個特定的網頁內部節點,其實就可以在遍歷所有的節點基礎上來判斷,看被迭代的節點是否滿足特定的需要。

  1. 使用 NodeFilter

NodeFilter 是一個接口,任何一個自定義的 Filter 都需要實現這個接口中的 boolean accept() 方法。如果希望迭代網頁節點的時候保留當前節點,則在節點條件滿足的情況下返回 true;否則返回 false。HtmlParse 裏提供了很多實現了 NodeFilter 接口的類,下面就一些筆者所用到的,以及常用的 Filter 做一些介紹:

  1. 對 Filter 做邏輯操作的 Fitler 有:AndFilterNotFilter ,OrFilterXorFilter
  2. 這些 Filter 來組合不同的 Filter,形成滿足兩個 Filter 邏輯關係結果的 Filter。
  3. 判斷節點的孩子,兄弟,以及父親節點情況的 Filter 有:HasChildFilterHasParentFilterHasSiblingFilter
  4. 判斷節點本身情況的 Filter 有 HasAttributeFilter:判讀節點是否有特定屬性;LinkStringFilter:判斷節點是否是具有特定模式 (pattern) url 的節點;

TagNameFilter:判斷節點是否具有特定的名字;NodeClassFilter:判讀節點是否是某個 HtmlParser 定義好的 Tag 類型。在 org.htmlparser.tags 包下有對應 Html標籤的各種 Tag,例如 LinkTag,ImgeTag 等。

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

代碼:

// 獲取一個網頁上所有的鏈接和圖片鏈接

public static void extracLinks(String url) {

       try {

         Parser parser = new Parser(url);

         parser.setEncoding("gb2312");

//過濾 <frame> 標籤的 filter,用來提取 frame 標籤裏的 src 屬性所、表示的鏈接

         NodeFilter frameFilter = new NodeFilter() {

public boolean accept(Node node) {

         if (node.getText().startsWith("frame src=")) {

                 return true;

         } else {

                 return false;

         }

         }

};

//OrFilter 來設置過濾 <a> 標籤,<img> 標籤和 <frame> 標籤,三個標籤是 or 的關係

OrFilte rorFilter = new OrFilter(new NodeClassFilter(LinkTag.class), new

NodeClassFilter(ImageTag.class));

          OrFilter linkFilter = new OrFilter(orFilter, frameFilter);

         //得到所有經過過濾的標籤

         NodeList list = parser.extractAllNodesThatMatch(linkFilter);

         for (int i = 0; i < list.size(); i++) {

                  Node tag = list.elementAt(i);

                 if (tag instanceof LinkTag)//<a> 標籤

                 {

                          LinkTag link = (LinkTag) tag;

                          String linkUrl = link.getLink();//url

                          String text = link.getLinkText();//鏈接文字

                          System.out.println(linkUrl + "**********" + text);

                 }

                 else if (tag instanceof ImageTag)//<img> 標籤

                 {

                          ImageTag image = (ImageTag) list.elementAt(i);

                          System.out.print(image.getImageURL() + "********");//圖片地址

                          System.out.println(image.getText());//圖片文字

                 }

                 else//<frame> 標籤

                 {

//提取 frame 裏 src 屬性的鏈接如 <frame src="test.html"/>

                          String frame = tag.getText();

                          int start = frame.indexOf("src=");

                          frame = frame.substring(start);

                          int end = frame.indexOf(" ");

                          if (end == -1)

                                   end = frame.indexOf(">");

                          frame = frame.substring(5, end - 1);

                          System.out.println(frame);

                 }

         }

} catch (ParserException e) {

                          e.printStackTrace();

}

}

簡單強大的 StringBean

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

StringBean sb = new StringBean();

sb.setLinks(false);//設置結果中去點鏈接

sb.setURL(url);//設置你所需要濾掉網頁標籤的頁面 url

System.out.println(sb.getStrings());//打印結果

HtmlParser 提供了強大的類庫來處理網頁,由於本文旨在簡單的介紹,因此只是將與筆者後續爬蟲部分有關的關鍵類庫進行了示例說明。感興趣的讀者可以專門來研究一下 HtmlParser 更爲強大的類庫。

簡易爬蟲的實現

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

網頁關係的建模圖

 

簡易爬蟲實現流程

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

爬蟲流程圖

 

各個類的源碼以及說明

對應上面的流程圖,簡易爬蟲由下面幾個類組成,各個類職責如下:

Crawler.java:爬蟲的主方法入口所在的類,實現爬取的主要流程。

LinkDb.java:用來保存已經訪問的 url 和待爬取的 url 的類,提供url出對入隊操作。

Queue.java: 實現了一個簡單的隊列,在 LinkDb.java 中使用了此類。

FileDownloader.java:用來下載 url 所指向的網頁。

HtmlParserTool.java: 用來抽取出網頁中的鏈接。

LinkFilter.java:一個接口,實現其 accept() 方法用來對抽取的鏈接進行過濾。

下面是各個類的源碼,代碼中的註釋有比較詳細的說明。

Crawler.java

package com.ie;

 

import java.util.Set;

public class Crawler {

         /* 使用種子 url 初始化 URL 隊列*/

         private void initCrawlerWithSeeds(String[] seeds)

         {

                 for(int i=0;i<seeds.length;i++)

                          LinkDB.addUnvisitedUrl(seeds[i]);

         }

        

         /* 爬取方法*/

         public void crawling(String[] seeds)

         {

                 LinkFilter filter = new LinkFilter(){

                          //提取以 http://www.twt.edu.cn 開頭的鏈接

                          public boolean accept(String url) {

                                   if(url.startsWith("http://www.twt.edu.cn"))

                                            return true;

                                   else

                                            return false;

                          }

                 };

                 //初始化 URL 隊列

                 initCrawlerWithSeeds(seeds);

                 //循環條件:待抓取的鏈接不空且抓取的網頁不多於 1000

                 while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)

                 {

                          //隊頭 URL 出對

                          String visitUrl=LinkDB.unVisitedUrlDeQueue();

                          if(visitUrl==null)

                                   continue;

                          FileDownLoader downLoader=new FileDownLoader();

                          //下載網頁

                          downLoader.downloadFile(visitUrl);

                          //該 url 放入到已訪問的 URL 中

                          LinkDB.addVisitedUrl(visitUrl);

                          //提取出下載網頁中的 URL

                         

                          Set<String> links=HtmlParserTool.extracLinks(visitUrl,filter);

                          //新的未訪問的 URL 入隊

                          for(String link:links)

                          {

                                            LinkDB.addUnvisitedUrl(link);

                          }

                 }

         }

         //main 方法入口

         public static void main(String[]args)

         {

                 Crawler crawler = new Crawler();

                 crawler.crawling(new String[]{"http://www.twt.edu.cn"});

         }

}

LinkDb.java

package com.ie;

 

import java.util.HashSet;

import java.util.Set;

 

/**

 * 用來保存已經訪問過 Url 和待訪問的 Url 的類

 */

public class LinkDB {

 

         //已訪問的 url 集合

         private static Set<String> visitedUrl = new HashSet<String>();

         //待訪問的 url 集合

         private static Queue<String> unVisitedUrl = new Queue<String>();

 

        

         public static Queue<String> getUnVisitedUrl() {

                 return unVisitedUrl;

         }

 

         public static void addVisitedUrl(String url) {

                 visitedUrl.add(url);

         }

 

         public static void removeVisitedUrl(String url) {

                 visitedUrl.remove(url);

         }

 

         public static String unVisitedUrlDeQueue() {

                 return unVisitedUrl.deQueue();

         }

 

         // 保證每個 url 只被訪問一次

         public static void addUnvisitedUrl(String url) {

                 if (url != null && !url.trim().equals("")

 && !visitedUrl.contains(url)

                                   && !unVisitedUrl.contians(url))

                          unVisitedUrl.enQueue(url);

         }

 

         public static int getVisitedUrlNum() {

                 return visitedUrl.size();

         }

 

         public static boolean unVisitedUrlsEmpty() {

                 return unVisitedUrl.empty();

         }

}

Queue.java

package com.ie;

 

import java.util.LinkedList;

/**

 * 數據結構隊列

 */

public class Queue<T> {

 

         private LinkedList<T> queue=new LinkedList<T>();

        

         public void enQueue(T t)

         {

                 queue.addLast(t);

         }

        

         public T deQueue()

         {

                 return queue.removeFirst();

         }

        

         public boolean isQueueEmpty()

         {

                 return queue.isEmpty();

         }

        

         public boolean contians(T t)

         {

                 return queue.contains(t);

         }

        

         public boolean empty()

         {

                 return queue.isEmpty();

         }

}

 FileDownLoader.java

package com.ie;

 

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;

import org.apache.commons.httpclient.HttpClient;

import org.apache.commons.httpclient.HttpException;

import org.apache.commons.httpclient.HttpStatus;

import org.apache.commons.httpclient.methods.GetMethod;

import org.apache.commons.httpclient.params.HttpMethodParams;

 

public class FileDownLoader {

        

         /**根據 url 和網頁類型生成需要保存的網頁的文件名

          *去除掉 url 中非文件名字符

          */

         public  String getFileNameByUrl(String url,String contentType)

         {

                 url=url.substring(7);//remove http://

                 if(contentType.indexOf("html")!=-1)//text/html

                 {

                          url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";

                          return url;

                 }

                 else//如application/pdf

                 {

return url.replaceAll("[\\?/:*|<>\"]", "_")+"."+ \

          contentType.substring(contentType.lastIndexOf("/")+1);

                 }       

         }

 

         /**保存網頁字節數組到本地文件

          * filePath 爲要保存的文件的相對地址

          */

         private void saveToLocal(byte[] data,String filePath)

         {

                 try {

                          DataOutputStream out=new DataOutputStream(

new FileOutputStream(new File(filePath)));

                          for(int i=0;i<data.length;i++)

                          out.write(data[i]);

                          out.flush();

                          out.close();

                 } catch (IOException e) {

                          e.printStackTrace();

                 }

         }

 

         /*下載 url 指向的網頁*/

         public String  downloadFile(String url)

         {

                   String filePath=null;

                   /* 1.生成 HttpClinet 對象並設置參數*/

                   HttpClient httpClient=new HttpClient();

                   //設置 Http 連接超時 5s

                            httpClient.getHttpConnectionManager().getParams().

setConnectionTimeout(5000);

                  

                   /*2.生成 GetMethod 對象並設置參數*/

                    GetMethod getMethod=new GetMethod(url);  

                   //設置 get 請求超時 5s

                   getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);

                   //設置請求重試處理

                   getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

                          new DefaultHttpMethodRetryHandler());

                  

                   /*3.執行 HTTP GET 請求*/

                   try{

                            int statusCode = httpClient.executeMethod(getMethod);

                            //判斷訪問的狀態碼

                            if (statusCode != HttpStatus.SC_OK)

                            {

System.err.println("Method failed: "+ getMethod.getStatusLine());

                                     filePath=null;

                            }

                           

                            /*4.處理 HTTP 響應內容*/

 byte[] responseBody = getMethod.getResponseBody();//讀取爲字節數組

                            //根據網頁 url 生成保存時的文件名

filePath="temp\\"+getFileNameByUrl(url,

                    getMethod.getResponseHeader("Content-Type").getValue());

                          saveToLocal(responseBody,filePath);

                   } catch (HttpException e) {

                                      // 發生致命的異常,可能是協議不對或者返回的內容有問題

                                      System.out.println("Please check your provided http

address!");

                                      e.printStackTrace();

                                     } catch (IOException e) {

                                      // 發生網絡異常

                                      e.printStackTrace();

                                     } finally {

                                      // 釋放連接

                                      getMethod.releaseConnection();              

                                     }

                                     return filePath;

         }

         //測試的 main 方法

         public static void main(String[]args)

         {

                 FileDownLoader downLoader = new FileDownLoader();

                 downLoader.downloadFile("http://www.twt.edu.cn");

         }

}

HtmlParserTool.java

package com.ie;

 

import java.util.HashSet;

import java.util.Set;

 

import org.htmlparser.Node;

import org.htmlparser.NodeFilter;

import org.htmlparser.Parser;

import org.htmlparser.filters.NodeClassFilter;

import org.htmlparser.filters.OrFilter;

import org.htmlparser.tags.LinkTag;

import org.htmlparser.util.NodeList;

import org.htmlparser.util.ParserException;

 

public class HtmlParserTool {

         // 獲取一個網站上的鏈接,filter 用來過濾鏈接

         public static Set<String> extracLinks(String url,LinkFilter filter) {

 

                 Set<String> links = new HashSet<String>();

                 try {

                          Parser parser = new Parser(url);

                          parser.setEncoding("gb2312");

                          // 過濾 <frame >標籤的 filter,用來提取 frame 標籤裏的 src 屬性所表示的鏈接

                          NodeFilter frameFilter = new NodeFilter() {

                                   public boolean accept(Node node) {

                                            if (node.getText().startsWith("frame src=")) {

                                                    return true;

                                            } else {

                                                    return false;

                                            }

                                   }

                          };

                          // OrFilter 來設置過濾 <a> 標籤,和 <frame> 標籤

                          OrFilter linkFilter = new OrFilter(new NodeClassFilter(

                                            LinkTag.class), frameFilter);

                          // 得到所有經過過濾的標籤

                          NodeList list = parser.extractAllNodesThatMatch(linkFilter);

                          for (int i = 0; i < list.size(); i++) {

                                   Node tag = list.elementAt(i);

                                   if (tag instanceof LinkTag)// <a> 標籤

                                   {

                                            LinkTag link = (LinkTag) tag;

                                            String linkUrl = link.getLink();// url

                                            if(filter.accept(linkUrl))

                                                    links.add(linkUrl);

                                   } else// <frame> 標籤

                                   {

                         // 提取 frame 裏 src 屬性的鏈接如 <frame src="test.html"/>

                                            String frame = tag.getText();

                                            int start = frame.indexOf("src=");

                                            frame = frame.substring(start);

                                            int end = frame.indexOf(" ");

                                            if (end == -1)

                                                    end = frame.indexOf(">");

                                            String frameUrl = frame.substring(5, end - 1);

                                           if(filter.accept(frameUrl))

                                                    links.add(frameUrl);

                                   }

                          }

                 } catch (ParserException e) {

                          e.printStackTrace();

                 }

                 return links;

         }

         //測試的 main 方法

         public static void main(String[]args)

         {

Set<String> links = HtmlParserTool.extracLinks(

"http://www.twt.edu.cn",new LinkFilter()

                 {

                          //提取以 http://www.twt.edu.cn 開頭的鏈接

                          public boolean accept(String url) {

                                   if(url.startsWith("http://www.twt.edu.cn"))

                                            return true;

                                   else

                                            return false;

                          }

                         

                 });

                 for(String link : links)

                          System.out.println(link);

         }

}

LinkFilter.java

package com.ie;

 

public interface LinkFilter {

         public boolean accept(String url);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章