HTMLParser的核心模塊是org.htmlparser.Parser類,這個類實際完成了對於HTML頁面的分析工作。這個類有下面幾個構造函數:
public Parser (); public Parser (Lexer lexer, ParserFeedback fb); public Parser (URLConnection connection, ParserFeedback fb) throws ParserException; public Parser (String resource, ParserFeedback feedback) throws ParserException; public Parser (String resource) throws ParserException; public Parser (Lexer lexer); public Parser (URLConnection connection) throws ParserException; public static Parser createParser (String html, String charset);//靜態類
Node中包含的方法有幾類:
對於樹型結構進行遍歷的函數,這些函數最容易理解:
Node getParent ():取得父節點
NodeList getChildren ():取得子節點的列表
Node getFirstChild ():取得第一個子節點
Node getLastChild ():取得最后一個子節點
Node getPreviousSibling ():取得前一個兄弟(不好意思,英文是兄弟姐妹,直譯太麻煩而且不符合習慣,對不起女同胞了)
Node getNextSibling ():取得下一個兄弟節點
取得Node內容的函數:
String getText ():取得文本
String toPlainTextString():取得純文本信息。
String toHtml () :取得HTML信息(原始HTML)
String toHtml (boolean verbatim):取得HTML信息(原始HTML)
String toString ():取得字符串信息(原始HTML)
Page getPage ():取得這個Node對應的Page對象
int getStartPosition ():取得這個Node在HTML頁面中的起始位置
int getEndPosition ():取得這個Node在HTML頁面中的結束位置
用於Filter過濾的函數:
void collectInto (NodeList list, NodeFilter filter):基於filter的條件對於這個節點進行過濾,符合條件的節點放到list中。
用於Visitor遍歷的函數:
void accept (NodeVisitor visitor):對這個Node應用visitor
用於修改內容的函數,這類用得比較少:
void setPage (Page page):設置這個Node對應的Page對象
void setText (String text):設置文本
void setChildren (NodeList children):設置子節點列表
其他函數:
void doSemanticAction ():執行這個Node對應的操作(只有少數Tag有對應的操作)
Object clone ():接口Clone的抽象函數。
類框架圖:
AbstractNodes是Node的直接子類,也是一個抽象類。它的三個直接子類實現是RemarkNode,用於保存注釋。在輸出結果的toString部分中可以看到有一個"Rem (345[6,2],356[6,13]): 這是注釋",就是一個RemarkNode。TextNode也很簡單,就是用戶可見的文字信息。TagNode是最復雜的,包含了HTML語言中的所有標簽,而且可以擴展(擴展 HTMLParser 對自定義標簽的處理能力)。TagNode包含兩類,一類是簡單的Tag,實際就是不能包含其他Tag的標簽,只能做葉子節點。另一類是CompositeTag,就是可以包含其他Tag,是分支節點
HTMLParser遍歷了網頁的內容以后,以樹(森林)結構保存了結果。HTMLParser訪問結果內容的方法有兩種。使用Filter和使用Visitor。
實際我們用HTMLParser最多的是處理HTML頁面,Filter或Visitor相關的函數是必須的,然后第一類和第二類函數是用得最多的。第一類函數比較容易理解,下面用例子說明一下第二類函數。
下面是用於測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head> <html xmlns="http://www.w3.org/1999/xhtml"> <body > <div id="top_main"> <div id="logoindex"> <!--這是注釋--> 白澤居-www.baizeju.com <a href="http://www.baizeju.com">白澤居-www.baizeju.com</a> </div> 白澤居-www.baizeju.com </div> </body> </html>
測試代碼:
/** * @author www.baizeju.com */ package com.baizeju.htmlparsertester; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.FileInputStream; import java.io.File; import java.net.HttpURLConnection; import java.net.URL; import org.htmlparser.Node; import org.htmlparser.util.NodeIterator; import org.htmlparser.Parser; /** * @author www.baizeju.com */ public class Main { private static String ENCODE = "GBK"; private static void message( String szMsg ) { try{ System.out.println(new String(szMsg.getBytes(ENCODE), System.getProperty("file.encoding"))); } catch(Exception e ){} } public static String openFile( String szFileName ) { try { BufferedReader bis = new BufferedReader(new InputStreamReader(new FileInputStream( new File(szFileName)), ENCODE) ); String szContent=""; String szTemp; while ( (szTemp = bis.readLine()) != null) { szContent+=szTemp+"\n"; } bis.close(); return szContent; } catch( Exception e ) { return ""; } } public static void main(String[] args) { try{ Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() ); for (NodeIterator i = parser.elements (); i.hasMoreNodes(); ) { Node node = i.nextNode(); message("getText:"+node.getText()); message("getPlainText:"+node.toPlainTextString()); message("toHtml:"+node.toHtml()); message("toHtml(true):"+node.toHtml(true)); message("toHtml(false):"+node.toHtml(false)); message("toString:"+node.toString()); message("================================================="); } } catch( Exception e ) { System.out.println( "Exception:"+e ); } } }
輸出結果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" getPlainText: toHtml:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> toHtml(true):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> toHtml(false):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> toString:Doctype Tag : !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd; begins at : 0; ends at : 121 ================================================= getText: getPlainText: toHtml: toHtml(true): toHtml(false): toString:Txt (121[0,121],123[1,0]): \n ================================================= getText:head getPlainText:白澤居-www.baizeju.com toHtml:<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head> toHtml(true):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head> toHtml(false):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head> toString:HEAD: Tag (123[1,0],129[1,6]): head Tag (129[1,6],197[1,74]): meta http-equiv="Content-Type" content="text/html; ... Tag (197[1,74],204[1,81]): title Txt (204[1,81],223[1,100]): 白澤居-www.baizeju.com End (223[1,100],231[1,108]): /title End (231[1,108],238[1,115]): /head ================================================= getText: getPlainText: toHtml: toHtml(true): toHtml(false): toString:Txt (238[1,115],240[2,0]): \n ================================================= getText:html xmlns="http://www.w3.org/1999/xhtml" getPlainText: 白澤居-www.baizeju.com 白澤居-www.baizeju.com 白澤居-www.baizeju.com toHtml:<html xmlns="http://www.w3.org/1999/xhtml"> <body > <div id="top_main"> <div id="logoindex"> <!--這是注釋--> 白澤居-www.baizeju.com <a href="http://www.baizeju.com">白澤居-www.baizeju.com</a> </div> 白澤居-www.baizeju.com </div> </body> </html> toHtml(true):<html xmlns="http://www.w3.org/1999/xhtml"> <body > <div id="top_main"> <div id="logoindex"> <!--這是注釋--> 白澤居-www.baizeju.com <a href="http://www.baizeju.com">白澤居-www.baizeju.com</a> </div> 白澤居-www.baizeju.com </div> </body> </html> toHtml(false):<html xmlns="http://www.w3.org/1999/xhtml"> <body > <div id="top_main"> <div id="logoindex"> <!--這是注釋--> 白澤居-www.baizeju.com <a href="http://www.baizeju.com">白澤居-www.baizeju.com</a> </div> 白澤居-www.baizeju.com </div> </body> </html> toString:Tag (240[2,0],283[2,43]): html xmlns="http://www.w3.org/1999/xhtml" Txt (283[2,43],285[3,0]): \n Tag (285[3,0],292[3,7]): body Txt (292[3,7],294[4,0]): \n Tag (294[4,0],313[4,19]): div id="top_main" Txt (313[4,19],316[5,1]): \n\t Tag (316[5,1],336[5,21]): div id="logoindex" Txt (336[5,21],340[6,2]): \n\t\t Rem (340[6,2],351[6,13]): 這是注釋 Txt (351[6,13],376[8,0]): \n\t\t白澤居-www.baizeju.com\n Tag (376[8,0],409[8,33]): a href="http://www.baizeju.com" Txt (409[8,33],428[8,52]): 白澤居-www.baizeju.com End (428[8,52],432[8,56]): /a Txt (432[8,56],435[9,1]): \n\t End (435[9,1],441[9,7]): /div Txt (441[9,7],465[11,0]): \n\t白澤居-www.baizeju.com\n End (465[11,0],471[11,6]): /div Txt (471[11,6],473[12,0]): \n End (473[12,0],480[12,7]): /body Txt (480[12,7],482[13,0]): \n End (482[13,0],489[13,7]): /html =================================================
對於第一個Node的內容,對應的就是第一行<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">,這個比較好理解。
從這個輸出結果中,也可以看出內容的樹狀結構。或者說是樹林結構。在Page內容的第一層Tag,如DOCTYPE,head和html,分別形成了一個最高層的Node節點(很多人可能對第二個和第四個Node的內容有點奇怪。實際上這兩個Node就是兩個換行符號。HTMLParser把HTML頁面內容中的所有換行,空格,Tab等都轉換成了相應的Tag,所以就出現了這樣的Node。雖然內容少但是級別高,呵呵)
getPlainTextString是把用戶可以看到的內容都包含了。有趣的有兩點,一是<head>標簽中的Title內容是在plainText中的,可能在標題中可見的也算可見吧。另外就是象前面說的,HTML內容中的換行符什么的,也都成了plainText,這個邏輯上好像有點問題。
另外可能大家發現toHtml,toHtml(true)和toHtml(false)的結果沒什么區別。實際也是這樣的,如果跟蹤HTMLParser的代碼就可以發現,Node的子類是AbstractNode,其中實現了toHtml()的代碼,直接調用toHtml(false),而AbstractNode的三個子類RemarkNode,TagNode和TextNode中,toHtml(boolean verbatim)的實現中,都沒有處理verbatim參數,所以三個函數的結果是一模一樣的。如果你不需要實現你自己的什么特殊處理,簡單使用toHtml就可以了。
(一)Filter類
顧名思義,Filter就是對於結果進行過濾,取得需要的內容。HTMLParser在org.htmlparser.filters包之內一共定義了16個不同的Filter,也可以分為幾類。
判斷類Filter:
TagNameFilter
HasAttributeFilter
HasChildFilter
HasParentFilter
HasSiblingFilter
IsEqualFilter
邏輯運算Filter:
AndFilter
NotFilter
OrFilter
XorFilter
其他Filter:
NodeClassFilter
StringFilter
LinkStringFilter
LinkRegexFilter
RegexFilter
CssSelectorNodeFilter
所有的Filter類都實現了org.htmlparser.NodeFilter接口。這個接口只有一個主要函數:
boolean accept (Node node);
各個子類分別實現這個函數,用於判斷輸入的Node是否符合這個Filter的過濾條件,如果符合,返回true,否則返回false。
(二)判斷類Filter
2.1 TagNameFilter
TabNameFilter是最容易理解的一個Filter,根據Tag的名字進行過濾。
下面是用於測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title>< /head>
<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
<div id="logoindex">
<!--這是注釋-->
白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
</div>
白澤居-www.baizeju.com
</div>
</body>
</html>
測試代碼:(這里只列出了Main函數,全部代碼請參考 HTMLParser使用入門(2)- Node內容,自己添加import部分)
public static void main(String[] args) {
try{
Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
// 這里是控制測試的部分,后面的例子修改的就是這個地方。
NodeFilter filter = new TagNameFilter ("DIV");
NodeList nodes = parser.extractAllNodesThatMatch(filter);
if(nodes!=null) {
for (int i = 0; i < nodes.size(); i++) {
Node textnode = (Node) nodes.elementAt(i);
message("getText:"+textnode.getText());
message("=================================================");
}
}
}
catch( Exception e ) {
e.printStackTrace();
}
}
輸出結果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================
可以看出文件中兩個Div節點都被取出了。下面可以針對這兩個DIV節點進行操作
2.2 HasChildFilter
下面讓我們看看HasChildFilter。剛剛看到這個Filter的時候,我想當然地認為這個Filter返回的是有Child的Tag。直接初始化了一個
NodeFilter filter = new HasChildFilter();
結果調用NodeList nodes = parser.extractAllNodesThatMatch(filter);的時候HasChildFilter內部直接發生NullPointerException。讀了一下HasChildFilter的代碼,才發現,實際HasChildFilter是返回有符合條件的子節點的節點,需要另外一個Filter作為過濾子節點的參數。缺省的構造函數雖然可以初始化,但是由於子節點的Filter是null,所以使用的時候發生了Exception。從這點來看,HTMLParser的代碼還有很多可以優化的的地方。呵呵。
修改代碼:
NodeFilter innerFilter = new TagNameFilter ("DIV");
NodeFilter filter = new HasChildFilter(innerFilter);
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:body
=================================================
getText:div id="top_main"
=================================================
可以看到,輸出的是兩個有DIV子Tag的Tag節點。(body有子節點DIV "top_main","top_main"有子節點"logoindex"。
注意HasChildFilter還有一個構造函數:
public HasChildFilter (NodeFilter filter, boolean recursive)
如果recursive是false,則只對第一級子節點進行過濾。比如前面的例子,body和top_main都是在第一級的子節點里就有DIV節點,所以匹配上了。如果我們用下面的方法調用:
NodeFilter filter = new HasChildFilter( innerFilter, true );
輸出結果:
getText:html xmlns="http://www.w3.org/1999/xhtml"
=================================================
getText:body
=================================================
getText:div id="top_main"
=================================================
可以看到輸出結果中多了一個html xmlns="http://www.w3.org/1999/xhtml",這個是整個HTML頁面的節點(根節點),雖然這個節點下直接沒有DIV節點,但是它的子節點body下面有DIV節點,所以它也被匹配上了。
2.3 HasAttributeFilter
HasAttributeFilter有3個構造函數:
public HasAttributeFilter ();
public HasAttributeFilter (String attribute);
public HasAttributeFilter (String attribute, String value);
這個Filter可以匹配出包含制定名字的屬性,或者制定屬性為指定值的節點。還是用例子說明比較容易。
調用方法1:
NodeFilter filter = new HasAttributeFilter();
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
什么也沒有輸出。
調用方法2:
NodeFilter filter = new HasAttributeFilter( "id" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================
調用方法3:
NodeFilter filter = new HasAttributeFilter( "id", "logoindex" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:div id="logoindex"
=================================================
很簡單吧。呵呵
2.4 其他判斷列Filter
HasParentFilter和HasSiblingFilter的功能與HasChildFilter類似,大家自己試一下就應該了解了。
IsEqualFilter的構造函數參數是一個Node:
public IsEqualFilter (Node node) {
mNode = node;
}
accept函數也很簡單:
public boolean accept (Node node) {
return (mNode == node);
}
不需要過多說明了。
(三)邏輯運算Filter
前面介紹的都是簡單的Filter,只能針對某種單一類型的條件進行過濾。HTMLParser支持對於簡單類型的Filter進行組合,從而實現復雜的條件。原理和一般編程語言的邏輯運算是一樣的。
3.1 AndFilter
AndFilter可以把兩種Filter進行組合,只有同時滿足條件的Node才會被過濾。
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new AndFilter(filterID, filterChild);
輸出結果:
getText:div id="logoindex"
=================================================
3.2 OrFilter
把前面的AndFilter換成OrFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new OrFilter(filterID, filterChild);
輸出結果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================
3.3 NotFilter
把前面的AndFilter換成NotFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new NotFilter(new OrFilter(filterID, filterChild));
輸出結果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
=================================================
getText:
=================================================
getText:head
=================================================
getText:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
=================================================
getText:title
=================================================
getText:白澤居-www.baizeju.com
=================================================
getText:/title
=================================================
getText:/head
=================================================
getText:
=================================================
getText:html xmlns="http://www.w3.org/1999/xhtml"
=================================================
getText:
=================================================
getText:body
=================================================
getText:
=================================================
getText:
=================================================
getText:
=================================================
getText:這是注釋
=================================================
getText:
白澤居-www.baizeju.com
=================================================
getText:a href="http://www.baizeju.com"
=================================================
getText:白澤居-www.baizeju.com
=================================================
getText:/a
=================================================
getText:
=================================================
getText:/div
=================================================
getText:
白澤居-www.baizeju.com
=================================================
getText:/div
=================================================
getText:
=================================================
getText:/body
=================================================
getText:
=================================================
getText:/html
=================================================
getText:
=================================================
除了前面3.2中輸出的幾個Tag,其余的Tag都在這里了。
3.4 XorFilter
把前面的AndFilter換成NotFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new XorFilter(filterID, filterChild);
輸出結果:
getText:div id="top_main"
=================================================
(四)其他Filter:
4.1 NodeClassFilter
這個Filter用於判斷節點類型是否是某個特定的Node類型。在HTMLParser使用入門(2)- Node內容 中我們已經了解了Node的不同類型,這個Filter就可以針對類型進行過濾。
測試代碼:
NodeFilter filter = new NodeClassFilter(RemarkNode.class);
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:這是注釋
=================================================
可以看到只有RemarkNode(注釋)被輸出了。
4.2 StringFilter
這個Filter用於過濾顯示字符串中包含制定內容的Tag。注意是可顯示的字符串,不可顯示的字符串中的內容(例如注釋,鏈接等等)不會被顯示。
修改一下例子代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-title-www.baizeju.com</title></head>
<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
<div id="logoindex">
<!--這是注釋白澤居-www.baizeju.com -->
白澤居-字符串1-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-鏈接文本-www.baizeju.com</a>
</div>
白澤居-字符串2-www.baizeju.com
</div>
</body>
</html>
測試代碼:
NodeFilter filter = new StringFilter("www.baizeju.com");
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:白澤居-title-www.baizeju.com
=================================================
getText:
白澤居-字符串1-www.baizeju.com
=================================================
getText:白澤居-鏈接文本-www.baizeju.com
=================================================
getText:
白澤居-字符串2-www.baizeju.com
=================================================
可以看到包含title,兩個內容字符串和鏈接的文本字符串的Tag都被輸出了,但是注釋和鏈接Tag本身沒有輸出。
4.3 LinkStringFilter
這個Filter用於判斷鏈接中是否包含某個特定的字符串,可以用來過濾出指向某個特定網站的鏈接。
測試代碼:
NodeFilter filter = new LinkStringFilter("www.baizeju.com");
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:a href="http://www.baizeju.com"
=================================================
4.4 其他幾個Filter
其他幾個Filter也是根據字符串對不同的域進行判斷,與前面這些的區別主要就是支持正則表達式。這個不在本文的討論范圍以內,大家可以自己實驗一下。
(五)visitor
HTMLParser遍歷了網頁的內容以后,以樹(森林)結構保存了結果。HTMLParser訪問結果內容的方法有兩種。使用Filter和使用Visitor。
下面介紹使用Visitor訪問內容的方法。
5.1 NodeVisitor
從簡單方面的理解,Filter是根據某種條件過濾取出需要的Node再進行處理。Visitor則是遍歷內容樹的每一個節點,對於符合條件的節點進行處理。實際的結果異曲同工,兩種不同的方法可以達到相同的結果。
下面是一個最常見的NodeVisitro的例子。
測試代碼:
public static void main(String[] args) {
try{
Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
NodeVisitor visitor = new NodeVisitor( false, false ) {
public void visitTag(Tag tag) {
message("This is Tag:"+tag.getText());
}
public void visitStringNode (Text string) {
message("This is Text:"+string);
}
public void visitRemarkNode (Remark remark) {
message("This is Remark:"+remark.getText());
}
public void beginParsing () {
message("beginParsing");
}
public void visitEndTag (Tag tag){
message("visitEndTag:"+tag.getText());
}
public void finishedParsing () {
message("finishedParsing");
}
};
parser.visitAllNodesWith(visitor);
}
catch( Exception e ) {
e.printStackTrace();
}
}
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Text:Txt (244[1,121],246[2,0]): \n
finishedParsing
可以看到,開始遍歷所以的節點以前,beginParsing先被調用,然后處理的是中間的Node,最后在結束遍歷以前,finishParsing被調用。因為我設置的 recurseChildren和recurseSelf都是false,所以Visitor沒有訪問子節點也沒有訪問根節點的內容。中間輸出的兩個\n就是我們在HTMLParser使用詳解(1)- 初始化Parser 中討論過的最高層的那兩個換行。
我們先把recurseSelf設置成true,看看會發生什么。
NodeVisitor visitor = new NodeVisitor( false, true) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Tag:html xmlns="http://www.w3.org/1999/xhtml"
finishedParsing
可以看到,HTML頁面的第一層節點都被調用了。
我們再用下面的方法調用看看:
NodeVisitor visitor = new NodeVisitor( true, false) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
This is Text:Txt (204[1,81],229[1,106]): 白澤居-title-www.baizeju.com
visitEndTag:/title
visitEndTag:/head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Text:Txt (289[2,43],291[3,0]): \n
This is Text:Txt (298[3,7],300[4,0]): \n
This is Text:Txt (319[4,19],322[5,1]): \n\t
This is Text:Txt (342[5,21],346[6,2]): \n\t\t
This is Remark:這是注釋白澤居-www.baizeju.com
This is Text:Txt (378[6,34],408[8,0]): \n\t\t白澤居-字符串1-www.baizeju.com\n
This is Text:Txt (441[8,33],465[8,57]): 白澤居-鏈接文本-www.baizeju.com
visitEndTag:/a
This is Text:Txt (469[8,61],472[9,1]): \n\t
visitEndTag:/div
This is Text:Txt (478[9,7],507[11,0]): \n\t白澤居-字符串2-www.baizeju.com\n
visitEndTag:/div
This is Text:Txt (513[11,6],515[12,0]): \n
visitEndTag:/body
This is Text:Txt (522[12,7],524[13,0]): \n
visitEndTag:/html
finishedParsing
可以看到,所有的子節點都出現了,除了剛剛例子里面的兩個最上層節點This is Tag:head和This is Tag:html xmlns="http://www.w3.org/1999/xhtml"。
想讓它們都出來,只需要
NodeVisitor visitor = new NodeVisitor( true, true) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:head
This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
This is Tag:title
This is Text:Txt (204[1,81],229[1,106]): 白澤居-title-www.baizeju.com
visitEndTag:/title
visitEndTag:/head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Tag:html xmlns="http://www.w3.org/1999/xhtml"
This is Text:Txt (289[2,43],291[3,0]): \n
This is Tag:body
This is Text:Txt (298[3,7],300[4,0]): \n
This is Tag:div id="top_main"
This is Text:Txt (319[4,19],322[5,1]): \n\t
This is Tag:div id="logoindex"
This is Text:Txt (342[5,21],346[6,2]): \n\t\t
This is Remark:這是注釋白澤居-www.baizeju.com
This is Text:Txt (378[6,34],408[8,0]): \n\t\t白澤居-字符串1-www.baizeju.com\n
This is Tag:a href="http://www.baizeju.com"
This is Text:Txt (441[8,33],465[8,57]): 白澤居-鏈接文本-www.baizeju.com
visitEndTag:/a
This is Text:Txt (469[8,61],472[9,1]): \n\t
visitEndTag:/div
This is Text:Txt (478[9,7],507[11,0]): \n\t白澤居-字符串2-www.baizeju.com\n
visitEndTag:/div
This is Text:Txt (513[11,6],515[12,0]): \n
visitEndTag:/body
This is Text:Txt (522[12,7],524[13,0]): \n
visitEndTag:/html
finishedParsing
哈哈,這下調用清楚了,大家在需要處理的地方增加自己的代碼好了。
5.2 其他Visitor
HTMLParser還定義了幾個其他的Visitor。HtmlPage,NodeVisitor,ObjectFindingVisitor,StringFindingVisitor,TagFindingVisitor,TextExtractingVisitor,UrlModifyingVisitor,它們都是NodeVisitor的子類,實現了一些特定的功能。筆者個人的感覺是沒什么用處,如果你需要什么特定的功能,還不如自己寫一個,想在這些里面找到適合你需要的,化的時間可能更多。反正大家看看代碼就發現,它們每個都沒幾行真正有效的代碼。
實例:
目標頁面代碼
<ul class="list_ul"> <li class="title_li">1 <a href="http://f.wanfangdata.com.cn/download/Periodical_zhfsbx98200105004.aspx" style="" title="全文" target="_blank" class="pdf_img"></a> <a href="http://d.wanfangdata.com.cn/Periodical_zhfsbx98200105004.aspx" style="display:none" title="文摘" target="_blank" class="abs_img"></a> [url=http://d.wanfangdata.com.cn/Periodical_zhfsbx98200105004.aspx]抗環瓜氨酸肽抗體檢測在<font color="red">類風濕</font>關節炎中的意義[/url] <span>(<t>被引用</t> 182 <t>次</t>)</span></li> <li class="greencolor">[<t>期刊論文</t>] [url=http://c.wanfangdata.com.cn/Periodical-zhfsbx98.aspx]《中華風濕病學雜志》[/url] <span> <span title="被中信所《中國科技期刊引證報告》收錄">ISTIC</span> <span title="被北京大學《中文核心期刊要目總覽》收錄">PKU</span> </span>-[url=http://c.wanfangdata.com.cn/periodical/zhfsbx98/2001-5.aspx]2001年5期[/url]<a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e6%9b%be%e5%b0%8f%e5%b3%b0">曾小峰</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e8%89%be%e8%84%89%e5%85%b4">艾脈興</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e7%94%98%e6%99%93%e4%b8%b9">甘曉丹</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e5%8f%b2%e8%89%b3%e8%90%8d">史艷萍</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e5%ae%8b%e7%90%b4%e8%8a%b3">宋琴芳</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&Name=%e5%94%90%e7%a6%8f%e6%9e%97">唐福林</a></li> </ul>
目標解析上述頁面代碼,獲取標簽
[url=http://d.wanfangdata.com.cn/Periodical_zhfsbx98200105004.aspx]抗環瓜氨酸肽抗體檢測在<font color="red">類風濕</font>關節炎中的意義[/url]
中的內容,通過htmlparser實現如下:
package com.nit.htmlparser.test; import java.io.File; import org.htmlparser.Node; import org.htmlparser.NodeFilter; import org.htmlparser.Parser; import org.htmlparser.filters.AndFilter; import org.htmlparser.filters.HasAttributeFilter; import org.htmlparser.filters.TagNameFilter; import org.htmlparser.util.NodeList; public class Htmlparser { public static void main(String[] args) { File file = new File("E:\\JavaEE Work\\HtmlparserTest\\source.txt");//獲取上述html代碼 try { Parser parser = new Parser(file.getAbsolutePath()); parser.setEncoding("GBK"); //設置編碼格式 NodeFilter filter1 = new TagNameFilter("li"); NodeFilter filter2 = new HasAttributeFilter("class","title_li"); NodeFilter filter = new AndFilter(filter1,filter2); //設置頁面過濾條件 NodeList nodeList = parser.extractAllNodesThatMatch(filter); //根據過濾條件解析頁面 Node node = nodeList.elementAt(0); String html = node.getChildren().toHtml(); //將抽取出來的信息轉化為html再次解析 filter1 = new HasAttributeFilter("target", "_blank"); parser = Parser.createParser(html, "GBK"); nodeList = parser.extractAllNodesThatMatch(filter1); System.out.println(nodeList.elementAt(2).getChildren().asString()); filter2 = new TagNameFilter("span"); parser = Parser.createParser(html, "GBK"); nodeList = parser.extractAllNodesThatMatch(filter2); System.out.println(nodeList.elementAt(0).getChildren().asString()); } catch (Throwable e) { e.printStackTrace(); } } }