alancxx

------简单就是美

使用JRegex抽取网页信息

当网络爬虫将网页下载到磁盘上以后,需要对这些网页中的内容进行抽取,为索引做准备。一个网页中的数据大部分是HTML标签,索引肯定不会去索引这些标签。也就是说,这种信息是没有用处的信息,需要在抽取过程中过滤掉。另外,一个网页中一般会存在广告信息、锚文本信息,还有一些我们不感兴趣的信息,都被视为垃圾信息,如果不加考虑这些内容,抽取出来的信息不仅占用存储空间,而且在索引以后,为终端用户提供检索服务,用户检会索到很多无用的垃圾信息,势必影响用户的体验。

这里,针对论坛,采用配置模板的方式来实现信息的抽取。使用的工具可以到http://jregex.sourceforge.net上下载,JRegex是一个基于Java的正则库,可以通过在正则模板中指定待抽取信息的变量,在抽取过程中会将抽取到的信息赋给该变量,从而得到感兴趣的信息。而且,JRegex库支持多级分组匹配。

为了直观,假设,有一个论坛的一个网页的源代码形如:

<a id="anchor">标题</a>
<cont>
<author>a1</author>
<time>2009</time>
<post>p1</post>

<author>a2</author>
<time>2008</time>
<post>p2</post>

<author>a3</author>
<time>2007</time>
<post>p3</post>

<author>a4</author>
<time>2006</time>
<post>p4</post>

<author>2005</author>
<time>t5</time>
<post>p5</post>
</cont>

将该网页代码文件保存为bbsPage.txt文件,准备进行处理。

现在,我们的目标是抽取标题、作者、时间、内容这些内容,当然,标题完全可以从TITLE标签中获得,但是一般网站的一个网页,会在标题文本的后面加上一些目录或者网站名称的信息,例如一个标题为“品味北京奥运中心_奥运加油站_我行我摄_XXX社区_XXX社区是最活跃的社区之一”,一些垃圾信息占了标题的大部分,所以我们不从TITLE标签中抽取标题。

接着,针对上面的网页文件创建信息抽取的正则模板,如下所示:

(?s)<a\sid="anchor">({title}.{1,100}?)</a>\s*<cont>(.{1,10240}?)</cont> <author>({name}.{1,100}?)</author>\s*<time>({when}.{1,100}?)</time>\s*<post>({content}.{1,100}?)</post>

该模板包含两部分:

第一部分为(?s)<a\sid="anchor">({title}.{1,100}?)</a>\s*<cont>(.{1,10240}?)</cont>,包含两个组,第一个组名称为title,直接能够抽取到网页的标题文本,并存储到变量title中;而第二个组没有指定组的名称,表示在后面还存在子组,在子组中继续进行抽取。

第二部分为<author>({name}.{1,100}?)</author>\s*<time>({when}.{1,100}?)</time>\s*<post>({content}.{1,100}?)</post>,恰好是父组中未指定组名称的第二个组内中的一个循环。

上面两个模板之间使用一个空格字符隔开,保存到pattern.txt文件中。

可能,你已经观察到了,网页的标题只有一个,而对其他的信息正好能够构成一个循环组,单独从父组中分离出来继续进行抽取,结构很整齐。所以,在使用JRegex库进行编码抽取的时候,主要就是针对两个组进行的。

我基于上面思想和数据,实现了信息的抽取。

首先定义了一个键值对实体类,使用泛型,如下所示:

package org.shirdrn.test;

public class Pair<K, V> {

private K key;
private V value;

public Pair(K key, V value) {
   this.key = key;
   this.value = value;
}

public K getKey() {
   return key;
}
public void setKey(K key) {
   this.key = key;
}
public V getValue() {
   return value;
}
public void setValue(V value) {
   this.value = value;
}

}

进行信息抽取的核心类为InfomationExtraction,如下所示:

package org.shirdrn.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import jregex.Matcher;
import jregex.Pattern;

