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的子类,实现了一些特定的功能。笔者个人的感觉是没什么用处,如果你需要什么特定的功能,还不如自己写一个,想在这些里面找到适合你需要的,化的时间可能更多。反正大家看看代码就发现,它们每个都没几行真正有效的代码。

 

实例:

目标页面代码

 



【HtmlParser】HtmlParser使用_子节点

<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>


【HtmlParser】HtmlParser使用_子节点


 

目标解析上述页面代码,获取标签

[url=http://d.wanfangdata.com.cn/Periodical_zhfsbx98200105004.aspx]抗环瓜氨酸肽抗体检测在<font color="red">类风湿</font>关节炎中的意义[/url]


中的内容,通过htmlparser实现如下:



【HtmlParser】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();
}
}
}


【HtmlParser】HtmlParser使用_子节点