【转】 用 Lucene 加速 Web 搜索应用程序的开发

In: 搜索引擎

2 十一 2006

作者:周 登朋 (zhoudengpeng@yahoo.com.cn), 软件工程师, 上海交通大学

2006 年 9 月 06 日 

在本篇文章中,你会学习到如何利用 Lucene 实现高级搜索功能以及如何利用 Lucene 来创建 Web 搜索应用程序。通过这些学习,你就可以利用 Lucene 来创建自己的搜索应用程序。

架构概览

通常一个 Web 搜索引擎的架构分为前端和后端两部分,就像图一中所示。在前端流程中,用户在搜索引擎提供的界面中输入要搜索的关键词,这里提到的用户界面一般是一个带有输入框的 Web 页面,然后应用程序将搜索的关键词解析成搜索引擎可以理解的形式,并在索引文件上进行搜索操作。在排序后,搜索引擎返回搜索结果给用户。在后端流程中,网络爬虫或者机器人从因特网上获取 Web 页面,然后索引子系统解析这些 Web 页面并存入索引文件中。如果你想利用 Lucene 来创建一个 Web 搜索应用程序,那么它的架构也和上面所描述的类似,就如图一中所示。
Figure 1. Web 搜索引擎架构
Web搜索引擎架构

利用 Lucene 实现高级搜索

Lucene 支持多种形式的高级搜索,我们在这一部分中会进行探讨,然后我会使用 Lucene 的 API 来演示如何实现这些高级搜索功能。

布尔操作符

大多数的搜索引擎都会提供布尔操作符让用户可以组合查询,典型的布尔操作符有 AND, OR, NOT。Lucene 支持 5 种布尔操作符,分别是 AND, OR, NOT, 加(+), 减(-)。接下来我会讲述每个操作符的用法。

  • OR: 如果你要搜索含有字符 A 或者 B 的文档,那么就需要使用 OR 操作符。需要记住的是,如果你只是简单的用空格将两个关键词分割开,其实在搜索的时候搜索引擎会自动在两个关键词之间加上 OR 操作符。例如,“Java OR Lucene” 和 “Java Lucene” 都是搜索含有 Java 或者含有 Lucene 的文档。
  • AND: 如果你需要搜索包含一个以上关键词的文档,那么就需要使用 AND 操作符。例如,“Java AND Lucene” 返回所有既包含 Java 又包含 Lucene 的文档。
  • NOT: Not 操作符使得包含紧跟在 NOT 后面的关键词的文档不会被返回。例如,如果你想搜索所有含有 Java 但不含有 Lucene 的文档,你可以使用查询语句 “Java NOT Lucene”。但是你不能只对一个搜索词使用这个操作符,比如,查询语句 “NOT Java” 不会返回任何结果。
  • 加号(+): 这个操作符的作用和 AND 差不多,但它只对紧跟着它的一个搜索词起作用。例如,如果你想搜索一定包含 Java,但不一定包含 Lucene 的文档,就可以使用查询语句“+Java Lucene”。
  • 减号(-): 这个操作符的功能和 NOT 一样,查询语句 “Java -Lucene” 返回所有包含 Java 但不包含 Lucene 的文档。

接下来我们看一下如何利用 Lucene 提供的 API 来实现布尔查询。清单1 显示了如果利用布尔操作符进行查询的过程。
清单1:使用布尔操作符

  //Test boolean operator
public void testOperator(String indexDirectory) throws Exception{
   Directory dir 
= FSDirectory.getDirectory(indexDirectory,false);
   IndexSearcher indexSearcher 
= new IndexSearcher(dir);
   String[] searchWords 
= {Java AND LuceneJava NOT LuceneJava OR Lucene
                    
+Java +Lucene+Java -Lucene}
;
   Analyzer language 
= new StandardAnalyzer();
   Query query;
   
for(int i = 0; i < searchWords.length; i++){
      query 
= QueryParser.parse(searchWords[i], title, language);
      Hits results 
= indexSearcher.search(query);
      System.out.println(results.length() 
+ search results for query  + searchWords[i]);
   }

}

 

域搜索(Field Search)

Lucene 支持域搜索,你可以指定一次查询是在哪些域(Field)上进行。例如,如果索引的文档包含两个域,TitleContent,你就可以使用查询 “Title: Lucene AND Content: Java” 来返回所有在 Title 域上包含 Lucene 并且在 Content 域上包含 Java 的文档。清单 2 显示了如何利用 Lucene 的 API 来实现域搜索。
清单2:实现域搜索