public class InfomationExtraction {

private String htmlString;
private String patternString;
private List<Pair<String, String>> dataList = new ArrayList<Pair<String, String>>();

public InfomationExtraction() {
  
}

public InfomationExtraction(String htmlFileName, String patternFileName) {
   this.htmlString = this.readString(htmlFileName);
   this.patternString = this.readString(patternFileName);
}

public Pattern[] getPatternArray() {
   Pattern[] pa = new Pattern[2];
   String[] psa = this.patternString.split(" ");
   for(int i=0; i<psa.length; i++) {
    Pattern p = new Pattern(psa[i]);
    pa[i] = p;
   }
   return pa;
}

public void extract(Integer sgIndex) {   // 指定父组中第sgIndex个组需要在子组中继续进行抽取
   Pattern[] pa = this.getPatternArray();
   Pattern pBase = pa[0]; 
   Matcher mBase = pBase.matcher(this.htmlString);
   if(mBase.find()) {
    for(int i=0; i<mBase.groupCount(); i++) {
     String gn = pBase.groupName(i);
     if(gn != null) {
      String gv = mBase.group(i);
      this.dataList.add(new Pair<String, String>(gn, gv));
     }
    }
    String subText = mBase.group(sgIndex);
    if(subText != null) {
     this.dataList.addAll(this.getSubGroupDataList(pa, subText)); // 调用使用子组正则模板进行抽取的方法
    }
   }
}

public List<Pair<String, String>> getSubGroupDataList(Pattern[] pa, String subText) { // 使用子组正则模板进行抽取
   List<Pair<String, String>> list = new ArrayList<Pair<String, String>>();
   for(int i=1; i<pa.length; i++) {
    Pattern subp =pa[i];
    Matcher subm = subp.matcher(subText);
    while(subm.find()) {
     for(int k=0; k<subm.groupCount(); k++) {
      String gn = subp.groupName(k);
      if(gn != null) {
       String gv = subm.group(k);
       list.add(new Pair<String, String>(gn, gv));
      }
     }
    }
   }
   return list;
}

public String readString(String fileName) {
   InputStream in = this.getClass().getResourceAsStream("/" + fileName);
   BufferedReader reader = new BufferedReader(new InputStreamReader(in));
   StringBuffer sb = new StringBuffer();
   String line = null;
   try {
    while((line=reader.readLine()) != null) {
     sb.append(line);
    }
   } catch (IOException e) {
    e.printStackTrace();
   }
   return sb.toString();
}

public List<Pair<String, String>> getDataList() {
   return this.dataList;
}

public static void main(String[] args) {  
   InfomationExtraction ie = new InfomationExtraction("bbsPage.txt","pattern.txt");
   ie.extract(2);
   for(Pair<String, String> p : ie.getDataList()) {
    System.out.println("[" + p.getKey() + " " + p.getValue() + "]");
   }
}

}

测试一下,如下所示:

[title 标题]
[name a1]
[when 2009]
[content p1]
[name a2]
[when 2008]
[content p2]
[name a3]
[when 2007]
[content p3]
[name a4]
[when 2006]
[content p4]
[name 2005]
[when t5]
[content p5]

至于如何组织抽取到的信息,比如你可能使用Lucene的索引,需要构造Field和Document,那么你就要设计一个实体能够包含一个Document的所有的Field,比如一个Document包括:URL、标题、作者、发表时间、发表内容这五个项,非常容易就能做到。

使用JRegex库,可以非常灵活地配置模板,尤其是对多个组的设计,这要根据你的需要来考虑。

posted on 2011-04-16 20:47 蜂鸟 阅读(383) 评论(0)  编辑  收藏 所属分类: openSource


只有注册用户登录后才能发表评论。


网站导航:
 

公告

 掌握了XML就掌握了未来!

导航

<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

统计

常用链接

留言簿

随笔档案(1)

文章分类(17)

文章档案(17)

搜索

最新评论