dySE:一个 Java 搜索引擎的实现,第 3 部分 查询服务

来源:互联网 发布:航天证券交易软件 编辑:程序博客网 时间:2024/05/01 17:56

dySE:一个 Java 搜索引擎的实现,第三部分:查询服务

在之前的两个部分中,您了解到 spider 的编写和对原始网页库的预处理:通过 spider 我们得到一个原始网页库,而通过预处理部分建立网页的索引,并用分词器对网页进行分词进而创建倒排索引。本部分内容将要介绍查询服务的编写,查询服务通过接收用户的输入,调用后台程序对输入进行分词以及查询操作之后,将返回的查询结果在网页上显示。本文分三个步骤介绍查询服务的实现过程:首先使程序在控制台下能够返回查询结果,为查询结果的显示做准备;然后,搭建 Web 服务器进行网络编程使得程序能够方便的输入并进行结果返回;最后,介绍网页的排名策略和实现。下面就让我们逐步介绍查询服务的设计和实现。

查询服务的整体结构

查询服务的整体结构如下:

图 1. 查询服务整体结构
图 1. 查询服务整体结构

在前面两部分的叙述中,我们有了放在文件中的原始网页库、放在数据库中的网页索引 ( 指示某个网页所在原始网页库的位置 )、倒排索引,以及一些小工具:分词器。在这些部件的基础上,我们开始搭建我们搜索引擎的界面并且实现信息的输入和输出。

以下的章节安排如下:首先我们完善后台服务,使得程序能够在控制台输入查询的情况下,在控制台中返回需要的结果信息,这些结果将在后续的部分中返回给网页进行显示;其次,我们搭建 Web 服务器,进行网页编程,使得查询服务与后台服务程序能够交互;最后我们介绍网页结果返回时的一些优化,比如网页排名的实现。

简单查询

在第二部分预处理之后,我们现有的待用数据如下:原始网页库,网页索引,倒排索引,分词器。为了方便您对于后文的理解,我们再次说明这些资源的用途:原始网页库记录了爬虫获取的各个网页信息,按照一定的格式保留在本地;然而这些网页信息不便于随机的进行访问,所以我们通过网页索引记录某个网页在原始网页库中的位置,以方便查询;倒排索引是一个关键字和包含这个关键字的网页 URL 集合的映射,通过倒排索引可以方便的得到哪些网页包含确定的关键词;分词器的作用在于可以对用户输入的文字进行分词,因为用户可能会输入多个词组所以分词器在查询服务中也起着必不可少的作用。

简单的查询服务过程如下:对于用户的输入,首先进行分词,对于每个词组,搜索倒排索引获取包含该词组的网页 URL 信息, 找到各个分词对应的 URL 集合中共同的 URL,根据结果 URL 集合查询网页索引获得 URL 对应的网页信息,整合网页信息之后进行返回。

结果集合的生成

在上述的过程中,分词和倒排索引的具体结构已经在第二部分预处理模块中提及,这里就不再赘述。分词结果集 (keywords) 在倒排索引中搜索结果的算法如下:

清单 1. 结果检索算法
1
2
3
4
5
6
7
8
结果集合,URLs 初始化为空
 For each keyword in keywords do
 begin
获取 keyword 对应的倒排索引中的数据项:urls;
合并 URLs 与 urls,保留两者的共同 url,如果 URLs 为空,则将 urls 赋值给 URLs;
 end;
 if(URLs 为空 ) return null;
 else return URLs;

由于可能会产生信息查询不到的情况,所以算法中追加了检索结果为空的判断,保证程序的健壮性。合并结果集合 URLs 和 keyword 对应的网页集合 urls 中相同的元素有许多种方法,简单的可以采用建立一个临时的集合,存储两者的公共 url,等到该次执行结束,将该临时集合中的值赋值给 URLs 即可。

如此我们得到了作为简单结果的 URL 集合,下一步我们要通过这个集合生成详细结果并且进行返回。我们可以使用其他的搜索引擎比如 google、百度等来了解一个网页结果具体需要包含哪些信息,下图是在 google 中搜索“中国教育”关键字的返回结果:

Figure xxx. Requires a heading

从图中可以看出,在第一行显示的是该网页的标题,并且是一个超级链接,第二行是正文内容的一个摘要,该摘要最好能够包含搜索的关键词,从而用户可以判断该网页与用户查询的相关度。最后一行显示的是该网页的 URL 以及网页快照的一个功能。快照功能是指收录的网页的纯文本备份,在网速很慢或者原始网页无法打开的情况下,可以使用快照功能查看该网页的文本内容,快照功能的实现我们将在本文的末尾提及。下面我们主要完成标题的提取、正文摘要的提取两个部分。

