information extraction

使用HtmlParser抽取页面所有文本数据的方法总结

方法test1应该说是最有效的,避免了出现很多空格。
package   test;

import   java.io.BufferedReader;
import   java.io.File;
import   java.io.FileInputStream;
import   java.io.InputStreamReader;

import   org.htmlparser.Node;
import   org.htmlparser.NodeFilter;
import   org.htmlparser.Parser;
import   org.htmlparser.filters.NodeClassFilter;
import   org.htmlparser.filters.OrFilter;
import   org.htmlparser.nodes.TextNode;
import   org.htmlparser.parserapplications.StringExtractor;
import   org.htmlparser.tags.LinkTag;
import   org.htmlparser.util.NodeList;
import   org.htmlparser.util.ParserException;
import   org.htmlparser.visitors.HtmlPage;
import   org.htmlparser.visitors.TextExtractingVisitor;

/**
*   演示了Html   Parse的应用.
*/

public   class   ParseHtmlTest
{

public   static   void   main(String[]   args)   throws   Exception
{
String   aFile   =   “D:\\Eclipse\\workspace\\search\\test001\\content_1349887.htm”;

String   content   =   readTextFile(aFile,   “GBK”);
StringExtractor   se;
se   =   new   StringExtractor   (aFile);
System.out.println(se.extractStrings(false));

test1(content);
System.out.println(“=====Test1==============================”);

test2(content);
System.out.println(“=====Test2==========================”);

test3(content);
System.out.println(“=====Test3===============================”);

test4(content);
System.out.println(“=====Test4===============================”);

test5(aFile);
System.out.println(“======Test5==============================”);

//访问外部资源,相对慢
test5(“http://www.medlink.com.cn”);
System.out.println(“====================================”);

}

/**
*   读取文件的方式来分析内容.
*   filePath也可以是一个Url.
*
*   @param   resource   文件/Url
*/
public   static   void   test5(String   resource)   throws   Exception
{
Parser   myParser   =   new   Parser(resource);

//设置编码
myParser.setEncoding(“GBK”);

HtmlPage   visitor   =   new   HtmlPage(myParser);

myParser.visitAllNodesWith(visitor);

String   textInPage   =   visitor.getBody().toString();

System.out.println(textInPage);
}

/**
*   按页面方式处理.对一个标准的Html页面,推荐使用此种方式.
*/
public   static   void   test4(String   content)   throws   Exception
{
Parser   myParser;
myParser   =   Parser.createParser(content,   “GBK”);

HtmlPage   visitor   =   new   HtmlPage(myParser);

myParser.visitAllNodesWith(visitor);

String   textInPage   =   visitor.getTitle();

System.out.println(textInPage);
System.out.println(“--------------------”);
System.out.println(visitor.getBody());
}

/**
*   利用Visitor模式解析html页面.
*
*   小优点:翻译了<>等符号
*   缺点:好多空格,无法提取link
*
*/
public   static   void   test3(String   content)   throws   Exception
{
Parser   myParser;
myParser   =   Parser.createParser(content,   “GBK”);

TextExtractingVisitor   visitor   =   new   TextExtractingVisitor();

myParser.visitAllNodesWith(visitor);

String   textInPage   =   visitor.getExtractedText();

System.out.println(textInPage);
}

/**
*   得到普通文本和链接的内容.
*
*   使用了过滤条件.
*/
public   static   void   test2(String   content)   throws   ParserException
{
Parser   myParser;
NodeList   nodeList   =   null;

myParser   =   Parser.createParser(content,   “GBK”);

NodeFilter   textFilter   =   new   NodeClassFilter(TextNode.class);
NodeFilter   linkFilter   =   new   NodeClassFilter(LinkTag.class);

//暂时不处理   meta
//NodeFilter   metaFilter   =   new   NodeClassFilter(MetaTag.class);

OrFilter   lastFilter   =   new   OrFilter();
lastFilter.setPredicates(new   NodeFilter[]   {   textFilter,   linkFilter   });

nodeList   =   myParser.parse(lastFilter);

Node[]   nodes   =   nodeList.toNodeArray();

for   (int   i   =   0;   i   <   nodes.length;   i++)
{
Node   anode   =   (Node)   nodes[i];

String   line   =   “”;
if   (anode   instanceof   TextNode)
{
TextNode   textnode   =   (TextNode)   anode;
line   =   textnode.toPlainTextString().trim();
//line   =   textnode.getText();
}
//                         else   if   (anode   instanceof   LinkTag)
//                         {
//                                 LinkTag   linknode   =   (LinkTag)   anode;
//
//                                 line   =   linknode.getLink();
//                                 //@todo   过滤jsp标签:可以自己实现这个函数
//                                 //line   =   StringFunc.replace(line,   “<%.*%>”,   “”);
//                         }

if   (isTrimEmpty(line))
continue;

System.out.println(line);
}
}

/**
*   解析普通文本节点.
*
*   @param   content
*   @throws   ParserException
*/
public   static   void   test1(String   content)   throws   ParserException
{
Parser   myParser;
Node[]   nodes   =   null;

myParser   =   Parser.createParser(content,   null);

nodes   =   myParser.extractAllNodesThatAre(TextNode.class);   //exception   could   be   thrown   here

for   (int   i   =   0;   i   <   nodes.length;   i++)
{
TextNode   textnode   =   (TextNode)   nodes[i];
String   line   =   textnode.toPlainTextString().trim();
if   (line.equals(“”))
continue;
System.out.println(line);
}

}

/**
*   读取一个文件到字符串里.
*
*   @param   sFileName     文件名
*   @param   sEncode       String
*   @return   文件内容
*/
public   static   String   readTextFile(String   sFileName,   String   sEncode)
{
StringBuffer   sbStr   =   new   StringBuffer();

try
{
File   ff   =   new   File(sFileName);
InputStreamReader   read   =   new   InputStreamReader(new   FileInputStream(ff),
sEncode);
BufferedReader   ins   =   new   BufferedReader(read);

String   dataLine   =   “”;
while   (null   !=   (dataLine   =   ins.readLine()))
{
sbStr.append(dataLine);
sbStr.append(“\r\n”);
}

ins.close();
}
catch   (Exception   e)
{
//   LogMan.error(“read   Text   File   Error”,   e);
}

return   sbStr.toString();
}

/**
*   去掉左右空格后字符串是否为空
*   @param   astr   String
*   @return   boolean
*/
public   static   boolean   isTrimEmpty(String   astr)
{
if   ((null   ==   astr)   ||   (astr.length()   ==   0))
{
return   true;
}
if   (isBlank(astr.trim()))
{
return   true;
}
return   false;
}

/**
*   字符串是否为空:null或者长度为0.
*   @param   astr   源字符串.
*   @return   boolean
*/
public   static   boolean   isBlank(String   astr)
{
if   ((null   ==   astr)   ||   (astr.length()   ==   0))
{
return   true;
}
else
{
return   false;
}
}

}

正文提取二 转载

我这次要介绍的是如何抽取正文,这部分是最为核心的.因为如果不能很好的提取原有文章的内容和样式,那么搜索出来的东西
就会惨不忍睹.根本就没有使用价值

在做正文抽取模块之前我曾经参考过很多抽取模式,有配置模版的,有搞视觉匹配的.有搞关键字识别的.我挨个做了分析
首先配置摸版是不太现实的,因为我在搜索技术资讯的时候,根本不知道会搜索到哪个网站,也根本没精力去配置摸版.所以这个行不通

基于视觉效果的分析,这个难度比较大,而且只适合于规范的网站,而现在很多网站根本不规范,广告链接漫天飞.人家都把最好的
位置留给广告了.而且我一直怀疑这个模式的可行性,它只是一个善意的推测.所以这方面没做过多尝试

我在想,是否有种简单的方法呢?难道就没有什么共性吗?

我想所有的正文应该有个共同的特点,那就是正文的长度应该超过其他文字组合的长度.很少会有一句话的正文,很少会有长度
短于标题的正文.所以这个应该成为一个突破口.

接下来,有一个很重要的问题,那段最长的正文在哪里呢?
肯定是在一个TABLE,或者DIV,或者ParagraphTag里.那好,那就找到那个包含文字最多的DIV或者TABLE.

不过问题又来了,HTML页面,经常是HTML元素的长度超过了正文的长度,有时候混入了不少的JAVASCRIPT.这些元素
HTMLPARSER经常会误认为是正文加以识别,导致很多正文竟然是一段JAVASCRIPT.
祛除杂质是一个关键,这里面要把那些HTML中常用的标签,以及连接中正文去除掉,否则,你搜索出来的很可能是别的什么,尤其
当正文文字相对较少的时候.我在搜索SOHU页面的时候就经常遇到这个问题,原因是SOHU的页面不是严格按照DIV布局,里面有很多广告
的JAVASCRIPT。新浪的有些页面也有这个现象,反到是一些中小网站的布局很规范,呵呵,真奇怪了。

做完这些工作后,我发现仍然有些网页不能正常抓取,原因是HTMLPARSER对TEXT的认识有问题.例如一段文字在
ParagraphTag中或者span中包含的,就不能很好的识别.所以要单独做个抽取ParagraphTag内容的函数.

做完这些步骤后,有一个问题出来了就是正文中包含的图片,连接,粗体,正常的表格.这些问题一个个的冒出来.既然问题出来了
那就要一个个的解决.解决了这些难题.我的网站抓取文章的质量就大大的提高了85%的准确率,基本达到实用阶段.我网站上的正文快照基本和原文保持一致.

提供几个例子,大家可以看下原文和我抓取的有多少不同
1. [url]http://www.itsubway.com/context/20071218/11762.htm[/url]
这个是单纯获取正文的例子,其中有粗体标签和链接

2 [url]http://www.itsubway.com/context/20071218/12041.htm[/url]
这个是正文里混有图片和表格.

为了感谢chinaunix保留我的帖子,感谢大家看我的帖子.我把抽取正文的部分代码和大家共享.这些代码基本解决了我在
上面列举出来的问题。包括正文中混有图片,连接,粗体,表格等。大家要是有兴趣可以改造下这些代码,完整的代码在附件中
代码中有全部的抽取算法.你可以新建个项目导入这些类,稍微调整下,就可以运行.其中有个字串替换函数,这个我没有加,诸位可以自己
随便找个这样的函数.你可以用我提供的代码试验下,看看抽取的效果如何.其中ExtractContext是程序执行的入口.context.vm是我定义的生成正文的摸版.你可以自己定义喜欢的格式.

请大家重点看protected List extractHtml(Node nodeP, PageContext context, String siteUrl)
这个函数是正文抽取的入口。我的这些函数写的不是很规范,别笑话!

最后,有几句话想说下,我看到有个评论说我是个AD广告贴,我对这种看法表示理解,从客观的角度说,我写这帖子
既是和大家分享一些思路,同时自然也是在为自己的思路做个宣传。如果双方都有所收益,那也不是什么坏事情。难道你喜欢研究半天
然后默默无闻的藏起来吗?互联网是个开放的平台,应该要能包容一切不同的做法,理念。尤其是我们这些没日没夜辛苦做技术的人。
我办这个网站的初衷一方面是想把自己的那点点技术发挥出作用来,找出一些好的IT网站和大家分享。另一方面也是想能使自己或
者帮助其他的程序员开拓下视野和思路。

我感谢CHINAUNIX的管理员保留我的帖子,我相信有容乃大!

/     /**
* 递归钻取正文信息 --- 详细见附件
* @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(“&nbsp;”, “”)
.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;
}

关于网页正文抽取的一个不错的帖子(转) 分享

最近在做网页正文抽取的东西,需要从典型的新闻页面中取出网页的正文,尽可能地滤掉广告,并且找到文章的标题。

我所处理的网页还没有变态到内容全部用java script提取的地步。也就是说,信息的内容全部是在html代码中的明文。

网络中的html页面数以亿万计,不同的网站,甚至同一网站不同板块的构成也不尽相同,并且很多网站的代码是由服务器后台动态生成,也有可能代码本 身可能就有错误。即使摸清了某网站的代码规律,修正了可能有的错误,但是所写的抽取程序可能也只能是对这个网站的这个板块有效,要做到能够针对各类不同页 面都有一个比较好的抽取结果是一个有点意思的挑战。

首先n多论文已经定义了网页中存在的噪声,也就是诸如页面导航、广告信息等会对抽取结果的准确性产生干扰的信息。那么尽可能消除这些噪声是第一件事。在噪声基本消除的情况下,找到正文块就是第二件事了。不过我更加喜欢先判定正文区域,在正文中剔出噪声的想法。

作为噪声,和正文的一个很大的不同就是,正文多是非标签的文字性内容比较多,也就是很长的连续文字。网页中最为常见的噪声就是存在 于<a>块中的链接信息,广告导航基本上都是以这样的形式存在的。但是简单地剔除<a>块也是不行的,因为许多网站的正文中出现 的热门词汇都会附带一个相关的链接。

首先我将html源代码交由jtidy进行一个预处理,处理成规范的xml代码。之所以处理成xml代码,是我希望能够将其建立成为一颗dom树来进行反复的扫瞄处理。

得到dom树对象document后,首先进行第一轮扫描,扫除script块,style块,没有文字内容的空块。如果某节点下出现了连续 的<a>块,则说明这些<a>块全部都是广告,因为正文中基本不会出现连续不断的链接,需要扫除。另外,许多网站的正文是由连续 的<p>块组成的,那么针对这样的块,第一轮扫瞄中需要将其合并为一个<p>块。在合并两个<p>时,由于少了一 个<p>,需要补充一个<br>块来补充一个换行。

经过第一轮扫描,代码应该已经干净了一些,这时我们可以开始寻找正文。正文可能存在于tr,td,div,p等块中,进行第二次扫描,提取这些节 点,然后计算其文字内容的长度。一般情况下,正文所在的块文字长度应该会远远超过噪音块,但是也有极特殊情况数量众多的零碎的广告文字长度也能超过言简意 赅的正文,这时我的方案是借鉴很多游戏中对连击的奖励,文字连续越长,越后面的文字得分越高,可能一个100个字长的段落第100个字就顶10个零碎的4 字广告的得分((1 + 2 + 3 + 4) * 10 = 100 )。这样越连续的文字得分就越高,保证正文尽可能地出线。

这时挑选出的正文一般也就是到位了,但是问题是很可能在头尾残留了一些<a>块广告。我认为这些<a>块广告与正文中广告 有很大的不同。这些<a>广告的马脚就是其父节点,它们的父节点要么也包含了正文所在区域,也就是和正文平级,要么本身就是正文所在区域的一 个子节点,很难是正文节点本身的。那么对疑似正文节点进行一次扫描,剔除那些父节点文字内容过大(包含了广告以及正文,即和正文平级) 的<a>块,也剔除那些父节点文字内容过小的<a>块。

经过这样的处理,得到的内容基本上就是我们需要的正文了。下面就是要提取标题。

在代表整个网页的document中扫描一次,寻找那些有font字体的,strong的,h1的,title的节点,提取他们的信息。然后将得到 的文字内容分词,查验分出来的词有多少是被正文包含的,包含最多的一半就是标题。但是这里要注意,有时候找到的节点本身是正文节点的子节点,那么无论怎么 分,分出来都是完全包含的,所以要剔除那些本身是正文一部分的疑似标题。这样做对大部分网页也是有效了,但是对仅有的标题就在正文节点里的那些页面,目前 为止我还没有特别好的想法。

这些日子也研究了一些别人的论文,有很多思想都非常好,也有很多人想到用马尔科夫,人工神经来训练。也许以后我会考虑用用看吧。现在这样也还可以,呵呵。

来自:http://lonegunman.spaces.live.com/blog/cns!9C763BD9399B0328!1779.entry