本次实现分为两个部分,第一个部分是利用Lucene构建一个全文的搜索引擎,另外一部分则是利用Nutch实现同样的功能。由于Lucene并不是一个可以直接运行的程序,且不具备爬虫和文档处理的功能,因此在这一部分利用到了Heritrix和HTMLParser这两个工具分别实现爬虫与HTML文档解析的功能。而使用Nutch的时候只需要一些简单的配置和安装就可以直接运行。最后还对这两者进行了一个简单的对比,说明其各自的特点和适应的范围。
由于Lucene不具备爬虫的功能,因此这里使用到了Heritrix这样的一个工具。我们利用Heritrix爬取一个指定网站(如:南京大学信息管理学院官方网站)的内容,为了简单起见这里只爬去网页格式的文档,而“.doc/.xls/.ppt/.pdf”等格式的文件则直接忽略,因此这里还需要对爬虫进行一个简单的设计。
这里使用的Heritrix版本为Heritrix-1.14.4,爬虫的配置安装过程如下:
(1)源代码的导入
由于这里需要一些自定义的功能,因此使用的是Heritrix的源代码版本。为方便开发,首先我们需要将其导入Myeclipse。在Myeclipse下建立一个工程名为“MyHeritrix”。
导入类库
这一步需要将 Heritrix-1.14.4-src 下的 lib 文件夹拷贝到 MyHeritrix 项目根目录;方法如下:在 MyHeritrix 工程上右键单击选择“Build Path Configure Build Path …”,然后选择 Library 选项卡,单击“Add JARs …”,在弹出的“JAR Selection”对话框中选择 MyHeritrix 工程 lib 文件夹下所有的 jar 文件,然后点击 OK 按钮。
拷贝源代码
将 Heritrix-1.14.4-src\src\java 下的 com、org 和 st 三个文件夹拷贝进 MyHeritrix 工程的 src 下。这三个文件夹包含了运行 Heritrix 所必须的核心源代码;将 Heritrix-1.14.4-src\src\resources\org\archive\util 下的文件 tlds-alpha-by-domain.txt 拷贝到 MyHeritrix\src\org\archive\util 中。该文件是一个顶级域名列表,在 Heritrix 启动时会被读取;将 Heritrix-1.14.4-src\src 下 conf 文件夹拷贝至 Heritrix 工程根目录。它包含了 Heritrix 运行所需的配置文件;将 Heritrix-1.14.4-src\src 中的 webapps 文件夹拷贝至 Heritrix 工程根目录。该文件夹是用来提供 servlet 引擎的,包含了 Heritrix 的 web UI 文件。
(2)源代码的修改
由于我们需要一个功能:不爬取网页中的.doc/.xls/.ppt/.pdf等格式的文件,而是自定义需要爬取的网页文件,因此我们需要自行修改程序。这里实现的方法为扩展 FrontierScheduler 来抓取特定网站内容。
FrontierScheduler 是org.archive.crawler.postprocessor 包中的一个类,它的作用是将在 Extractor 中所分析得出的链接加入到 Frontier 中,以待继续处理。在该类的 innerProcess(CrawlURI) 函数中,首先检查当前链接队列中是否有一些属于高优先级的链接。如果有,则立刻转走进行处理;如果没有,则对所有的链接进行遍历,然后调用 Frontier 中的 schedule() 方法加入队列进行处理。这里我们自行设计一个类FrontierSchedulerForDaven继承FrontierScheduler类来修改程序的功能:
1
2
3
4
5
6
7
8
9
|
public
class
FrontierSchedulerForDaven
extends
FrontierScheduler
然后在该类中重写schedule函数:
protected
void
schedule(CandidateURI caURI)
…
String uri=caURI.toString();
if
(uri.indexOf(
"dns:"
)!=-
1
){getController().getFrontier().schedule(caURI);}
else
if
(uri.indexOf(“nju.edu.cn
")!=-1)||uri.indexOf("
.html
")!=-1||uri.indexOf("
.php")!=-
1
||uri.indexOf(
".jsp"
)!=-
1
||uri.indexOf(
".asp"
)!=-
1
||uri.indexOf(
".shtml"
)!=-
1
))
…
|
通过这样的一个布尔表达式实现就可以实现只爬取后缀名为html、php、jsp、asp、shtml格式的文件,其他类型的文件一律忽略。
程序修改完成之后,在修改conf/modules/Processor.options文件,在该文件中添加如下一行:
(3)Heritrix启动与爬虫创建
在启动之前,我们还需要对Heritrix进行一些简单的配置,找到conf/Heritrix.properties文件,在文件当中将Heritrix.cmdline.admin(用户名、密码)和Heritrix.cmdline.port(端口)两个参数修改为如下形式:
上面的配置代表,Heritrix启动时的端口为9999,用户名密码分别为admin和admin。
完成上面步骤之后,找到org.archive.crawler.Heritrix类,该类是Heritrix的主类入口,直接运行该类就可以启动Heritrix。启动成功之后,控制台会输出如下信息:
从上图可以看出,Heritrix内置了一个Jetty服务器,在浏览其启动的地址为:127.0.0.1:9999。在地址栏直接输入该地址就可以进入Heritrix的登录界面。账户名和密码为上面所设置的admin。
接下来就可以直接进入爬虫的创建过程,过程如下:在Job选项卡的create new job中选择with default即进入爬虫的创建界面:
上面所表单的第一行为爬虫的名称,第二行为对爬虫的描述,下面的文本框中输入爬虫的入口地址,例如:http://im.nju.edu.cn/。
(4)爬虫配置的修改与爬虫运行
在上面一步的基础上单击”modules”按钮,在相应的页面为此次任务设置各个处理模块,一共有七项可配置的内容:
1)Select Crawl Scope:Crawl Scope 用于配置当前应该在什么范围内抓取网页链接。例如选择 BroadScope 则表示当前的抓取范围不受限制,选择 HostScope 则表示抓取的范围在当前的 Host 范围内。在这里我们选择 BroadScope。
2)Select URI Frontier:Frontier 是一个 URL 的处理器,它决定下一个被处理的 URL是什么。同时,它还会将经由处理器链解析出来的 URL 加入到等待处理的队列中去。
3)Select Pre Processors:这个队列的处理器是用来对抓取时的一些先决条件进行判断。比如判断 robot.txt 信息等,它是整个处理器链的入口。
4)Select Fetchers:这个参数用于解析网络传输协议,比如解析 DNS、HTTP 或 FTP 等。
5)Select Extractors:主要是用于解析当前服务器返回的内容,取出页面中的 URL,等待下次继续抓取。
6)Select Writers:它主要用于设定将所抓取到的信息以何种形式写入磁盘。一种是采用压缩的方式(Arc),还有一种是镜像方式(Mirror)。这里我们选择简单直观的镜像方式。
7)Select Post Processors:这个参数主要用于抓取解析过程结束后的扫尾工作,比如将 Extrator 解析出来的 URL 有条件地加入到待处理的队列中去,这里我们选择之前自己定义的类:org.archive.crawler.postprocessor.FrontierSchedulerForDaven
上面的7个参数,除了特别指出来的除外,其他的几个采用默认形式即可。
设置完“Modules”后,点击“Settings”按钮,这里只需要设置 user-agent 和 from,其中:“@VERSION@”字符串需要被替换成 Heritrix 的版本信息。“PROJECT_URL_HERE”可以被替换成任何一个完整的 URL 地址。“from”属性中不需要设置真实的 E-mail 地址,只要是格式正确的邮件地址就可以了。
当爬虫配置完成之后,就可以运行爬虫了,选择console选项卡,点击start就可以启动爬虫,爬虫运行过程中的截图如下:
爬虫运行完成之后会在MyHeritrix/jobs文件夹下面生成一个Mirror文件夹,里面存放了爬去的的文件内容。
在实际的应用当中,网站内会有大量除了网页文件之外的文件,例如ms office文件、pdf文件等等。而这些不同的文件需要不同的工具来进行处理,下面我总结了一些常见格式文档与其对应的解析工具:
文档类型 HTML Word Excel PPT RTF PDF
文档解析器 HTMLParser POI POI POI POI PDFBox
下面对上面所列举的工具进行一个简单的介绍:
(1)HTMLParser
HTMLParser是一个专门用来解析HTML格式文档的开源Java库,毫不夸张地说,HTMLParser就是目前最好的HTML解析和分析的工具,它的特点是快速健壮。其主要的功能有:
文本信息抽取,例如对HTML进行有效信息搜索
链接提取,用于自动给页面的链接文本加上链接的标签
资源提取,例如对一些图片、声音的资源的处理
链接检查,用于检查HTML中的链接是否有效
页面内容的监控
链接重写,用于修改页面中的所有超链接
网页内容拷贝,用于将网页内容保存到本地
内容检验,可以用来过滤网页上一些令人不愉快的字词
HTML信息清洗,把本来乱七八糟的HTML信息格式化
转成XML格式数据
在项目实施过程中,我利用HTMLParser将一个HTML文档解析成三行,第一行为文档所对应的URL地址,第二行为文档的标题,第三行为文档的摘要。
第一行:URL
this.page.setURL(getURL(this.filename));
第二行:title
this.page.setTITLE(visitor.getTitle());
第三行:summary
this.page.setCONTEXT(combineNodeText(visitor.getBody().toNodeArray()));
然后将所有解析出来的文档存入另外一个文件夹。该文件夹中的文件可以直接用于索引的构建。
(2)POI
POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式文档读和写的功能,它主要有五个模块,其名称和功能如下:
模块 功能
HSSF 提供读写Microsoft Excel格式档案的功能
XSSF 提供读写Microsoft Excel OOXML格式档案的功能
HWPF 提供读写Microsoft Word格式档案的功能
HSLF 提供读写Microsoft PowerPoint格式档案的功能
HDGF 提供读写Microsoft Visio格式档案的功能
由于这里不做MS OFFICE文档的解析,因此不再详细解释其用法。
(3)PDFBox
PDFBox同样是Apache下的一个开源项目,专门用于PDF文档的解析。它允许在项目中利用程序设计语言创建PDF文档、操作多个PDF文档以及从PDF文档中解析出内容。
其主要的功能如下:从PDF文档中解析出内容;合并和分割PDF文档;PDF问的打印;将PDF文档转换为照片格式;PDF文档中表格的填充;PDF/A的验证;PDF文档的创建;与Lucene的整合开发。
索引的构建直接利用Lucene即可完成,索引中利用的分词工具为MMAnalyzer极易中文分词组件,为了得到更好的效果我们可以使用中科院的分词ICTCLAS,但是需要我们自行编写分析器Analyzer,这里为了简单起见,利用了现成的中文分词工具MMAnalyzer。
创建索引的时候,首先需要创建IndexWriter,然后再创建的document,最后创建field来存放不同部分的数据。在本实验中总共创建了4个域(fields),第一个域名称为”id”,用于标识一个文件;第二个为”url”,用于存放该文件所对应的URL;第三个域为”title”,用于存放该文件的标题,这个标题也被用作在搜索结果中的标题;第四个域为”context”,用于存放文本的内容,即搜索结果中的摘要部分。
具体的程序代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
IndexWriter writer=
new
IndexWriter(path,
new
MMAnalyzer());
//MMAnalyzer极易中文分词组件
reader=
new
BufferedReader(
new
FileReader(files[i]));
fields[
0
] =
new
Field(
"id"
,String.valueOf(i),Field.Store.YES,Field.Index.NO);
fields[
1
]=
new
Field(
"url"
,reader.readLine(),Field.Store.YES,Field.Index.NO);
fields[
2
]=
new
Field(
"title"
,reader.readLine(),Field.Store.YES,Field.Index.TOKENIZED);
fields[
3
]=
new
Field(
"context"
,getBodyFile(files[i].getAbsolutePath(),reader),Field.Store.YES,Field.Index.TOKENIZED);
//创建Document
Document doc=
new
Document();
//将五个field添加到doc中
for
(
int
j=
0
;j<fields.length;j++)
{doc.add(fields[j]);}
//将Document添加到IndexWriter中
writer.addDocument(doc);
//优化和关闭
writer.optimize();
writer.close();
reader.close()
|
注意在创建完成field之后,通过document的add函数将field添加到document中,完成之后还要利用Indexwriter的addDocument函数将document添加到IndexWriter中。
最后一步不要忘了利用optimize和close函数来优化索引和关闭资源。
索引建立完成之后会在指定索引文件夹中生成四个文件:
_o.cfs/_w.cfs/segments.gen/segements_1f。
接下来就可以对这些索引文件进行检索了。
Lucene查询实现的方法主要有两种,第一种是Query,另外一种是Queryparser,两者有明显的差别。QueryParer类用来转换用户的检索关键词,其作用就是把用户输入的非格式化检索词转换为后台检索可以理解的Query对象。用Queryparser时需要一个参数Analyzer,如果要进行分词处理的话就可能需要这个参数。
Query类是一个抽象类,需要使用Query的某个子类的构造函数来实例化并以此来表示与用户检索有关的内容。具体使用的时候,用户可以通过Query的一系列子类,如TermQuery/BooleanQuery/RangeQuery/PrefixQuery/PhraseQuery/MultiPhraseQuery/FuzzyQuery/WildcardQuery/SpanQuery/Regexuery的构造函数来直接生成对应的Query对象。这些检索方式各有其特点,通过命名大致可以猜测出它的功能,这里不再做详细的介绍。
为了方便起见,实验中用的检索方式为TermQuery,这个检索方式只支持词的检索,因此需要在检索前将问句进行分词处理,而这里使用的分词工具为ICTCLAS。各个检索词之间采用的是布尔运算的“或”运算。
1
2
|
TermQuery[] term=
new
TermQuery[length];
query.add(term[i],BooleanClause.Occur.SHOULD);
//通过后面参数确定布尔检索类型
|
在Lucene中布尔检索中各运算符与其所对应的参数如下表:
运算类型 所对应的参数
AND(与) BooleanClause.Occur.MUST
NOT(非) BooleanClause.Occur.MUST_NOT
OR(或) BooleanClause.Occur.SHOULD
举例说明布尔或运算的运算规则,如果输入查询词“信息”和“管理”,查询结果为包含“信息”与“管理”这两个词的文档与只包含“信息”这个检索词的文档以及只包含“管理”这个词的文档。当然在实际的操作中可以采用完全不同的查询方式。
下面为ICTCLAS的分词过程:
1
2
3
4
5
6
7
8
9
10
11
|
public
String[] getParserResult(ICTCLAS instance,String sentence){
String result;
result=instance.paragraphProcess(sentence);
String[] res;
res=result.split(
"\\s+"
);
for
(
int
i=
0
;i<res.length;i++){
int
position=res[i].indexOf(
"/"
);
System.out.println(position);
res[i]=res[i].substring(
0
, position);
System.out.println(res[i]); }
return
res;}
|
这里对上面的程序进行一个简单的介绍,其中最重要的一行为:
result=instance.paragraphProcess(sentence);
其中sentence代表输入的问句,result就是经过分词得到的结果,不过输出结果形式有一个特别之处,例如:句子“信息检索”其result的结果形式为“信息/n 检索/v”,而在实际的过程中我们并不需要后面的词性以及”/”,因此后面的程序代码都是为了将分词结果变成一个字符数组,去掉空格以及后面的”/”和词性。
分词完成之后就可以把分词结果放入term中进行检索了。
界面部分需要两个文件一个index.html文件,一个是search.jsp文件,其中index.html主要作用是查询主界面的实现。而search.jsp则用户处理问句、问句检索以及检索结果展示。
下面是index.html文件的表单部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<form action=
"search.jsp"
method=
"GET"
>
<input type=
"text"
name=
"keys"
size=
50
height=100px
class
=kw maxlength=
"100"
>
<input type=
"submit"
value=
"search"
class
=sb>
<font size=
"2"
face=
"宋体"
>
可以看到其表单的内容其实都交给了search.jsp来处理。而search.jsp的部分代码如下:
<jsp:directive.page
import
=
"com.davenzhang.util.*"
/>
<jsp:directive.page
import
=
"com.xjt.nlp.word.ICTCLAStest"
/>
……
<form action=
"search.jsp"
method=
"get"
>
<input type=
"text"
name=
"keys"
size=
36
maxlength=
"100"
>
<input type=
"submit"
value=
"search"
>
<a href=
"index.html"
>Home</a>
……
out.println(
"<font size=4px color=\"blue\">"
+title+
"</font><br>"
);
out.print(
"摘要:"
+summary);
|
前面的import部分,是导入一些java包,方便在jsp代码中直接使用其功能,com.davenzhang.util.*主要包含了Lucene的查询函数,而com.xjt.nlp.word.ICTCLAStest包则包含分词所用到的函数。后面的out.println()则用于查询结果的展示
与Lucene不同,Nutch是一个可以直接运行的程序,基本上不需要自己编写任何代码就能实现简单的全文搜索引擎的功能。下面介绍Nutch的安装和配置过程。
(1) 安装JDK/Tomcat/Cygwin,这些软件的安装都有详细的教程,这里不再赘述。
(2) 修改环境变量
变量名 NUTCH_JAVA_HOME
变量值 %JAVA_HOME%
运行cygwin,输入命令cd /cygdrive/d/ Nutch-1.2,再输入bin/Nutch,如果出现Nutch的命令行介绍就代表Nutch安装成功。
(3) 爬虫设置
与Lucene不同,Nutch自带了爬虫的功能,但是需要进行一些配置,下面对这个过程做一个详细的介绍。
设置需要抓取的网站主域名
在Nutch-1.2的安装目录下建立一个名为urls的文件夹,并在文件夹下建立url.txt文件,在文件中写入口地址http://im.nju.edu.cn/
设置网站过滤规则
编辑conf/crawl-urlfilter.txt文件,修改MY.DOMAIN.NAME部分。将
# accept hosts in MY.DOMAIN.NAME
+^http://([a-z0-9]*\.)*MY.DOMAIN.NAME/
改为:
# accept hosts in MY.DOMAIN.NAME
+^http://([a-z0-9]*\.)*nju.edu.cn/
修改conf/Nutch-site.xml代理信息
在和之间添加如下文件:
http.agent.name
http://im.nju.edu.cn/value> http.agent.url
http://im.nju.edu.cn/value> http.robots.agents
http://im.nju.edu.cn/ 设置代理名
编辑Nutch-1.2\conf\Nutch-default.xml文件,找http.agent.name,然后随便设置Value值。例如:
http.agent.name
test
执行Nutch抓取url数据
在Cygwin命令行窗口中输入:cd /cygdrive/d/Nutch-1.2
再输入bin/Nutch crawl urls –dir crawl –depth 3 –threads 4 –topN 30 >& crawl.log
命令说明:
crawl:是Nutch检索数据命令,后面跟上要检索的URL文件。urls就是第一部分部份创建的文件;
-di:是检索后的结果存放目录参数,后面跟上结果存放地址。如果我们存放到Nutch目录下的crawl目录,注意此目录当前是不存在的。检索完后Nutch会创建出来。-threads 抓取时的线程数参数
-depth:抓取时的深度参数
-topN:抓取时每页的最大抓取链接
最后把执行信息写入crawl.log日志文件中,方便查找错误。
爬虫执行完成之后会在Nutch的目录下生成一个crawl文件夹,里面包含五个子文件夹,分别为:crawldb/index/indexes/linkdb/segments。
部署到Tomcat
将Nutch-1.2目录下的Nutch-1.2.war复制到Tomcat目录webapp下面
修改配置文件WEB-INF\classes\Nutch-site.xml为如下形式,其中的value值就是刚才爬虫爬取得信息。
然后重启tomcat就可以完成Nutch的安装于配置的全部过程。
在浏览器中输入http://localhost:8080/Nutch-1.2/zh/,即可进入Nutch主页面,输入检索词“南京大学”所得到的检索结果如下图所示:
下面是Nucth的一些特性:
默认把词语分成单个词来查询,例如,“计算机”会以“计”、“算”、“机”来做查询。
Nutch中所有的parsing(分析)、indexing(索引)和searching(查询)都是通过不同的插件来实现的。Nutch把自己可以提供的Plugin接口用一个xml文档来描述。
自定义插件时,只需要自行设计一些符合自身功能需求的类(继承Nutch提供的父类或者实现一些接口),然后修改配置文件就行了。
Lucene其实是一个提供全文文本搜索的函数库,它不是一个应用软件。它提供很多API函数让你可以运用到各种实际应用程序中。现在,它已经成为Apache的一个项目并被广泛应用着。Lucene不管是爬虫的设计,还是文档的预处理,还是索引的建立,还是系统的分词等等,都需要用户编写代码,实现功能。Lucene给用户提供了非常强大的功能,但要享受这些功能的则必须具备编写复杂代码的能力。无论是在爬虫的选择、搜索的优化、排序算法的设计等等用户都需要自行决定。
Nutch是一个建立在Lucene核心之上的Web搜索的实现,它是一个真正的应用程序。也就是说,你可以直接下载下来拿过来用。它在Lucene的基础上加了网络爬虫和一些和Web相关的东西。其目的就是想从一个简单的站内索引和搜索推广到全球网络的搜索上。Nutch主要分为两个部分:爬虫crawler和查询searcher。Crawler主要用于从网络上抓取网页并为这些网页建立索引。Searcher主要利用这些索引检索用户的查找关键词来产生查找结果。两者之间的接口是索引,所以除去索引部分,两者之间的耦合度很低。Crawler和Searcher两部分尽量分开的目的主要是为了使两部分可以分布式配置在硬件平台上,例如将Crawler和Searcher分别放在两个主机上,这样可以提升性能。
另外,还有一个和Lucene类似的工具Solr。Solr是Lucene的服务器化,内嵌了jetty,用户可以直接post数据给Solr,然后由Solr进行索引。Solr不包含下载系统,用户需要负责下载,转成Solr所需要的格式。Nutch和Solr都是基于Lucene的,二者都是可直接运行的应用程序。 一般可以使用Nutch做crawler,而使用Solr做indexer和查询接口。
参考文献:
[1]. Otis Gospodnetic;Erik Hatcher(著). lucene in action(中文版).谭鸿;黎俊鸿等译.北京:电子工业出版社, 2007
[2]. 罗刚.解密搜索引擎技术实战:Lucene&Java精华版. 北京:电子工业出版社,2011
[3]. 袁津生, 李群主编.搜索引擎基础教程.北京:清华大学出版社, 2010
[4]. 高凯,郭立炜,许云峰编著.网络信息检索技术及搜索引擎系统开发.北京:科学出版社, 2010
[5]. http://lucene.apache.org/
[6]. http://nutch.apache.org/
[7]. http://crawler.archive.org/index.html
[8]. http://sourceforge.net/p/htmlparser/wiki/Home/
[9]. http://pdfbox.apache.org/
[10]. poi