在第二部分中,我们介绍了如何通过原始网页库的文件名和文件内偏移进行某个 URL 对应的页面数据查询,所以这部分我们只是再简单的提及,通过数据库的查询,我们可以得到某个 URL 所对应的文件的所在位置,通过 BufferedReader 类中提供的 skip 函数可以完成偏移量的跳转从而直接开始读取所需要的页面信息。标题的获取比较直观,由于 html 中 <title></title> 标签对中的内容即对应该网页的标题,所以提取该标签即可,对于页面数据,我们可以用正则表达式来比配这个标签对,该匹配的正则表达式如下:title[^>]*?>[\\s\\S]*?</title>。正则表达式具体的匹配过程在第二部分中有示例,这里不再赘述。

正文摘要的生成主要有两种方法,一种是在 html 标签中提取 description 信息,网页的摘要信息会放在形如:<META content="关注搜索引擎…" name=description> 的标签中,仍旧通过正则表达式,我们可以匹配得到网页的摘要信息,这种方法比较常用,同时也很方便。第二种方法在网页正文的基础上生成,由于某些网页中可能不包含 description 标签,这样就需要在正文中抽取网页摘要,这种方法也是第一种方法的一个备用方法。网页正文可以通过去掉网页的 html 标签来获得。正文摘要的目标是使摘要能尽量多的在一段内容中显示更多与查询关键字相关的信息,为此,我们可以采用如下策略来进行摘要的生成:

  • 首先,用户查询的关键字在摘要中最好能处于相邻位置。由于 URL 结果是在分词的基础上搜索生成的,所以 URL 对应页面包含的关键字可能也是分散的,例如,我们搜索“搜索引擎”关键字,如果一个页面上有如下两段文字:(1) 本文介绍搜索引擎的具体实现步骤…;(2) 警察通过搜索发现,汽车的引擎不翼而飞…。很显然,文本 (1) 更加适合做该关键字的摘要。
  • 在提取的限定长度的摘要中,关键词的出现频率应该要比较高;
  • 如果第一点不能达到,那么在摘要中,关键词之间的间隔应该要尽可能的小。

根据这些策略,我们就可以提取摘要的文字信息。

除了上述的两项信息,百度在搜索结果中还有网页的日期这一项,我们可以参照这点在结果中显示日期信息。由于我们在将网页格式化存储时包含了摘录该网页的时间,我们可以直接获取该日期显示在结果中。

为了更好的封装返回的结果,我们创建 Result 类来存储单个网页的返回信息,这其中主要包括了标题 (title)、正文动态摘要、日期、URL 四种数据。而查询的最终结果是返回一个 Result 的 List。

由于这些数据都是字符串类型,所以可以很容易的在控制台上进行显示并进行测试,我们可以在控制台下测试如下:

清单 2. 控制台下检索结果检测
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
Scanner cin=new Scanner(System.in);
String keyword = cin.next();  //read the keyword from console
Response response = new Response();
ArrayList<Result> results = response.getResponse(keyword);
System.out.println("返回结果如下:");
for(Result result : results){
System.out.println(result.getTitle());
System.out.println(result.getContent());
System.out.println(result.getUrl() + "  " + result.getDate());
}
}

在控制台输入“中国教育”返回的结果大致如下:

图 2. 查询“中国教育”返回的结果
图 2. 查询“中国教育”返回的结果

搭建 Web 服务器提供查询服务

一般的搜索引擎都是通过 Web 程序提供应用接口,从而提供服务,在本节我们介绍 Web 服务器的搭建提供查询服务。我们按照 Web 服务器的搭建、与后台查询模块的连接两个部分来进行叙述。

Web 服务器搭建

由于我们的后台 ( 即之前所述的倒排索引建立查询和结果返回等部分 ) 是用 Java 编写,所以很自然的,我们想到用 JSP(Java Server Page) 来提供查询服务,在这小节中,我们重点介绍如何搭建服务器提供 JSP 服务。

我们使用 Tomcat 作为 Web 应用服务器,Tomcat 是一个小型的开源轻量级应用服务器,是开发和调试 JSP 程序的很好选择。我们先来介绍 Tomcat 服务器的搭建过程:

  • 下载 Tomcat6.0,参考地址:http://tomcat.apache.org/;
  • Tomcat 是免安装的,所以解压到本地进行环境变量的配置即可使用;
  • 双击在 tomcat 的 bin 目录下的 startup.bat 文件,启动 tomcat 服务器;