//Test field search
public void testFieldSearch(String indexDirectory) throws Exception{
    Directory dir 
= FSDirectory.getDirectory(indexDirectory,false);
    IndexSearcher indexSearcher 
= new IndexSearcher(dir);
    String searchWords 
= title:Lucene AND content:Java;
    Analyzer language 
= new StandardAnalyzer();
    Query query 
= QueryParser.parse(searchWords, title, language);
    Hits results 
= indexSearcher.search(query);
    System.out.println(results.length() 
+ search results for query  + searchWords);
}

 

通配符搜索(Wildcard Search)

Lucene 支持两种通配符:问号(?)和星号(*)。你可以使用问号(?)来进行单字符的通配符查询,或者利用星号(*)进行多字符的通配符查询。例如,如果你想搜索 tiny 或者 tony,你就可以使用查询语句 “t?ny”;如果你想查询 Teach, Teacher 和 Teaching,你就可以使用查询语句 “Teach*”。清单3 显示了通配符查询的过程。
清单3:进行通配符查询

//Test wildcard search
public void testWildcardSearch(String indexDirectory)throws Exception{
   Directory dir 
= FSDirectory.getDirectory(indexDirectory,false);
   IndexSearcher indexSearcher 
= new IndexSearcher(dir);
   String[] searchWords 
= {tex*tex??ex*};
   Query query;
   
for(int i = 0; i < searchWords.length; i++){
      query 
= new WildcardQuery(new Term(title,searchWords[i]));
      Hits results 
= indexSearcher.search(query);
      System.out.println(results.length() 
+ search results for query  + searchWords[i]);
   }

}

 

模糊查询

Lucene 提供的模糊查询基于编辑距离算法(Edit distance algorithm)。你可以在搜索词的尾部加上字符 ~ 来进行模糊查询。例如,查询语句 “think~” 返回所有包含和 think 类似的关键词的文档。清单 4 显示了如果利用 Lucene 的 API 进行模糊查询的代码。
清单4:实现模糊查询

//Test fuzzy search
public void testFuzzySearch(String indexDirectory)throws Exception{
   Directory dir 
= FSDirectory.getDirectory(indexDirectory,false);
   IndexSearcher indexSearcher 
= new IndexSearcher(dir);
   String[] searchWords 
= {textfunny};
   Query query;
   
for(int i = 0; i < searchWords.length; i++){
      query 
= new FuzzyQuery(new Term(title,searchWords[i]));
      Hits results 
= indexSearcher.search(query);
      System.out.println(results.length() 
+ search results for query  + searchWords[i]);
   }

}

 

范围搜索(Range Search)

范围搜索匹配某个域上的值在一定范围的文档。例如,查询 “age:[18 TO 35]” 返回所有 age 域上的值在 18 到 35 之间的文档。清单5显示了利用 Lucene 的 API 进行返回搜索的过程。
清单5:测试范围搜索

//Test range search
public void testRangeSearch(String indexDirectory)throws Exception{
    Directory dir 
= FSDirectory.getDirectory(indexDirectory,false);
    IndexSearcher indexSearcher 
= new IndexSearcher(dir);
    Term begin 
= new Term(birthDay,20000101);
    Term end   
= new Term(birthDay,20060606);
    Query query 
= new RangeQuery(begin,end,true);
    Hits results 
= indexSearcher.search(query);
    System.out.println(results.length() 
+ search results is returned);
}

 

在 Web 应用程序中集成 Lucene

接下来我们开发一个 Web 应用程序利用 Lucene 来检索存放在文件服务器上的 HTML 文档。在开始之前,需要准备如下环境:

  1. Eclipse 集成开发环境
  2. Tomcat 5.0
  3. Lucene Library
  4. JDK 1.5

这个例子使用 Eclipse 进行 Web 应用程序的开发,最终这个 Web 应用程序跑在 Tomcat 5.0 上面。在准备好开发所必需的环境之后,我们接下来进行 Web 应用程序的开发。

1、创建一个动态 Web 项目

  1. 在 Eclipse 里面,选择 File > New > Project,然后再弹出的窗口中选择动态 Web 项目,如图二所示。

图二:创建动态Web项目
创建动态Web项目

  1. 在创建好动态 Web 项目之后,你会看到创建好的项目的结构,如图三所示,项目的名称为 sample.dw.paper.lucene。

图三:动态 Web 项目的结构
动态 Web 项目的结构

2. 设计 Web 项目的架构

