HTML Parser简易教程--强大的解析html库

HTML Parser是一个用Java写的高速解析html的库。

当今的 Internet 上面有数亿记的网页,越来越多应用程序将这些网页作为分析和处理的数据对象。这些网页多为半结构化的文本,有着大量的标签和嵌套的结构。当我们自己开发一些处理网页的应用程序时,会想到要开发一个单独的网页解析器,这一部分的工作必定需要付出相当的精力和时间。事实上,做为 JAVA 应用程序开发者, HtmlParser 为其提供了强大而灵活易用的开源类库,大大节省了写一个网页解析器的开销。

在它的官网 http://htmlparser.sourceforge.net/ 是这样介绍的:

HTML Parser is a Java library used to parse HTML in either a linear or nested fashion. Primarily used for transformation or extraction, it features filters, visitors, custom tags and easy to use JavaBeans. It is a fast, robust and well tested package.

大意是:HTML Parser是一个用于解析Html的Java的库,可采用线性或嵌套两种方式。主要用于网页的转换或提取,他有一些特性:过滤器,visitors,通常的标签和易用的JavaBeans。它是一个快速,健壮,并严格测试过的组件。

HTML Parser的JavaDocs, API文档地址:http://htmlparser.sourceforge.net/javadoc/index.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);

    对于大多数使用者来说,使用最多的是通过一个URLConnection或者一个保存有网页内容的字符串来初始化Parser,或者使用静态函数来生成一个Parser对象。ParserFeedback的代码很简单,是针对调试和跟踪分析过程的,一般不需要改变。而使用Lexer则是一个相对比较高级的话题,放到以后再讨论吧。
    这里比较有趣的一点是,如果需要设置页面的编码方式的话,不使用Lexer就只有静态函数一个方法了。对需要转码的页面来说,这应该是用得比较多的一个方法。

HTMLParser将解析过的信息保存为一个树的结构。Node是信息保存的数据类型基础
请看Node的定义:
public interface Node extends Cloneable;
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的抽象函数。


· 迭代遍历网页所有节点

网页是一个半结构化的嵌套文本文件,有类似 XML 文件的树形嵌套结构。使用HtmlParser 可以让我们轻易的迭代遍历网页的所有节点。一下代码 展示了如何来实现这个功能


// 循环访问所有节点,输出包含关键字的值节点
	public static void extractKeyWordText(String url, String keyword) {
		try {
            //生成一个解析器对象,用网页的 url 作为参数
			Parser parser = new Parser(url);
			//设置网页的编码,这里只是请求了一个 gb2312 编码网页
			parser.setEncoding("gb2312");
			//迭代所有节点, null 表示不使用 NodeFilter
			NodeList list = parser.parse(null);
            //从初始的节点列表跌倒所有的节点
			processNodeList(list, keyword);
		} catch (ParserException e) {
			e.printStackTrace();
		}
	}

	private static void processNodeList(NodeList list, String keyword) {
		//迭代开始
		SimpleNodeIterator iterator = list.elements();
		while (iterator.hasMoreNodes()) {
			Node node = iterator.nextNode();
			//得到该节点的子节点列表
			NodeList childList = node.getChildren();
			//孩子节点为空,说明是值节点
			if (null == childList)
			{
				//得到值节点的值
				String result = node.toPlainTextString();
				//若包含关键字,则简单打印出来文本
				if (result.indexOf(keyword) != -1)
					System.out.println(result);
			} //end if
			//孩子节点不为空,继续迭代该孩子节点
			else 
			{
				processNodeList(childList, keyword);
			}//end else
		}//end wile
	}

上面的中有两个方法:

 private static void processNodeList(NodeList list, String keyword)

该方法是用类似深度优先的方法来迭代遍历整个网页节点,将那些包含了某个关键字的值节点的值打印出来。

 public static void extractKeyWordText(String url, String keyword)

该方法生成针对 String 类型的 url 变量代表的某个特定网页的解析器,调用 1中的方法实现简单的遍历。

以上代码展示了如何迭代所有的网页,更多的工作可以在此基础上展开。比如找到某个特定的网页内部节点,其实就可以在遍历所有的节点基础上来判断,看被迭代的节点是否满足特定的需要。