Web 页面编写

有了服务器的支持,我们即可进行 Web 页面的编写,以提供查询服务的入口和结果返回页面。查看大部分搜索引擎的界面,无论是主界面还是搜索结果显示界面,其显示的内容都较为简单,所以 JSP 的页面开发环境您可以根据您的习惯和喜好自由选择,本文主要在 MyEclipse 中进行页面编写。

清单 3. 查询服务入口 index.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"
+request.getServerName()+":"
+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head>
   <basehref="<%=basePath%>">
       <title>dySE</title>
         <style>
#search{
width:78px;
height:28px;
font:14px "宋体"
}
#textArea{
width:300px;
height:30px;
font:14px "宋体"
}
</style>
 </head>
  
 <body>
<palign="center"><imgsrc="dySE-logo.jpg"/></p>
<formaction="search.jsp"name="search"method="get"
enctype="application/x-www-form-urlencoded">
<tableborder="0"height="30px"width="450px"align="center">
<tr>
<tdwidth ="66%"><inputname="keyword"type="text"maxlength="100"
id="textArea"></td>
<tdheight="29"align="center"><inputtype="submit"value="搜索一下"
id= "search"></td>
</tr>
</table>
</form>
 </body>
</html>

我们在 MyEclipse 中新建一个 WEB PROJECT,并新建一个 JSP 页面,命名为 index.jsp,MyEclipse 会自动生成基本的页面代码,我们编写的代码主要是两个部分,一部分是 <style></style> 标签对中的 CSS 样式,这部分指定了页面中关键字输入文本框和按钮的样式,这里就此略过。另一部分是 <body></body> 标签对中的代码,第一行居中显示 dySE 的 logo 图标,然后空行,之后就是一个表单,其中包括了一个含有文本输入框和按钮的表格—— <table> </table> 标签对中,在 form 标签中,设定了按下按钮的动作——转到 search.jsp 页面,其中的 enctype="application/x-www-form-urlencoded"指定了编码格式,如果没有指定,在搜索中文的时候会导致乱码。该代码显示结果如下:

图 3. 搜索界面
图 3. 搜索界面

接下来我们编写搜索结果显示页面。

清单 4. 查询结果显示 search.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
<jsp:directive.pageimport="core.query.Response"/>
<jsp:directive.pageimport="core.util.Result"/>
 
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":
"+request.getServerPort()+path+"/";
%>
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head>
   <basehref="<%=basePath%>">
   <title>Search Result</title>
   <style>
#search{
width:78px;
height:28px;
font:14px "宋体"
}
#textArea{
width:300px;
height:30px;
font:14px "宋体"
}
</style>
 </head>
  
 <body>
   <formaction="search.jsp"name="search"method="get">
<tableborder="0"height="30px"width="450px"align="center">
<tr>
<td><imgsrc="dySE-logo.jpg"/></td>
<tdwidth ="66%"><inputname="keyword"type="text"maxlength="100"id="textArea"></td>
<tdheight="29"align="center"><inputtype="submit"value="搜索一下"id = "search"></td>
</tr>
</table>
     </form>
