代码改变世界

【java】【HtmlParser】HtmlParser使用

2012-09-04 16:31  Loull  阅读(3787)  评论(0编辑  收藏  举报

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 ()取得这个NodeHTML页面中的起始位置
int
getEndPosition ()取得这个NodeHTML页面中的结束位置
用于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页面,FilterVisitor相关的函数是必须的,然后第一类和第二类函数是用得最多的。第一类函数比较容易理解,下面用例子说明一下第二类函数。

下面是用于测试的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,如DOCTYPEheadhtml,分别形成了一个最高层的Node节点(很多人可能对第二个和第四个Node的内容有点奇怪。实际上这两个Node就是两个换行符号。HTMLParserHTML页面内容中的所有换行,空格,Tab等都转换成了相应的Tag,所以就出现了这样的Node。虽然内容少但是级别高,呵呵)
getPlainTextString
是把用户可以看到的内容都包含了。有趣的有两点,一是<head>标签中的Title内容是在plainText中的,可能在标题中可见的也算可见吧。另外就是象前面说的,HTML内容中的换行符什么的,也都成了plainText,这个逻辑上好像有点问题。

另外可能大家发现toHtmltoHtml(true)toHtml(false)的结果没什么区别。实际也是这样的,如果跟踪HTMLParser的代码就可以发现,Node的子类是AbstractNode,其中实现了toHtml()的代码,直接调用toHtml(false),而AbstractNode的三个子类RemarkNodeTagNodeTextNode中,toHtml(boolean verbatim)的实现中,都没有处理verbatim参数,所以三个函数的结果是一模一样的。如果你不需要实现你自己的什么特殊处理,简单使用toHtml就可以了。

 

(一)Filter
顾名思义,Filter就是对于结果进行过滤,取得需要的内容。HTMLParserorg.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返回的是有ChildTag。直接初始化了一个
NodeFilter filter = new HasChildFilter();
结果调用NodeList nodes = parser.extractAllNodesThatMatch(filter);的时候HasChildFilter内部直接发生NullPointerException。读了一下HasChildFilter的代码,才发现,实际HasChildFilter是返回有符合条件的子节点的节点,需要另外一个Filter作为过滤子节点的参数。缺省的构造函数虽然可以初始化,但是由于子节点的Filternull,所以使用的时候发生了Exception。从这点来看,HTMLParser的代码还有很多可以优化的的地方。呵呵。

修改代码:
NodeFilter innerFilter = new TagNameFilter ("DIV");
NodeFilter filter = new HasChildFilter(innerFilter);
NodeList nodes = parser.extractAllNodesThatMatch(filter);
输出结果:
getText:body 
=================================================
getText:div id="top_main"
=================================================
可以看到,输出的是两个有DIVTagTag节点。(body有子节点DIV "top_main""top_main"有子节点"logoindex"

注意HasChildFilter还有一个构造函数:
public HasChildFilter (NodeFilter filter, boolean recursive)
如果recursivefalse,则只对第一级子节点进行过滤。比如前面的例子,bodytop_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
HasAttributeFilter3个构造函数:
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被调用。因为我设置的 recurseChildrenrecurseSelf都是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:headThis 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
还定义了几个其他的VisitorHtmlPageNodeVisitorObjectFindingVisitorStringFindingVisitorTagFindingVisitorTextExtractingVisitorUrlModifyingVisitor,它们都是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&amp;Name=%e6%9b%be%e5%b0%8f%e5%b3%b0">曾小峰</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&amp;Name=%e8%89%be%e8%84%89%e5%85%b4">艾脉兴</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&amp;Name=%e7%94%98%e6%99%93%e4%b8%b9">甘晓丹</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&amp;Name=%e5%8f%b2%e8%89%b3%e8%90%8d">史艳萍</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&amp;Name=%e5%ae%8b%e7%90%b4%e8%8a%b3">宋琴芳</a><a target="_blank" href="http://social.wanfangdata.com.cn/Locate.ashx?ArticleId=zhfsbx98200105004&amp;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();
        }
        
    }
}