1. 程式人生 > >使用 HttpClient 和 HtmlParser 實現簡易爬蟲

使用 HttpClient 和 HtmlParser 實現簡易爬蟲

HttpClient 與 HtmlParser 簡介
本小結簡單的介紹一下 HttpClinet 和 HtmlParser 兩個開源的專案,以及他們的網站和提供下載的地址。

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 元件。筆者使用的是 HttpClient3.1。

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

開發環境的搭建


筆者所使用的開發環境是 Eclipse Europa,此開發工具可以在 www.eclipse.org 免費的下載;JDK是1.6,你也可以在 www.java.sun.com 站點下載,並且在作業系統中配置好環境變數。在 Eclipse 中建立一個 JAVA 工程,在工程的 Build Path 中匯入下載的Commons-httpClient3.1.Jar,htmllexer.jar 以及 htmlparser.jar 檔案。

圖 1. 開發環境搭建


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

GetMethod
使用 GetMethod 來訪問一個 URL 對應的網頁,需要如下一些步驟。
生成一個 HttpClinet 物件並設定相應的引數。
生成一個 GetMethod 物件並設定響應的引數。
用 HttpClinet 生成的物件來執行 GetMethod 生成的 Get 方法。
處理響應狀態碼。
若響應正常,處理 HTTP 響應內容。
釋放連線。
清單 1 的程式碼展示了這些步驟,其中的註釋對程式碼進行了較詳細的說明。

清單 1.

