最近学习HTMLParser,想使用HTMLParser做一个可以半自动解析网页的应用。HTMLParser是一个功能非常强大的解析网页的开源代码,他将网页源码看做是一个树(或者森林)的结构,通过树之间的逻辑关系遍历访问网页中的每一个节点,下面是一段网页源码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白泽居-www.baizeju.com</title></head> <body > <div id="top_main" class="name"> <div id="logoindex"> 白泽居-www.baizeju.com<a href="http://www.baizeju.com">白泽居-www.baizeju.com</a> </div> <div class="name">东方教主文成武德,一同江湖</div> </div> </body> </html>
他被组织成三棵树的森林,其中以<html>标签为根节点的树高度最大,网页的树状结构图如下:
html树中要特别注意的是每一个回车换行,HTMLParser会将他们看做一个节点处理,下面介绍HTMLParser如何遍历所有的节点:
(1)可以写一个有如下代码的main函数:
package parsertool; 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.filters.TagNameFilter; import org.htmlparser.util.NodeIterator; import org.htmlparser.util.NodeList; import org.htmlparser.Parser; public class Nodes { private static String ENCODE = "GBK"; public static void main(String[] args) { try{ Parser parser = new Parser("http:指向上面源码的链接url" ); TagNameFilter filter=new TagNameFilter ("DIV"); NodeList list=parser.parse(filter); if(list!=null){ System.out.println("list.size()==="+list.size()); for(int i=0;i<list.size();i++) System.err.println("children[i]="+list.elementAt(i).toHtml()); } System.out.println("finished.............................."); } catch( Exception e ) { System.out.println( "Exception:"+e ); } } }
解释:首先实例化一个Parser类,他是HTMLParser的核心类,通过遍历网页树状结构,他的构造函数是一个指向需要解析网页的链接(也可以是网页源码等);然后实例化了一个TagNameFilter类,该类通过标签的名字类过滤选择标签,如我们想要得到<div>标签(HTMLParser不区分大小写)就需要如上构建TagNameFilter类实例;再然后就是用Parser的parse()函数,以filter为参数过滤标签,结果返回一个NodeList类似于链表的类实例;剩下的部分就是展示过滤得到的标签。
(2)下面看Parser如何遍历html的DOM树,下面是parse函数:
public NodeList parse (NodeFilter filter) throws ParserException { NodeIterator e; Node node; NodeList ret; ret = new NodeList (); for (e = elements (); e.hasMoreNodes (); ) { // System.out.println("for+++++++++++++++++++++++++++++++++++++++++"); node = e.nextNode (); // System.out.println("node content:"+node.toHtml()); if (null != filter){ // System.out.println("parse null!=filter"); node.collectInto (ret, filter); }else{ // System.err.println("no posibilty"); ret.add (node); } // System.out.println("endfor+++++++++++++++++++++++++++++++++++++++++++\n"); } // System.out.println("return result"); return (ret); }
解释:他以一个NodeFilter为参数,返回一个存放着符合条件节点的NodeList的结果。他有一个for循环遍历Parser解析的树或者森林(根节点可以通过elements()得到),对于每一个根节点根据filter进行处理,如果filter为空说明获得全部树节点则直接将根节点添加到返回结果中,否则根据filter调用节点的collectInto()函数,遍历Node和他的子Node返回符合过滤条件的节点链表。
(3)当然最终遍历树结构的还是每一个Node本身,node本身保存有他的父节点和子节点,一个DOM树只要知道这课树的根节点就可以通过他们的父子关系遍历出整棵树。Node的子类主要是AbstractNode和CompositeTag。其中AbstractNode相当于叶节点,他不包含子节点;而CompositeTag则相当于中间节点,他包含子节点。他们两个的collectInto()函数是不同的,AbstractNode节点只需要根据filter过滤本节点就行了,代码如下:
public void collectInto (NodeList list, NodeFilter filter) { // System.out.println("============================AbstractNode================+size=="+list.size()+"content:"+this.toHtml()); if (filter.accept (this)){ // System.out.println("accapt+++++++++++++"); list.add (this); } // System.out.println("============================EndAbstractNode================+"+list.size()); }
而CompositeTag的collectInto()函数则需要递归遍历自己的子节点,代码如下:
public void collectInto (NodeList list, NodeFilter filter) { super.collectInto (list, filter); for (SimpleNodeIterator e = children(); e.hasMoreNodes ();){ e.nextNode ().collectInto (list, filter); } if ((null != getEndTag ()) && (this != getEndTag ())){ // 2nd guard handles <tag/> getEndTag ().collectInto (list, filter); } }
对CompositeTag的collectInto()函数的解释:因为CompositeTag继承自AbstractNode,所以调用super.collectInto(list,filter)就是调用AbstractNode的collectInto函数过滤自身;然后在for循环里对子节点根据filter条件进行递归,获得子节点中满足过滤条件的节点;最后判断该节点的尾标签(如</html>)是否需要递归遍历,如果是则进行递归。可见CompositeTag的collectInto函数是递归遍历html DOM树的关键所在。