手动解析HTML是一件很崩溃的事情,sun的swing里也有解析HTML的东东,不过已经是古董了,实在不好拿出来丢Java的人了。
今天要用的是Apache的一个开源项目,html parser。
它的强大不用多说,且看它提供的几个sample吧。
首先去htmlparser.sourceforge.net上去下载,在解压开之后目录里有几个目录,分别存放着src,jars,javadoc之类的,其中bin里有好几个xxx.cmd,用命令行运行那几个脚本,参数就加某个网页的地址就行了。
主意不要拿javaeye的网页做测试哦,不行的,因为sample里的HTTP客户端是用的sun的URLConnection,请求javaeye的话响应的内容是一个空文档,这只是URLConnection的众多bug之一,建议大家不要用哦,如果你只是测试的话那到无所谓咯。
其中stringextractor.cmd是解析出网页内的纯文本内容的,linkextractor.cmd是解析网页里的连接的,非常有用哦。
接下来就要开始编程了,如何使用这个好工具捏?貌似官方并米有详细的文档说明,就这几个sample就已经大致说清楚如何用了。我们找到这几个cmd文件,打开找到其中调用的是那个类,因为它的sample并没有单独拿出来,得要我们自己去它的source里去找。我的建议是在Eclipse的工程里添加那个jar文件,然后把jar文件设置src,这样代码看起来爽一些啦。
小研究一下那几个sample是如何实现的,然后想想你自己的需求,借题发挥吧,这是java程序员的强项了。
上面提到了那几个脚本的参数是网页地址,它的代码里也是在构建Parser对象的时候用的也是url字符串作为参数的,其实如果你想用URLConnection之外的其他Http客户端组件来代替html parser的内置请求方式,也不用担心,因为Parser对象同样提供了html内容作为参数的构建方式:Parser p = Parser.createParser(String html, String charser);
下面是我在项目中用的两个小method,模仿了sample里的代码
/** * 用来抽取html里的连接地址,并转换了相对地址为绝对地址 */ private void extractLinks(URL pageURL, Parser parser) { Map<String, String> links = new HashMap<String, String>(); try { NodeFilter filter = new NodeClassFilter(LinkTag.class); NodeList list = parser.extractAllNodesThatMatch(filter); for (int i = 0; i < list.size(); i++) { LinkTag n = (LinkTag) list.elementAt(i); String link = n.getLink(); if (!n.isHTTPLink()) { continue; } if (link.startsWith("http://")) { try { link = new URL(link).toString(); links.put(HashUtil.md5(link), link); } catch (MalformedURLException e) { continue; } } else { try { link = new URL(pageURL, link).toString(); links.put(HashUtil.md5(link), link); } catch (MalformedURLException e) { continue; } } } } catch (ParserException e) { // TODO 处理异常 e.printStackTrace(); } catch (RuntimeException e) { // TODO 处理异常 e.printStackTrace(); } doc.setDocs(createDoc(links)); }
/** * 抽取html里的文本内容,里面使用了一个自定义的节点访问器,TextExtractVisitor,下面一段有定义 */ private Map<String, Object> extractText(Parser parser) { try { TextExtractVistor textExtractor = new TextExtractVistor(); parser.visitAllNodesWith(textExtractor); doc.getText().setText(textExtractor.getText()); doc.getText().setDigest(textExtractor.getDigest()); doc.getText().setKeywords(textExtractor.getKeywords()); doc.getText().setTitle(textExtractor.getTitle()); return textExtractor.getHashedTextTags(); } catch (ParserException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }
/** * 用来抽取html里文本等相关内容的节点访问器 */ public class TextExtractVistor extends NodeVisitor { StringBuilder textBuf = new StringBuilder(4096); private boolean mIsScript; private boolean mIsStyle; private boolean mIsPre; protected int mCollapseState; private final String NEWLINE = System.getProperty("line.separator"); private final int NEWLINE_SIZE = NEWLINE.length(); private boolean mIsMeta; private boolean mIsH; private boolean isStrong; private StringBuilder digest = new StringBuilder(512); private StringBuilder keywords = new StringBuilder(100); private boolean isTtitle; private String title; private Map<String, Object> textTags = new HashMap<String, Object>(); protected void carriageReturn() { int length; length = textBuf.length(); if ((0 != length) && ((NEWLINE_SIZE <= length) && (!textBuf.substring( length - NEWLINE_SIZE, length).equals(NEWLINE)))) textBuf.append(NEWLINE); mCollapseState = 0; } public void visitStringNode(Text string) { if (!mIsScript && !mIsStyle) { String text = string.getText(); textTags.put(HashUtil.md5(text), null); if (!mIsPre) { text = Translate.decode(text); text = text.replace('\u00a0', ' '); collapse(textBuf, text); } else { textBuf.append(text); if(mIsMeta){ keywords.append(text); }else if(mIsH || isStrong){ digest.append(text); }else if(isTtitle){ title = text; } } } } public void visitTag(Tag tag) { String name = tag.getTagName(); if (name.equalsIgnoreCase("PRE")) mIsPre = true; else if (name.equalsIgnoreCase("SCRIPT")) mIsScript = true; else if (name.equalsIgnoreCase("STYLE")) mIsStyle = true; else if (name.equalsIgnoreCase("META")) mIsMeta = true; else if (name.startsWith("[H|h]")) mIsH = true; else if (name.equalsIgnoreCase("STRONG")) isStrong = true; else if (name.equalsIgnoreCase("TITLE")) isTtitle = true; if (tag.breaksFlow()) carriageReturn(); } public void visitEndTag(Tag tag) { String name; name = tag.getTagName(); if (name.equalsIgnoreCase("PRE")) mIsPre = false; else if (name.equalsIgnoreCase("SCRIPT")) mIsScript = false; else if (name.equalsIgnoreCase("STYLE")) mIsStyle = false; else if (name.equalsIgnoreCase("META")) mIsMeta = false; else if (name.startsWith("[H|h]")) mIsH = false; else if (name.equalsIgnoreCase("STRONG")) isStrong = false; else if (name.equalsIgnoreCase("TITLE")) isTtitle = false; } public String getText() { return textBuf.toString(); } public String getDigest(){ return digest.toString(); } public String getKeywords(){ return keywords.toString(); } public String getTitle(){ return title; } public Map<String, Object> getHashedTextTags(){ return textTags; } protected void collapse(StringBuilder buffer, String string) { int chars; char character; chars = string.length(); if (0 != chars) { for (int i = 0; i < chars; i++) { character = string.charAt(i); switch (character) { // see HTML specification section 9.1 White space // http://www.w3.org/TR/html4/struct/text.html#h-9.1 case '\u0020': case '\u0009': case '\u000C': case '\u200B': case '\r': case '\n': if (0 != mCollapseState) mCollapseState = 1; break; default: if (1 == mCollapseState) buffer.append(' '); mCollapseState = 2; buffer.append(character); } } } } }