/* 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();		   
		    }

這裡值得注意的幾個地方是:
設定連線超時和請求超時,這兩個超時的意義不同,需要分別設定。
響應狀態碼的處理。
返回的結果可以為位元組陣列,也可以為 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 請求引數的值,來動態的獲得返回的網頁結果。清單 2 中的程式碼展示瞭如何建立一個 Post 物件,並設定相應的請求引數。

清單2
PostMethod postMethod = new PostMethod("http://dict.cn/");
postMethod.setRequestBody(new NameValuePair[]{new NameValuePair("q","java")});

HtmlParser 基本類庫使用
HtmlParser 提供了強大的類庫來處理 Internet 上的網頁,可以實現對網頁特定內容的提取和修改。下面通過幾個例子來介紹 HtmlParser 的一些使用。這些例子其中的程式碼,有部分用在了後面介紹的簡易爬蟲中。以下所有的程式碼和方法都在在類 HtmlParser.Test.java 裡,這是筆者編寫的一個用來測試 HtmlParser 用法的類。
迭代遍歷網頁所有節點
網頁是一個半結構化的巢狀文字檔案,有類似 XML 檔案的樹形巢狀結構。使用HtmlParser 可以讓我們輕易的迭代遍歷網頁的所有節點。清單 3 展示瞭如何來實現這個功能。

清單 3
// 迴圈訪問所有節點,輸出包含關鍵字的值節點
	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
	}

上面的中有兩個方法:
private static void processNodeList(NodeList list, String keyword)
該方法是用類似深度優先的方法來迭代遍歷整個網頁節點,將那些包含了某個關鍵字的值節點的值打印出來。
public static void extractKeyWordText(String url, String keyword)
該方法生成針對 String 型別的 url 變數代表的某個特定網頁的解析器,呼叫 1中的方法實現簡單的遍歷。
清單 3 的程式碼展示瞭如何迭代所有的網頁,更多的工作可以在此基礎上展開。比如找到某個特定的網頁內部節點,其實就可以在遍歷所有的節點基礎上來判斷,看被迭代的節點是否滿足特定的需要。


使用 NodeFilter
NodeFilter 是一個介面,任何一個自定義的 Filter 都需要實現這個介面中的 boolean accept() 方法。如果希望迭代網頁節點的時候保留當前節點,則在節點條件滿足的情況下返回 true;否則返回 false。HtmlParse 裡提供了很多實現了 NodeFilter 介面的類,下面就一些筆者所用到的,以及常用的 Filter 做一些介紹:
對 Filter 做邏輯操作的 Fitler 有:AndFilter,NotFilter ,OrFilter,XorFilter。
這些 Filter 來組合不同的 Filter,形成滿足兩個 Filter 邏輯關係結果的 Filter。
判斷節點的孩子,兄弟,以及父親節點情況的 Filter 有:HasChildFilter HasParentFilter,HasSiblingFilter。
判斷節點本身情況的 Filter 有 HasAttributeFilter:判讀節點是否有特定屬性;LinkStringFilter:判斷節點是否是具有特定模式 (pattern) url 的節點;
TagNameFilter:判斷節點是否具有特定的名字;NodeClassFilter:判讀節點是否是某個 HtmlParser 定義好的 Tag 型別。在 org.htmlparser.tags 包下有對應 Html標籤的各種 Tag,例如 LinkTag,ImgeTag 等。
還有其他的一些 Filter 在這裡不一一列舉了,可以在 org.htmlparser.filters 下找到。
清單 4 展示瞭如何使用上面提到過的一些 filter 來抽取網頁中的 <a> 標籤裡的 href屬性值,<img> 標籤裡的 src 屬性值,以及 <frame> 標籤裡的 src 的屬性值。

清單4

// 獲取一個網頁上所有的連結和圖片連結
	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 吧。以下簡單的程式碼可以幫你解決這樣的問題:

清單5

StringBean sb = new StringBean();
sb.setLinks(false);//設定結果中去點連結
sb.setURL(url);//設定你所需要濾掉網頁標籤的頁面 url
System.out.println(sb.getStrings());//列印結果

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

簡易爬蟲的實現
HttpClient 提供了便利的 HTTP 協議訪問,使得我們可以很容易的得到某個網頁的原始碼並儲存在本地;HtmlParser 提供瞭如此簡便靈巧的類庫,可以從網頁中便捷的提取出指向其他網頁的超連結。筆者結合這兩個開源包,構建了一個簡易的網路爬蟲。
爬蟲 (Crawler) 原理
學過資料結構的讀者都知道有向圖這種資料結構。如下圖所示,如果將網頁看成是圖中的某一個節點,而將網頁中指向其他網頁的連結看成是這個節點指向其他節點的邊,那麼我們很容易將整個 Internet 上的網頁建模成一個有向圖。理論上,通過遍歷演算法遍歷該圖,可以訪問到Internet 上的幾乎所有的網頁。最簡單的遍歷就是寬度優先以及深度優先。以下筆者實現的簡易爬蟲就是使用了寬度優先的爬行策略。

圖 2. 網頁關係的建模圖


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

圖 3. 爬蟲流程圖


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

清單6 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"});
	}
}

清單7 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();
	}
}

清單8 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();
	}
}

清單 9 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");
	}
}

清單 10 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);
	}
}



清單11 LinkFilter.java

package com.ie;

public interface LinkFilter {
	public boolean accept(String url);
}
這些程式碼中關鍵的部分都在 HttpClient 和 HtmlParser 介紹中說明過了,其他部分也比較容易,請感興趣的讀者自行理解。

參考資料
學習
Developworks 學習其他關於 HttpClien t和 HtmlParser 的技術文章。
Developworks 其它專區學習更多的最新技術。
獲得技術和產品
在 www.eclipse.org 獲得免費的 IDE
討論
在 Developerworks 社群參與更多的討論


關於作者

蔣巨集偉是一名計算機專業的在校學生,在學習之餘參與一些 JavaEE 系統的研究和開發工作。目前,他感興趣的方向是 SOA 與 Web Service。可以 通過 [email protected] 與其聯絡。

相關推薦

使用 HttpClient HtmlParser 實現簡易爬蟲

HttpClient 與 HtmlParser 簡介 本小結簡單的介紹一下 HttpClinet 和 HtmlParser 兩個開源的專案,以及他們的網站和提供下載的地址。 HttpClient 簡介 HTTP 協議是現在的因特網最重要的協議之一。除了 WEB 瀏覽器之

利用HttpClientHtmlParser構造簡單爬蟲

/** * 爬蟲主方法入口類 * @author Qing * */ public class Clawler { /** * 用種子url初始化url佇列 * @param seeds */ private void initCrawlerWit

HttpClientHtmlParser配合實現自動CAS單點登入系統抽取頁面資訊

httpclient下載地址:http://mirror.bit.edu.cn/apache//httpcomponents/httpclient/binary/httpcomponents-client-4.5.1-bin.zip 專案中引入所有的jar包,然後看下面的

node 利用httpcheerio編寫簡易爬蟲

trim -s 監聽 fin this init utf8 爬蟲 簡易爬蟲 首先cnpm init創建一個package.json 引入cheerio模塊 cnpm install --save cheerio 然後開始編寫代碼 let cheerio = requi

HttpClient用HttpURLConnection做爬蟲發現爬取的程式碼少了的問題

最近在學習用java來做爬蟲但是發現不管用那種方式都是爬取的程式碼比網頁的原始碼少了很多在網上查了很多都說是inputStream的緩衝區太小而爬取的網頁太大導致讀取出來的網頁程式碼不完整,但是後面發現並不是這個問這個是用HttoClient所作的public static String getH

java--利用集合ListSet實現簡易版學生選課系統

 //建立學生類 import java.util.HashSet; import java.util.Set; public class student {  String id;        //學號  String sname;     //學生姓名  pu

Node.js 實現簡易爬蟲

為什麼選擇利用node來寫爬蟲呢?就是因為cheerio這個庫,全相容jQuery語法,熟悉的話用起來真真是爽 依賴選擇 cheerio: Node.js 版的jQueryhttp:封裝了一個HTPP伺服器和一個簡易的HTTP客戶端iconv-lite:解決爬取gb2

49.Qt-網路程式設計之QTCPSocketQTCPServer(實現簡易網路除錯助手)

在上章 48.QT-網路通訊講解1,我們學習了網路通訊基礎後,本章便來實戰一篇.原始碼正在整理中,等下貼地址. PS:支援客戶端和伺服器,提供原始碼,並且伺服器支援多客戶端連入,並且可以指定與個別客戶端傳送資料,也可以給所有連入的客戶端傳送資料. 1.效果圖所下所示:   如下圖所

Springboot+JPA下實現簡易爬蟲--爬取豆瓣電視劇資料

Springboot+JPA下實現簡易爬蟲--爬取豆瓣電視劇資料   前言:今天聽到產品那邊討論一些需求,好像其中一點是使用者要求我們爬蟲,在網頁上抓取一些資料然後存到我們公司資料庫中,眾所周知,爬蟲的實現對於python語言可是專家,而對於我們使用的Java語言,我也不確定可不可以,趁著無事,上網參考了下

Java爬蟲學習:利用HttpClientJsoup庫實現簡單的Java爬蟲程式

利用HttpClient和Jsoup庫實現簡單的Java爬蟲程式 HttpClient簡介 HttpClient是Apache Jakarta Common下的子專案,可以用來提供高效的、最新的、功能豐富的支援HTTP協議的客戶端程式設計工具包,並且它支

[js高手之路]Node.js實現簡易爬蟲-抓取博客所有文章列表信息

r.js 目錄 ref 抓取 {} attr 視頻 json clist 抓取目標:就是我自己的博客:http://www.cnblogs.com/ghostwu/ 需要實現的功能: 抓取博客所有的文章標題,超鏈接,文章摘要,發布時間 需要用到的庫: node.js自帶的h

淺談網路爬蟲中廣度優先演算法程式碼實現

前幾天給大家分享了網路爬蟲中深度優先演算法的介紹及其程式碼實現過程,沒來得及上車的小夥伴們可以戳這篇文章——淺談網路爬蟲中深度優先演算法和簡單程式碼實現。今天小編給大家分享網路爬蟲中廣度優先演算法的介紹及其程式碼實現過程。 廣度優先演算法和深度優先演算法恰好相反,這裡繼續以上圖的二叉樹為例。

實現簡易字串壓縮演算法:由字母a-z或者A-Z組成,將其中連續出現2次以上(含2次)的字母轉換為字母出現次數,

@Test public void test1(){ String content1 = "AAAAAAAAAAAAAAAAAAAAAAAAttBffgfaaddddddsCDaaaBBBBdddfdsgggggg"; String result = yasuo(content1);

用java實現簡易加密解密

實現原理:               通過位運算的異或運算子“^”把字串與一個指定的值進行異或運算,從而改變字串中的每個字元的值,這樣就可以得到一個加密後的字串。傳送後再將加密後的字串通過源程式實現解

基於HttpClient實現網路爬蟲~以百度新聞為例

      在以前的工作中,實現過簡單的網路爬蟲,沒有系統的介紹過,這篇部落格就係統的介紹以下如何使用java的HttpClient實現網路爬蟲。      關於網路爬蟲的一些理論知識、實現思想以及策略問題,可以參考百度百科“網路爬蟲”,那裡已經介紹的十分詳細,這裡也不再囉嗦

Python爬蟲例項——基於BeautifulSouprequests實現

這是一個招投標網站,我們使用python指令碼爬取紅框中的資訊,包括連結網址、連結名稱、時間等三項內容。 使用到的Python庫:BeautifulSoup、requests 程式碼如下: # -

Python 實現一個自動下載小說的簡易爬蟲

最近在學 Python,個人覺得 Python 是一種比較好玩的程式語言。快速看過一遍之後準備自己寫個小說爬蟲來鞏固下 Python 基礎知識。本人程式設計剛入門,很多東西理解還比較淺,寫下來是為了作為筆記方便以後回來優化改進,如果對本篇文章有好的建議或者有不足

爬蟲基本原理介紹初步實現(以抓取噹噹網圖書資訊為例)

本文程式碼等僅作學習記錄使用 一、爬蟲原理 網路爬蟲指按照一定的規則(模擬人工登入網頁的方式),自動抓取網路上的程式。簡單的說,就是講你上網所看到頁面上的內容獲取下來,並進行儲存。網路爬蟲的爬行策略分為深度優先和廣度優先。 (1)、深度優先 深度

Java爬蟲--利用HttpClientJsoup爬取部落格資料並存入資料庫

  由於今日頭條等頭條類產品的出現,以今日頭條為代表所使用的爬蟲技術正在逐漸火熱,在爬蟲領域具有良好效能和較好效果的Python在最近一年的時間裡逐漸火熱起來,同時因為Python良好的資料分析和機器學習的能力,Python的應用越來越廣泛。不過,今天我們要提到

基於Netty4的HttpServerHttpClient的簡單實現

使用的Netty的版本:netty-4.0.23.Final.tar.bz2 ‐ 15-Aug-2014 (Stable, Recommended) Http 訊息格式: Http request: Method path-to-resource HTTPVers