最近学习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>
<!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 );
- }
- }
- }
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);
- }
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());
- }
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);
- }
- }
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树的关键所在。
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的抽象函数。