· 使用 NodeFilter

NodeFilter 是一个接口,任何一个自定义的 Filter 都需要实现这个接口中的 boolean accept() 方法。如果希望迭代网页节点的时候保留当前节点,则在节点条件满足的情况下返回 true;否则返回 falseHtmlParse 里提供了很多实现了 NodeFilter 接口的类,下面就一些笔者所用到的,以及常用的 Filter 做一些介绍:

1. 对 Filter 做逻辑操作的 Fitler 有:AndFilterNotFilter OrFilterXorFilter

这些 Filter 来组合不同的 Filter,形成满足两个 Filter 逻辑关系结果的 Filter

1. 判断节点的孩子,兄弟,以及父亲节点情况的 Filter 有:HasChildFilter HasParentFilterHasSiblingFilter

2. 判断节点本身情况的 Filter  HasAttributeFilter:判读节点是否有特定属性;LinkStringFilter:判断节点是否是具有特定模式 (pattern) url 的节点;

TagNameFilter:判断节点是否具有特定的名字;NodeClassFilter:判读节点是否是某个 HtmlParser 定义好的 Tag 类型。在 org.htmlparser.tags 包下有对应 Html标签的各种 Tag,例如 LinkTagImgeTag 等。

还有其他的一些 Filter 在这里不一一列举了,可以在 org.htmlparser.filters 下找到。


接下来的代码 展示了如何使用上面提到过的一些 filter 来抽取网页中的 <a> 标签里的 href属性值,<img> 标签里的 src 属性值,以及 <frame> 标签里的 src 的属性值。


// 获取一个网页上所有的链接和图片链接
	public static void extracLinks(String url) {
		try {
			Parser parser = new Parser(url);
			parser.setEncoding("gb2312");
//过滤 <frame> 标签的 filter,用来提取 frame 标签里的 src 属性所、表示的链接
			NodeFilter frameFilter = new NodeFilter() {
				public boolean accept(Node node) {
					if (node.getText().startsWith("frame src=")) {
						return true;
					} else {
						return false;
					}
				}
			};
//OrFilter 来设置过滤 <a> 标签,<img> 标签和 <frame> 标签,三个标签是 or 的关系
	 OrFilte orFilter = new OrFilter(new NodeClassFilter(LinkTag.class), new 
NodeClassFilter(ImageTag.class));
	 OrFilter linkFilter = new OrFilter(orFilter, frameFilter);
	//得到所有经过过滤的标签
	NodeList list = parser.extractAllNodesThatMatch(linkFilter);
	for (int i = 0; i < list.size(); i++) {
		Node tag = list.elementAt(i);
		if (tag instanceof LinkTag)//<a> 标签 
		{
			LinkTag link = (LinkTag) tag;
			String linkUrl = link.getLink();//url
			String text = link.getLinkText();//链接文字
			System.out.println(linkUrl + "**********" + text);
		}
		else if (tag instanceof ImageTag)//<img> 标签
		{
			ImageTag image = (ImageTag) list.elementAt(i);
			System.out.print(image.getImageURL() + "********");//图片地址
			System.out.println(image.getText());//图片文字
		}
		else//<frame> 标签
		{
//提取 frame 里 src 属性的链接如 <frame src="test.html"/>
			String frame = tag.getText();
			int start = frame.indexOf("src=");
			frame = frame.substring(start);
			int end = frame.indexOf(" ");
			if (end == -1)
				end = frame.indexOf(">");
			frame = frame.substring(5, end - 1);
			System.out.println(frame);
		}
	}
} catch (ParserException e) {
			e.printStackTrace();
}
}


· 简单强大的 StringBean

如果你想要网页中去掉所有的标签后剩下的文本,那就是用 StringBean 吧。以下简单的代码可以帮你解决这样的问题:


StringBean sb = new StringBean();
sb.setLinks(false);//设置结果中去点链接
sb.setURL(url);//设置你所需要滤掉网页标签的页面 url
System.out.println(sb.getStrings());//打印结果


HtmlParser 提供了强大的类库来处理网页,本文只是简单的介绍。感兴趣的读者可以利用多读一下API或者其他方式专门来研究一下HtmlParser 更为强大的类库。




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值