http://www-128.ibm.com/developerworks/cn/java/l-html-parser/
本文并非想通过分析HTML的语法然后从中解析出数据,这样做实现困难而且没有什么实际应用的意义,或者应该这样说:我们并不想自己去实现一个HTML语法的分析器。我们要做的仅仅是从HTML中提取我们所需的信息。不同于XML这种对格式要求非常严格的标志语言,HTML在推出时并没有对其格式进行严格的定义,比如HTML中标签并不一定要成对出现,但是又要求浏览器能尽量的正确显示其所要表达出来的内容。浏览器经过多年发展其适应能力越来越强,很多格式非常糟糕的HTML文件都能显示得令人满意。不过如果我们需要精确的获取HTML中包含的数据,这恐怕比显示一个HTML更令人头疼。现在终于找到治疗头疼的特效药了!
接下来我们要介绍的就是使用 JAVA 语言如何简单快速的攫取 HTML 中包含的数据。我们会借助一些现有的成熟 API 来完成这件事,因为如果仅仅是为了实现这个功能,完全没有必要自己去实现 HTML 的语法分析器。我们引入了一个开源的项目-- HTML Parser ,这是一个在 SourceForge.net 上比较活跃的项目之一,目前的最新版本是 1.4 发行版。有如 HTML Parser 网站的自我介绍: HTML Parser 是一个对现有的 HTML 进行分析的快速实时的解析器,事实上在应用过程中你更为惊叹于 HTML Parser 给你带来一些周到的处理。
考虑到很多读者并不清楚该项目的应用场合,因此我依照以前的步骤来组织这篇文章。首先是提出问题,讲述我在怎么一种情况下需要对 HTML 进行语法解析;其次是分析问题,如何考虑到使用 HTML Parser 来达到我所需要的目的;最后就是解决问题。
在实际开发一个内容管理项目时,由于内容是基于 HTML 的格式进行存储以及编写,并提供了一个基于浏览器的所见即所得的 HTML 编辑器。用户经常会直接从其他网站上复制一些带格式的内容后直接发布,首页会显示这些内容的概要信息,概要信息直接切割内容,从中读取前几个字。这种做法引发的问题是,截断的内容包含不完整的格式信息,因为切割的长度是固定的,比如我取前 400 个字用于做概要显示,这时候经常把内容中的一些表格切断,这些包含不完整格式信息的内容带来的问题就是它将破坏整个页面的布局,我们碰到最常见的问题就是页面被撑大,从输出的页面源码中可以看出就是因为这些不完整的表格标签导致。
![]() ![]() |
![]()
|
解决我们前面提出的问题的办法唯有使内容的切割更加智能化,可以自动的处理例如表格被切断这个影响格式的罪魁祸首。之前的做法是在切割后的内容中搜索没有被正常结束的表格标签,这解决了大部分内容无法正常显示的问题,但是这仅仅处理了最简单的情况,一旦出现嵌套表格被切割的情况也就无能为力,如果我们再自己来处理嵌套表格的情况,那考虑的问题就非常的多,因为为了使页面的排版美观,网页设计师大量的使用表格来进行处理,事实上这也是编写 HTML 的唯一办法。因此你可能要对各种各样的表格嵌套方式进行单独的处理,想象一下你编写一个万行的代码来完成一个看似简单的问题,你的领导肯定跟你急了 ^_^,问题还在于你这一万行的代码并不一定可以解决问题。因此在这个问题的处理上我们要尽量采取一些成熟稳定的API,HTML Parser 就是一个用于解析 HTML 文本的开源项目,它可以准确高效的对HTML文本中的格式、数据进行处理,有将近二十位来自世界各地的工程师在为这个项目工作。
HTML Parser项目主要可以用在以下两个方面:
1. 信息提取
- 文本信息抽取,例如对HTML进行有效信息搜索
- 链接提取,用于自动给页面的链接文本加上链接的标签
- 资源提取,例如对一些图片、声音的资源的处理
- 链接检查,用于检查HTML中的链接是否有效
- 页面内容的监控
2. 信息转换
- 链接重写,用于修改页面中的所有超链接
- 网页内容拷贝,用于将网页内容保存到本地
- 内容检验,可以用来过滤网页上一些令人不愉快的字词
- HTML信息清洗,把本来乱七八糟的HTML信息格式化
- 转成XML格式数据
HTML Parser并没有对以上提到的一些应用进行专门的处理,但是它完全可以胜任上面提及的功能,在实际应用中如果遇见上面提及的问题可以使用该项目来处理。
![]() ![]() |
![]()
|
接下来我主要针对我们在前面提过那个网页截断的问题进行处理。我的做法是强行对HTML内容进行截断,然后把截断后的内容传递给HTML Parser,由它来补全缺漏的标签。这样做可能有些内容显示不全,但是至少不会破坏页面的排版。通过这样一个简单的例子可以了解HTML Parser的基本结构以及使用流程。
首先从SourceForge网站上下载HTML Parser的压缩包(下载网址参照文章最后的资料参考部分),下图是解压后的目录结构,其中画红线的是我们所需要的jar包文件。把这个文件加到项目的类路径中,其他类可以不管!
![](https://i-blog.csdnimg.cn/blog_migrate/bb4f89995ed1a4343ccf877b1769457a.png)
/** * 获取HTML的预览信息,其中content是对象的一个属性,也就是待处理的HTML内容 * @return */ public String getPreviewContent(){ //截取前N个字符 String ct = StringUtils.left(content,MAX_COUNT); //对一些未完成的标签先补齐,避免出现例如<tab这样的标签 if(ct!=null&&content!=null) { int idx2 = ct.lastIndexOf('>'); int idx1 = ct.lastIndexOf('<'); if((idx2==-1 && idx1>=0) || idx1 > idx2) { String ct2 = content.substring(ct.length()); int idx3 = ct2.indexOf('>'); if(idx3!=-1 && idx3<(MAX_COUNT2-MAX_COUNT)) { ct += content.substring(ct.length(),ct.length()+idx3+1); } } } //对于一些页面嵌入了ActiveX对象进行预处理 if(ct!=null&&content!=null) { int idx2 = ct.toLowerCase().lastIndexOf("</object>"); int idx1 = ct.toLowerCase().lastIndexOf("<object"); if((idx2==-1 && idx1>=0) || idx1 > idx2) { String ct2 = content.substring(ct.length()).toLowerCase(); int idx3 = ct2.indexOf("</object>"); if(idx3!=-1) ct += content.substring(ct.length(),ct.length()+idx3+9); else ct = ct.substring(0,idx1); } } if(ct!=null&&content!=null) { Parser parser = Parser.createParser(new String(ct.getBytes(),ISO8859_1)); //中文信息必须转码后方可传入 Node [] tables = parser.extractAllNodesThatAre (TableTag.class); if(tables!=null&&tables.lengtd>0) { TableTag tableTag = (TableTag)tables[0]; ct = ct.substring(0,tableTag.getStartPosition()) + new String(tableTag.toHtml().getBytes(ISO8859_1)); //处理后的数据转回GBK的编码 } } return ct; } |
上面这段代码就是用于显示HTML概要信息的方法,其中粗体部分是使用HTML Parser进行处理的过程。类Parser是HTML Parser的入口,我们可以将HTML文本信息传入给它,或者可以直接传递一个URL地址,如下:
Parser parser = new Parser("http://www.javayou.com"); |
初始化一个Parser实例后,紧接着就是对所传入的HTML内容进行解析,大家注意红色粗体的那行代码,从方法名我们很容易理解,该方法就是将HTML内容中存在的所有的表格给解析出来放到一个数组去,该方法所需的参数就是节点的类型,我们这里用的是表格的标签,几乎HTML的标签中都有对应的一个对应的类,比如FormTag、InputTag、AppletTag等等,这些标签类都在org.htmlparser.tags包中。根据我们要处理不同的标签传入不同的类,这种做法使我们可以很方便的处理其他类型的标签。返回的数组中每个元素都是你传入类的一个实例,通过这个实例可以访问到当前这个标签的起始未知、结束标签的未知以及包含在标签中的文本信息,同时也可以访问其父标签以及所有的子标签等等,同时我们可以通过toHtml方法来对标签中包含的HTML信息进行清洗,HTML Parser会自动帮我们把一些没有关闭的标签加上,这样所生成的字符串中就包含着完整的格式控制信息,在页面上显示这样的信息也不会破坏版面布局,达到了我预期的效果。
为了使大家更直观的看到执行效果,我们再来一个小例子并附上执行的结果:
public static void main(String[] args) throws Exception { //不完整格式的HTML信息 String html = "我们是害虫<table>1234567890<table>lk你好中国"; Parser parser = Parser.createParser(new String(html.getBytes(),"8859_1")); Node [] tables = parser.extractAllNodesThatAre (TableTag.class); for (int i = 0; i < tables.length; i++) { TableTag tableTag = (TableTag)tables[i]; //打印出结束标签所在的未知 System.out.println("END POS:"+tableTag.getEndTag().getEndPosition()); //补齐未结束的标签并打印 System.out.println(new String(tableTag.toHtml().getBytes("8859_1"))); } } |
这段代码旨在找出一段不完整 HTML 信息中的所有表格标签,然后打印出经过格式化后的 HTML 信息,下图是在 Eclipse 环境下的执行结果。
![](https://i-blog.csdnimg.cn/blog_migrate/9bee508190608287d67036d4079adb06.png)
为了更好的在实际的业务中应用HTML Parser 项目,HTML Parser 还提供了几个例子用于处理前面我们提到的功能实现。这些例子在解压目录下的 bin 都有批处理命令可以执行,执行时给命令传入 URL 地址或者是 html 文件的路径即可。
HTML Parser 项目仅仅是提供给我们一个简单而强健的 API 用于分析 HTML 文本信息,更多的应用模式还有待于我们自己去发掘,希望本文能将你引入 HTML Parser 的大门。
============================================================================================
1.6版
- package com.thy.deltabletag;
- import org.htmlparser.NodeFilter;
- import org.htmlparser.Parser;
- import org.htmlparser.filters.NodeClassFilter;
- import org.htmlparser.tags.TableTag;
- import org.htmlparser.util.NodeList;
- public class DeleteTableTag {
- private static int MAX_COUNT = 0;
- public String getPreviewContent() {
- String content = "";
- // 截取前N个字符
- String ct = content.substring(0, MAX_COUNT);
- // 对一些未完成的标签先补齐,避免出现例如<tab这样的标签
- if (ct != null && content != null) {
- int idx1 = ct.lastIndexOf('<');
- int idx2 = ct.lastIndexOf('>');
- if ((idx2 == -1 && idx1 >= 0) || idx1 > idx2) { // 只有< 没有> 或者<> < ,总之是 < 后面没 >了
- String ct2 = content.substring(ct.length());
- int idx3 = ct2.indexOf('>');
- if (idx3 != -1) { // && idx3 < (MAX_COUNT2 - MAX_COUNT) 不知道干嘛用的
- ct += content
- .substring(ct.length(), ct.length() + idx3 + 1);
- }
- }
- }
- // 对于一些页面嵌入了ActiveX对象进行预处理
- if (ct != null && content != null) {
- int idx2 = ct.toLowerCase().lastIndexOf("</object>");
- int idx1 = ct.toLowerCase().lastIndexOf("<object");
- if ((idx2 == -1 && idx1 >= 0) || idx1 > idx2) {
- String ct2 = content.substring(ct.length()).toLowerCase();
- int idx3 = ct2.indexOf("</object>");
- if (idx3 != -1)
- ct += content
- .substring(ct.length(), ct.length() + idx3 + 9);
- else
- ct = ct.substring(0, idx1);
- }
- }
- if (ct != null && content != null) {
- try {
- Parser parser = Parser.createParser(new String(ct.getBytes()), "GB2312");
- // 中文信息必须转码后方可传入
- // Node[] tables = parser.NodeClassFilter(TableTag.class);
- NodeFilter filter = new NodeClassFilter(TableTag.class);
- NodeList nodes = parser.extractAllNodesThatMatch(filter);
- // Node[] tables = parser.extractAllNodesThatAre
- // (TableTag.class);
- if (nodes != null && nodes.size() > 0) {
- TableTag tableTag = (TableTag) nodes.elementAt(0); // Node textNode = (TagNode)nodes.elementAt(0);
- ct = ct.substring(0, tableTag.getStartPosition())
- + new String(tableTag.toHtml().getBytes("GB2312"));
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 处理后的数据转回GBK的编码
- }
- return ct;
- }
- public static void main(String[] args) throws Exception {
- // 不完整格式的HTML信息
- String html = "我们是害虫<table>1234567890<table>lk你好中国是";
- Parser parser = Parser.createParser(new String(html.getBytes()), "GB2312");
- NodeFilter filter = new NodeClassFilter(TableTag.class);
- NodeList nodes = parser.extractAllNodesThatMatch(filter);
- if(nodes!=null) {
- for(int i=0; i<nodes.size(); i++) {
- TableTag tableTag = (TableTag) nodes.elementAt(i);
- // 打印出结束标签所在的位置
- System.out.println("END POS:"
- + tableTag.getEndTag().getEndPosition());
- // 补齐未结束的标签并打印 用utf8就乱码
- System.out
- .println(new String(tableTag.toHtml().getBytes("GB2312")));
- }
- }
- }
- }