<% 
String keyword = new String(request.getParameter("keyword") .getBytes("
ISO-8859-1"),"GB2312");
Response resp = new Response();
ArrayList<Result> results = resp.getResponse(keyword);
for(Result result : results)
{
%>
<h2><ahref=<%=result.getUrl()%>><%=result.getTitle()%></a></h2>
<p><%=result.getContent()%><p>
<p><%=result.getUrl()%> &nbsp;&nbsp;&nbsp; <%=result.getDate()%><p>
<% 
}
%> 
</body>
</html>

Search.jsp 在开头引入了 response 和 result 两个类,其后的代码与 index.jsp 有很大部分的相似之处,这里不再赘述,主要说明一下 <form></form> 标签对之后查询服务的调用以及返回的结果的显示方式。第一行先获取了用户在文本框内输入的查询关键字,为了防止编码问题,我们在获取结果时候加入编码格式。之后通过我们建立的 Response 类来进行结果的获得,通过传入搜索的关键字,Response 类在 getResponse 操作中对倒排索引进行查询,将查询的结果放入到结果列表中(算法可参见简单查询部分),操作返回的结果是一个 Result 类型的 List,遍历这个 List 并且按照一定的格式显示这些数据即可得到所需要的输出,输出的内容将按照一定的 html 格式进行设置。第一行建立一个超链接,链接的显示文字是 Result 类型中页面的 title 属性,链接的地址是对应的 url。第二行将页面的内容简介进行显示,并在第三行显示页面对应的 url 和页面的抓取日期。

图 4. 搜索结果返回
图 4. 搜索结果返回

由于我们在试验过程中,主要爬取的是几大门户网站的网页,所以搜索“中国教育”并不会出来中国教育网之类的网站,但是,我们的结果返回了新浪和网易的教育频道,可见我们的搜索引擎是可以正确运行的。

网页排名

到目前为止,我们的网页已经可以正确的返回所输入和查询的结果,但是还有一个问题需要我们考虑,那就是网页排名策略。网页排名简单来说就是搜索引擎对搜索某个关键字产生的结果网页集合的返回顺序,由于对于用户来说,用户感兴趣的网页最好能够排在前面来显示,从而减少用户筛选结果的开销。网页排名策略即是考评结果网页集合排列顺序的算法策略,最基本的策略要求就是使得与用户输入最相关的网页排在之前,那么如何确定网页内容与用户输入关键词的相关程度呢?

我们还是以搜索“中国教育”为例解释网页排名策略。我们知道,“中国教育”可以分为两个关键词:中国、教育。根据经验,我们知道,包含这两个词多的网页要比包含这两个词少的网页相关,所以我们可以统计网页中,包含的关键词的总数,从而简单的确定网页的相关性。但是,这样的方法有个问题,那就是长的网页比短的网页跟占优势,所以我们需要根据网页的长度,对关键词的次数进行归一化,也就是用关键词的次数除以网页的总字数,这个商叫做“关键词词频”(Term Frequency),比如,某个 1000 词的网页中,中国出现了 10 词,教育出现了 3 次,那么两者的词频分别为 0.01 和 0.003,则其和 0.013 就是该网页与“中国教育”的相关度的一个简单度量。相关性的一个简单的度量。概括地讲,如果一个查询包含关键词 w1,w2,...,wn,它们在一个特定网页中的词频分别是 :TF1,TF2,...,TFn (TF: Term Frequency)。那么,这个查询和该网页的相关性就是:TF1+TF2+...+TFn。

进一步我们可以发现,“中国”这个词很普通,而“教育”是一个较为专业的词,所以后者在相关性排名中应该比前者重要,因此我们引入关键词的权重,以区分各个关键词之间的重要性。该权重应该具有如下特性:首先一个词预测主题能力越强,权重越大,反之则权重越小;其次,停用词的权重为 0。那么,这个权重如何确定呢?在信息检索中,使用最多的权重计算方法是“逆文本频率指数”(Inverse Document Frequency:IDF)。其公式为 log(D/DW), 其中,D 是全部网页数,而 DW 是关键词 W 在 DW 个网页中出现过。假设全部网页 D=10 亿,“教育”在 2 百万个网页中出现,则其权重 IDF=log(500)=6.2,同理若“中国”在 5 亿个网页中出现,则其权重为 IDF=log(2)=0.7。所以,我们网页相关性的计算公式也转变为:

TF1*IDF1+TF2*IDF2+...+TFn*IDFn。

第三,既然搜索“中国教育”,那我们希望网页中“中国”和“教育”这两个词的出现位置是更多的是处于相邻位置,诸如“浅谈中国教育”的网页内容应该比“中国工人先进性教育”更符合我们的搜索目标。关于位置信息需要在倒排索引建立的过程中进行抽取,由于在第二部分的倒排索引中,为了方便理解,我们只是建立了最简单的倒排索引,而没有加入位置信息,所以这部分的策略我们将在后续的优化部分进行说明。

总结

到现在为止,我们已经完成了 dySE 搜索引擎的实现过程讲解,我们按照搜索引擎中处理的三个模块进行分块介绍,从第一部分的网络爬虫获取原始网页库,到第二部分的预处理建立索引网页库、分词以及建立倒排索引,到此文中搭建 Web 服务器提供网络查询服务并且进行网页的排名。这其中爬虫是搜索引擎的基础,提供了原始数据集,而预处理是核心,提供后台的查询服务并且返回给前台 Web,而第三部分是与用户交互的接口,提供查询结果的输入和输出。三者互相依赖,互相配合完成搜索引擎的工作。

然而,目前我们还只是对各个模块进行了简单的实现和连接,一些优化方案和具体实现过程将在后续的优化章节中进行介绍。

阅读全文
0 0