htmlparser(HTML Parser )是 sourceforge.net 上的一个成熟的 java 项目。它可以解析 HTML 页面,用来抽取或修改其内容,通过过滤器、访问者来访问程序关心的标签(Tag)。
一般使用 htmlparser 来做 HTML 解析后的抽取工作,但对 HTML 内容进行结构化修改则比较少用到。前段有一个对 HTML 抓取后修改其中所有链接地址的需求,使用 htmlparser 将 HTML 页面中附带资源(non html resource)的 URL 地址都做一下转换,包括链接(LinkTag)、图片(ImageTag)、框架页(FrameTag)、表单(FormTag)标签中指定的资源地址,还包括 head 中的 link(HeaderLinkTag)标签中指定的 CSS/favicon.ico 以及 script(ScriptExTag)标签中指定的 JS 文件资源地址。对于后二者 HeaderLinkTag 和 ScriptExTag 标签的处理功能在 htmlparser 是没有实现的,需要自己通过编写继承于 TagNode/CompositeTag 基类的自定义标签来做匹配、修改逻辑。
OK 言归正传,下面给出在 htmlparser 中修改 HTML 内容的 2 种方法。
首先,第 1 种修改方法可以通过自定义继承 UrlModifyingVisitor 的子类来自定义修改 URL 逻辑,通过 org.htmlparser.Parser 提供的遍历模式来完成修改。 直接上代码。
import java.net.MalformedURLException; import java.net.URL; import java.util.logging.Logger; import org.htmlparser.Tag; import org.htmlparser.Text; import org.htmlparser.tags.FormTag; import org.htmlparser.tags.FrameTag; import org.htmlparser.tags.ImageTag; import org.htmlparser.tags.LinkTag; import org.htmlparser.visitors.UrlModifyingVisitor; import org.lzy.fwswaper.FwswaperServlet; import org.lzy.fwswaper.htmlhandler.HtmlHandlerHelper; import org.lzy.fwswaper.util.ExceptionUtils; public class HtmlparserUrlModifier extends UrlModifyingVisitor { private static final Logger log = Logger.getLogger(HtmlparserUrlModifier.class.getName()); private URL base = null; public HtmlparserUrlModifier(URL base) { super(""); this.setBaseUrl(base); } public void setBaseUrl(URL base) { if (!HtmlHandlerHelper.isHttpLikeProtocolUrl(base)) throw new IllegalArgumentException(String.format( "Base url argument '%s' is not http like protocol. " + "They are not prefix with '%s' or '%s'", this.base.toString(), HtmlHandlerHelper.HttpProtocol, HtmlHandlerHelper.HttpsProtocol)); this.base = base; } public void visitStringNode(Text stringNode) { // MUST override this method. // Super class UrlModifingVistor wrote: 'this.modifiedResult.append (stringNode.toHtml());'. // It will append stringNode.toHtml() conent to outside of <html/> tag if not override it. } public void visitTag(Tag tag) { try { if (tag instanceof LinkTag) { LinkTag link = (LinkTag) tag; log.info(String.format("Found link: '%s' => '%s'.", link.getLinkText(), link.extractLink())); if (link.isHTTPLikeLink()) link.setLink(this.modifying(new URL(base, link.getLink()))); } else if (tag instanceof HeaderLinkTag) { HeaderLinkTag link = (HeaderLinkTag) tag; log.info(String.format("Found head link: '%s' => '%s'.", link.getLinkText(), link.getLink())); URL url = new URL(base, link.getLink()); if (HtmlHandlerHelper.isHttpLikeProtocolUrl(url)) link.setLink(this.modifying(url)); } else if (tag instanceof ScriptExTag) { ScriptExTag script = (ScriptExTag) tag; String src = script.getSrc(); if ((src != null) && (src.length() > 0)) { log.info(String.format("Found script: '%s' => '%s'.", script.getLanguage(), src)); URL url = new URL(base, src); if (HtmlHandlerHelper.isHttpLikeProtocolUrl(url)) script.setSrc(this.modifying(url)); } } else if (tag instanceof ImageTag) { ImageTag img = (ImageTag) tag; log.info(String.format("Found image => '%s'.", img.getImageURL())); URL url = new URL(base, img.getImageURL()); if (HtmlHandlerHelper.isHttpLikeProtocolUrl(url)) img.setImageURL(this.modifying(url)); } else if (tag instanceof FrameTag) { FrameTag frame = (FrameTag) tag; log.info(String.format("Found frame: '%s' => '%s'.", frame.getText(), frame.getFrameLocation())); URL url = new URL(base, frame.getFrameLocation()); if (HtmlHandlerHelper.isHttpLikeProtocolUrl(url)) frame.setFrameLocation(this.modifying(url)); } else if (tag instanceof FormTag) { FormTag form = (FormTag) tag; log.info(String.format("Found form: '%s' => (%s) '%s'.", form.getFormName(), form.getFormMethod(), form.extractFormLocn())); URL url = new URL(base, form.extractFormLocn()); if (HtmlHandlerHelper.isHttpLikeProtocolUrl(url)) form.setFormLocation(this.modifying(url)); } } catch(Exception e) { log.warning(String.format("Modify url failed. Exception message: '%s'.", ExceptionUtils.getStackTrace(e))); } super.visitTag(tag); } protected String modifying(URL url) throws MalformedURLException { // Modifying url and return. return null; } }
PrototypicalNodeFactory factory = new PrototypicalNodeFactory(); factory.registerTag(new HeaderLinkTag()); factory.registerTag(new ScriptExTag()); Parser parser = Parser.createParser(html, charset); parser.setNodeFactory(factory); // Match and modify link image and frame tag url address. HtmlparserUrlModifier modifier = new HtmlparserUrlModifier(this.base); parser.visitAllNodesWith(modifier); String html = modifier.getModifiedResult();
通过上面的 HtmlparserUrlModifier 中的具体处理,并在 org.htmlparser.PrototypicalNodeFactory 中注册 HeaderLinkTag 和 ScriptExTag 这 2 个要匹配的自定义标签类型,我们就可以对 html 内容进行结构化修改了,实际看了 htmlparser 的源码就会发现根本上最后就是 setAttribute 方法的调用。
这里有两个问题需要说明:
1. 在继承 UrlModifyingVisitor 对它进行扩展时,一定要重载其 visitStringNode 方法,否则会发现在 htmlparser 处理后的结果中,在 html 标签外还会有页面所有的文本内容的副本,这里的文本是指那些用于在浏览器中显示的文字内容。正如上述代码中所示,在我重载的 visitStringNode 方法中没有做任何处理。通过 UrlModifyingVisitor 源码可以看到 visitStringNode 方法默认实现如下。
public void visitStringNode(Text stringNode) { modifiedResult.append (stringNode.toHtml()); }
2. 不能通过 org.htmlparser.Parser 类的 parse 方法在解析过程中对 html 内容进行修改,因为在解析完成后,你会发现必须通过 reset 方法来复位,这样之前的处理结果就全部失效了。
其次,第 2 种方法是通过 org.htmlparser.util.NodeList 保存结构化的 html 内容并对其修改,最后通过它的 toHtml 方法将修改结果导出。 示例代码如下所示。
private String parse(String html, String charset) throws ParserException { Parser parser = Parser.createParser(html, charset); NodeList list = parser.parse(null); String html = recurse(list).toHtml(); System.out.println(html); } private NodeList recurse(NodeList list) { if(list==null) return null; Node node = null; SimpleNodeIterator iterator = list.elements(); while(iterator.hasMoreNodes()) { node = iterator.nextNode(); if(node==null) break; if(node instanceof Tag) { Tag tag = (Tag)node; // Modifying attributes or something else. recurse(node.getChildren()); } } return null; }
好了,这次要说的就是上面的这 2 个方法,希望能对有需要的兄弟有所帮助。记得上次在问答频道里有人问过这问题。
作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。