轉載 http://www.cnblogs.com/549294286/archive/2012/09/04/2670601.html
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
>
<
body
>
<
div
id
=
"top_main"
>
<
div
id
=
"logoindex"
>
<!--這是注釋-->
白澤居-www.baizeju.com
</
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
<body >
<div id=
"top_main"
>
<div id=
"logoindex"
>
<!--這是注釋-->
白澤居-www.baizeju.com
</div>
白澤居-www.baizeju.com
</div>
</body>
</html>
<body >
<div id=
"top_main"
>
<div id=
"logoindex"
>
<!--這是注釋-->
白澤居-www.baizeju.com
</div>
白澤居-www.baizeju.com
</div>
</body>
</html>
<body >
<div id=
"top_main"
>
<div id=
"logoindex"
>
<!--這是注釋-->
白澤居-www.baizeju.com
</div>
白澤居-www.baizeju.com
</div>
</body>
</html>
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
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();
}
}
}