在我们的设计中,把该系统分成如下四个子系统:

  1. 用户接口: 这个子系统提供用户界面使用户可以向 Web 应用程序服务器提交搜索请求,然后搜索结果通过用户接口来显示出来。我们用一个名为 search.jsp 的页面来实现该子系统。
  2. 请求管理器: 这个子系统管理从客户端发送过来的搜索请求并把搜索请求分发到搜索子系统中。最后搜索结果从搜索子系统返回并最终发送到用户接口子系统。我们使用一个 Servlet 来实现这个子系统。
  3. 搜索子系统: 这个子系统负责在索引文件上进行搜索并把搜索结构传递给请求管理器。我们使用 Lucene 提供的 API 来实现该子系统。
  4. 索引子系统: 这个子系统用来为 HTML 页面来创建索引。我们使用 Lucene 的 API 以及 Lucene 提供的一个 HTML 解析器来创建该子系统。

图4 显示了我们设计的详细信息,我们将用户接口子系统放到 webContent 目录下面。你会看到一个名为 search.jsp 的页面在这个文件夹里面。请求管理子系统在包 sample.dw.paper.lucene.servlet 下面,类 SearchController 负责功能的实现。搜索子系统放在包 sample.dw.paper.lucene.search 当中,它包含了两个类,SearchManagerSearchResultBean,第一个类用来实现搜索功能,第二个类用来描述搜索结果的结构。索引子系统放在包 sample.dw.paper.lucene.index 当中。类 IndexManager 负责为 HTML 文件创建索引。该子系统利用包 sample.dw.paper.lucene.util 里面的类 HTMLDocParser 提供的方法 getTitlegetContent 来对 HTML 页面进行解析。
图四:项目的架构设计
项目的架构设计

3. 子系统的实现

在分析了系统的架构设计之后,我们接下来看系统实现的详细信息。

  1. 用户接口: 这个子系统有一个名为 search.jsp 的 JSP 文件来实现,这个 JSP 页面包含两个部分。第一部分提供了一个用户接口去向 Web 应用程序服务器提交搜索请求,如图5所示。注意到这里的搜索请求发送到了一个名为 SearchController 的 Servlet 上面。Servlet 的名字和具体实现的类的对应关系在 web.xml 里面指定。

图5:向Web服务器提交搜索请求

向Web服务器提交搜索请求

 

 

这个JSP的第二部分负责显示搜索结果给用户,如图6所示:
图6:显示搜索结果

显示搜索结果

 

 

  1. 请求管理器: 一个名为 SearchController 的 servlet 用来实现该子系统。清单6给出了这个类的源代码。

清单6:请求管理器的实现

package sample.dw.paper.lucene.servlet;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import sample.dw.paper.lucene.search.SearchManager;

/**
 * This servlet is used to deal with the search request
 * and return the search results to the client
 
*/

public class SearchController extends HttpServlet{

    
private static final long serialVersionUID = 1L;

    
public void doPost(HttpServletRequest request, HttpServletResponse response)
                      
throws IOException, ServletException{
        String searchWord 
= request.getParameter(searchWord);
        SearchManager searchManager 
= new SearchManager(searchWord);
        List searchResult 
= null;
        searchResult 
= searchManager.search();
        RequestDispatcher dispatcher 
= request.getRequestDispatcher(search.jsp);
        request.setAttribute(
searchResult,searchResult);
        dispatcher.forward(request, response);
    }


    
public void doGet(HttpServletRequest request, HttpServletResponse response)
                     
throws IOException, ServletException{
        doPost(request, response);
    }

}

 

清单6中,doPost 方法从客户端获取搜索词并创建类 SearchManager 的一个实例,其中类 SearchManager 在搜索子系统中进行了定义。然后,SearchManager 的方法 search 会被调用。最后搜索结果被返回到客户端。

  1. 搜索子系统: 在这个子系统中,我们定义了两个类:SearchManagerSearchResultBean。第一个类用来实现搜索功能,第二个类是个JavaBean,用来描述搜索结果的结构。清单7给出了类 SearchManager 的源代码。

清单7:搜索功能的实现

package sample.dw.paper.lucene.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;

import sample.dw.paper.lucene.index.IndexManager;

/**
 * This class is used to search the 
 * Lucene index and return search resul
 

Comment Form

About this blog

QK31欢迎你的到来.

Photostream

search_extends

 

2006年十一月
« 十   十二 »
 12345
6789101112
13141516171819
20212223242526
27282930  

23
Unique
Visitors
Powered By Google Analytics

分类目录

标签云