用htmlparser分析并抽取正文
转:http://hannibal730816.iteye.com/blog/149493
我这次要介绍的是如何抽取正文,这部分是最为核心的.因为如果不能很好的提取原有文章的内容和样式,那么搜索出来的东西?
就会惨不忍睹.根本就没有使用价值?
在做正文抽取模块之前我曾经参考过很多抽取模式,有配置模版的,有搞视觉匹配的.有搞关键字识别的.我挨个做了分析?
首先配置摸版是不太现实的,因为我在搜索技术资讯的时候,根本不知道会搜索到哪个网站,也根本没精力去配置摸版.所以这个行不通?
?
基于视觉效果的分析,这个难度比较大,而且只适合于规范的网站,而现在很多网站根本不规范,广告链接漫天飞.人家都把最好的?
位置留给广告了.而且我一直怀疑这个模式的可行性,它只是一个善意的推测.所以这方面没做过多尝试?
我在想,是否有种简单的方法呢?难道就没有什么共性吗??
我想所有的正文应该有个共同的特点,那就是正文的长度应该超过其他文字组合的长度.很少会有一句话的正文,很少会有长度?
短于标题的正文.所以这个应该成为一个突破口.?
接下来,有一个很重要的问题,那段最长的正文在哪里呢??
肯定是在一个TABLE,或者DIV,或者ParagraphTag里.那好,那就找到那个包含文字最多的DIV或者TABLE.?
不过问题又来了,HTML页面,经常是HTML元素的长度超过了正文的长度,有时候混入了不少的JAVASCRIPT.这些元素?
HTMLPARSER经常会误认为是正文加以识别,导致很多正文竟然是一段JAVASCRIPT.?
祛除杂质是一个关键,这里面要把那些HTML中常用的标签,以及连接中正文去除掉,否则,你搜索出来的很可能是别的什么,尤其?
当正文文字相对较少的时候.我在搜索SOHU页面的时候就经常遇到这个问题,原因是SOHU的页面不是严格按照DIV布局,里面有很多广告?
的JAVASCRIPT。新浪的有些页面也有这个现象,反到是一些中小网站的布局很规范,呵呵,真奇怪了。?
做完这些工作后,我发现仍然有些网页不能正常抓取,原因是HTMLPARSER对TEXT的认识有问题.例如一段文字在?
ParagraphTag中或者span中包含的,就不能很好的识别.所以要单独做个抽取ParagraphTag内容的函数.?
做完这些步骤后,有一个问题出来了就是正文中包含的图片,连接,粗体,正常的表格.这些问题一个个的冒出来.既然问题出来了?
那就要一个个的解决.解决了这些难题.我的网站抓取文章的质量就大大的提高了85%的准确率,基本达到实用阶段.我网站上的正文快照基本和原文保持一致.?
提供几个例子,大家可以看下原文和我抓取的有多少不同?
1. http://www.itsubway.com/context/20071218/11762.htm?
??????????????? 这个是单纯获取正文的例子,其中有粗体标签和链接?
2 http://www.itsubway.com/context/20071218/12041.htm?
这个是正文里混有图片和表格.?
我把抽取正文的部分代码和大家共享.这些代码基本解决了我在上面列举出来的问题。包括正文中混有图片,连接,粗体,表格等。?
??? 大家要是有兴趣可以改造下这些代码 请大家重点看protected List extractHtml(Node nodeP, PageContext context, String siteUrl)?
??? 这个函数是正文抽取的入口。我的这些函数写的不是很规范,别笑话!?
????/**
* 收集HTML页面信息 调用抓取函数 按照自己的摸版 生成网页 * @param url * @param urlEncode */ public void makeContext(ChannelLinkDO c) { String metakeywords = "<META content={0} name=keywords>"; String metatitle = "<TITLE>{0}</TITLE>"; String metadesc = "<META content={0} name=description>"; String netshap = "<p> 正文快照: 时间{0}</p> "; String tempLeate = "<LI class=active><A href="{0}" target=_blank>{1}</A></LI>"; String crop = "<p><A href="{0}" target=_blank>{1}</A></p> "; try { String siteUrl = getLinkUrl(c.getLink()); Parser parser = new Parser(c.getLink()); parser.setEncoding(c.getEncode()); for (NodeIterator e = parser.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); if (node instanceof Html) { PageContext context = new PageContext(); context.setNumber(0); context.setTextBuffer(new StringBuffer()); //抓取出内容 extractHtml(node, context, siteUrl); StringBuffer testContext = context.getTextBuffer(); //.....生成网页 } } } catch (Exception e) { System.out.println(e); } } private String getLinkUrl(String link) { String urlDomaiPattern = "(http://[^/]*?" + "/)(.*?)"; Pattern pattern = Pattern.compile(urlDomaiPattern, Pattern.CASE_INSENSITIVE + Pattern.DOTALL); Matcher matcher = pattern.matcher(link); String url = ""; while (matcher.find()) { int start = matcher.start(1); int end = matcher.end(1); url = link.substring(start, end - 1).trim(); } return url; } /** * 递归钻取正文信息 * @param nodeP * @return */ protected List extractHtml(Node nodeP, PageContext context, String siteUrl) throws Exception { NodeList nodeList = nodeP.getChildren(); boolean bl = false; if ((nodeList == null) || (nodeList.size() == 0)) { if (nodeP instanceof ParagraphTag) { ArrayList tableList = new ArrayList(); StringBuffer temp = new StringBuffer(); temp.append("<p style="TEXT-INDENT: 2em">"); tableList.add(temp); temp = new StringBuffer(); temp.append("</p>").append(lineSign); tableList.add(temp); return tableList; } return null; } if ((nodeP instanceof TableTag) || (nodeP instanceof Div)) { bl = true; } if (nodeP instanceof ParagraphTag) { ArrayList tableList = new ArrayList(); StringBuffer temp = new StringBuffer(); temp.append("<p style="TEXT-INDENT: 2em">"); tableList.add(temp); extractParagraph(nodeP, siteUrl, tableList); temp = new StringBuffer(); temp.append("</p>").append(lineSign); tableList.add(temp); return tableList; } ArrayList tableList = new ArrayList(); try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); if (node instanceof LinkTag) { tableList.add(node); setLinkImg(node, siteUrl); } else if (node instanceof ImageTag) { ImageTag img = (ImageTag) node; if (img.getImageURL().toLowerCase().indexOf("http://") < 0) { img.setImageURL(siteUrl + img.getImageURL()); } else { img.setImageURL(img.getImageURL()); } tableList.add(node); } else if (node instanceof ScriptTag || node instanceof StyleTag || node instanceof SelectTag) { } else if (node instanceof TextNode) { if (node.getText().length() > 0) { StringBuffer temp = new StringBuffer(); String text = collapse(node.getText() .replaceAll(" ", "") .replaceAll(" ", "")); temp.append(text.trim()); tableList.add(temp); } } else { if (node instanceof TableTag || node instanceof Div) { TableValid tableValid = new TableValid(); isValidTable(node, tableValid); if (tableValid.getTrnum() > 2) { tableList.add(node); continue; } } List tempList = extractHtml(node, context, siteUrl); if ((tempList != null) && (tempList.size() > 0)) { Iterator ti = tempList.iterator(); while (ti.hasNext()) { tableList.add(ti.next()); } } } } } catch (Exception e) { return null; } if ((tableList != null) && (tableList.size() > 0)) { if (bl) { StringBuffer temp = new StringBuffer(); Iterator ti = tableList.iterator(); int wordSize = 0; StringBuffer node; int status = 0; StringBuffer lineStart = new StringBuffer( "<p style="TEXT-INDENT: 2em">"); StringBuffer lineEnd = new StringBuffer("</p>" + lineSign); while (ti.hasNext()) { Object k = ti.next(); if (k instanceof LinkTag) { if (status == 0) { temp.append(lineStart); status = 1; } node = new StringBuffer(((LinkTag) k).toHtml()); temp.append(node); } else if (k instanceof ImageTag) { if (status == 0) { temp.append(lineStart); status = 1; } node = new StringBuffer(((ImageTag) k).toHtml()); temp.append(node); } else if (k instanceof TableTag) { if (status == 0) { temp.append(lineStart); status = 1; } node = new StringBuffer(((TableTag) k).toHtml()); temp.append(node); } else if (k instanceof Div) { if (status == 0) { temp.append(lineStart); status = 1; } node = new StringBuffer(((Div) k).toHtml()); temp.append(node); } else { node = (StringBuffer) k; if (status == 0) { if (node.indexOf("<p") < 0) { temp.append(lineStart); temp.append(node); wordSize = wordSize + node.length(); status = 1; } else { temp.append(node); status = 1; } } else if (status == 1) { if (node.indexOf("</p") < 0) { if (node.indexOf("<p") < 0) { temp.append(node); wordSize = wordSize + node.length(); } else { temp.append(lineEnd); temp.append(node); status = 1; } } else { temp.append(node); status = 0; } } } } if (status == 1) { temp.append(lineEnd); } if (wordSize > context.getNumber()) { context.setNumber(wordSize); context.setTextBuffer(temp); } return null; } else { return tableList; } } return null; } /** * 设置图象连接 * @param nodeP * @param siteUrl */ private void setLinkImg(Node nodeP, String siteUrl) { NodeList nodeList = nodeP.getChildren(); try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); if (node instanceof ImageTag) { ImageTag img = (ImageTag) node; if (img.getImageURL().toLowerCase().indexOf("http://") < 0) { img.setImageURL(siteUrl + img.getImageURL()); } else { img.setImageURL(img.getImageURL()); } } } } catch (Exception e) { return; } return; } /** * 钻取段落中的内容 * @param nodeP * @param siteUrl * @param tableList * @return */ private List extractParagraph(Node nodeP, String siteUrl, List tableList) { NodeList nodeList = nodeP.getChildren(); if ((nodeList == null) || (nodeList.size() == 0)) { if (nodeP instanceof ParagraphTag) { StringBuffer temp = new StringBuffer(); temp.append("<p style="TEXT-INDENT: 2em">"); tableList.add(temp); temp = new StringBuffer(); temp.append("</p>").append(lineSign); tableList.add(temp); return tableList; } return null; } try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); if (node instanceof ScriptTag || node instanceof StyleTag || node instanceof SelectTag) { } else if (node instanceof LinkTag) { tableList.add(node); setLinkImg(node, siteUrl); } else if (node instanceof ImageTag) { ImageTag img = (ImageTag) node; if (img.getImageURL().toLowerCase().indexOf("http://") < 0) { img.setImageURL(siteUrl + img.getImageURL()); } else { img.setImageURL(img.getImageURL()); } tableList.add(node); } else if (node instanceof TextNode) { if (node.getText().trim().length() > 0) { String text = collapse(node.getText() .replaceAll(" ", "") .replaceAll(" ", "")); StringBuffer temp = new StringBuffer(); temp.append(text); tableList.add(temp); } } else if (node instanceof Span) { StringBuffer spanWord = new StringBuffer(); getSpanWord(node, spanWord); if ((spanWord != null) && (spanWord.length() > 0)) { String text = collapse(spanWord.toString() .replaceAll(" ", "") .replaceAll(" ", "")); StringBuffer temp = new StringBuffer(); temp.append(text); tableList.add(temp); } } else if (node instanceof TagNode) { String tag = node.toHtml(); if (tag.length() <= 10) { tag = tag.toLowerCase(); if ((tag.indexOf("strong") >= 0) || (tag.indexOf("b") >= 0)) { StringBuffer temp = new StringBuffer(); temp.append(tag); tableList.add(temp); } } else { if (node instanceof TableTag || node instanceof Div) { TableValid tableValid = new TableValid(); isValidTable(node, tableValid); if (tableValid.getTrnum() > 2) { tableList.add(node); continue; } } extractParagraph(node, siteUrl, tableList); } } } } catch (Exception e) { return null; } return tableList; } protected void getSpanWord(Node nodeP, StringBuffer spanWord) { NodeList nodeList = nodeP.getChildren(); try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); if (node instanceof ScriptTag || node instanceof StyleTag || node instanceof SelectTag) { } else if (node instanceof TextNode) { spanWord.append(node.getText()); } else if (node instanceof Span) { getSpanWord(node, spanWord); } else if (node instanceof ParagraphTag) { getSpanWord(node, spanWord); } else if (node instanceof TagNode) { String tag = node.toHtml().toLowerCase(); if (tag.length() <= 10) { if ((tag.indexOf("strong") >= 0) || (tag.indexOf("b") >= 0)) { spanWord.append(tag); } } } } } catch (Exception e) { } return; } /** * 判断TABLE是否是表单 * @param nodeP * @return */ private void isValidTable(Node nodeP, TableValid tableValid) { NodeList nodeList = nodeP.getChildren(); /**如果该表单没有子节点则返回**/ if ((nodeList == null) || (nodeList.size() == 0)) { return; } try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); /**如果子节点本身也是表单则返回**/ if (node instanceof TableTag || node instanceof Div) { return; } else if (node instanceof ScriptTag || node instanceof StyleTag || node instanceof SelectTag) { return; } else if (node instanceof TableColumn) { return; } else if (node instanceof TableRow) { TableColumnValid tcValid = new TableColumnValid(); tcValid.setValid(true); findTD(node, tcValid); if (tcValid.isValid()) { if (tcValid.getTdNum() < 2) { if (tableValid.getTdnum() > 0) { return; } else { continue; } } else { if (tableValid.getTdnum() == 0) { tableValid.setTdnum(tcValid.getTdNum()); tableValid.setTrnum(tableValid.getTrnum() + 1); } else { if (tableValid.getTdnum() == tcValid.getTdNum()) { tableValid.setTrnum(tableValid.getTrnum() + 1); } else { return; } } } } } else { isValidTable(node, tableValid); } } } catch (Exception e) { return; } return; } /** * 判断是否有效TR * @param nodeP * @param TcValid * @return */ private void findTD(Node nodeP, TableColumnValid tcValid) { NodeList nodeList = nodeP.getChildren(); /**如果该表单没有子节点则返回**/ if ((nodeList == null) || (nodeList.size() == 0)) { return; } try { for (NodeIterator e = nodeList.elements(); e.hasMoreNodes();) { Node node = (Node) e.nextNode(); /**如果有嵌套表单**/ if (node instanceof TableTag || node instanceof Div || node instanceof TableRow || node instanceof TableHeader) { tcValid.setValid(false); return; } else if (node instanceof ScriptTag || node instanceof StyleTag || node instanceof SelectTag) { tcValid.setValid(false); return; } else if (node instanceof TableColumn) { tcValid.setTdNum(tcValid.getTdNum() + 1); } else { findTD(node, tcValid); } } } catch (Exception e) { tcValid.setValid(false); return; } return; } protected String collapse(String string) { int chars; int length; int state; char character; StringBuffer buffer = new StringBuffer(); chars = string.length(); if (0 != chars) { length = buffer.length(); state = ((0 == length) || (buffer.charAt(length - 1) == ' ') || ((lineSign_size <= length) && buffer.substring(length - lineSign_size, length).equals(lineSign))) ? 0 : 1; for (int i = 0; i < chars; i++) { character = string.charAt(i); switch (character) { case '\u0020': case '\u0009': case '\u000C': case '\u200B': case '\u00a0': case '\r': case '\n': if (0 != state) { state = 1; } break; default: if (1 == state) { buffer.append(' '); } state = 2; buffer.append(character); } } } return buffer.toString(); }
?
????
?
1 楼 hegang126 2011-11-07 你好啊,最近做网页正文抽取,你的这个完整的源代码能给一份吗?,我邮箱是 hegang_126@126.com,谢谢啊