Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lucene 全文检索实践

3,493 views

Published on

  • Be the first to comment

Lucene 全文检索实践

  1. 1. Lucene 全文检索实践(1) 页码,1/12 Lucene 全文检索实践(1) Lucene 是 Apache Jakarta 的一个子项目,是一个全文检索的搜索引擎库。其提供了简单实用的 API,通过这些 API,可以自行编写对文件(TEXT/XML/HTML等)、目录、数据库的全文检索程序。 Features: * Very fast indexing, minimal RAM required * Index compression to 30% of original text * Indexes text and HTML, document classes available for XML, PDF and RTF * Search supports phrase and Boolean queries, plus, minus and quote marks, and parentheses * Allows single and multiple character wildcards anywhere in the search words, fuzzy search, proximity * Will search for punctuation such as + or ? * Field searches for title, author, etc., and date-range searching * Supports most European languages * Option to store and display full text of indexed documents * Search results in relevance order * APIs for file format conversion, languages and user interfaces 实践任务: 1) 编写 Java 程序 MyIndexer.java,使用 JDBC 取出 MySQL 数据表内容(以某一论坛数据做测试),然后通过 org.apache.lucene.index.IndexWriter 创建索引。 2) 编写 Java 程序 MySearcher.java,通过 org.apache.lucene.search.IndexSearcher 等查询索引。 3) 实现支持中文查询及检索关键字高亮显示。 4) 通过 PHP / Java Integration 实现对 MySearch.java 的调用。 5) 实现对 PHP 手册(简体中文) 的全文检索。 Lucene 全文检索实践(2) Java 的程序基本编写完成,实现了对中文的支持。下一步是将其放到 WEB 上运行,首先想到的是使用 JSP,安装了 Apache Tomcat/4.1.24,默认的发布端口是 8080。现在面临的一个问题是:Apache httpd 的端口是 80,并且 我的机器对外只能通过 80 端口进行访问,如果将 Tomcat 的发布端口改成 80 的话,httpd 就没法对外了,而其上的 PHP 程序也将无法在 80 端口运行。 对于这个问题,我想到两种方案: 1、使用 PHP 直接调用 Java。需要做的工作是使用 --with-java 重新编译 PHP; 2、使用 mod_jk 做桥接的方式,将 servlet 引擎结合到 httpd 中。需要做的工作是编译 jakarta-tomcat- connectors-jk-1.2.5-src,生成 mod_jk.so 给 httpd 使用,然后按照 Howto 文档 进行 Tomcat、httpd 的配 置。 对于第一个方案的尝试:使用 PHP 直接调用 Java 环境 * PHP 4.3.6 prefix=/usr * Apache 1.3.27 prefix=/usr/local/apache * j2sdk1.4.1_01 prefix=/usr/local/jdk http://www.lucene.com.cn/sj.htm 2009-9-3
  2. 2. Lucene 全文检索实践(1) 页码,2/12 配置步骤 1) 安装 JDK,这个就不多说了,到 GOOGLE 可以搜索出这方面的大量文章。 2) 重新编译 PHP,我的 PHP 版本是 4.3.6: cd php-4.3.6 ./configure --with-java=/usr/local/jdk make make install 完成之后,会在 PHP 的 lib 下(我的是在 /usr/lib/php)有个 php_java.jar,同时在扩展动态库存放的目录下(我的 是在 /usr/lib/php/20020429)有个 java.so 文件。到这一步需要注意一个问题,有些 PHP 版本生成的是 libphp_java.so 文件,extension 的加载只认 libphp_java.so,直接加载 java.so 可能会出现如下错误: PHP Fatal error: Unable to load Java Library /usr/local/jdk/jre/lib/i386/libjava.so, error: libjvm.so: cannot open shared object file: No such file or directory in /home/nio/public_html/java.php on line 2 所以如果生成的是 java.so,需要创建一个符号连接: ln -s java.so libphp_java.so 3) 修改 Apache Service 启动文件(我的这个文件为 /etc/init.d/httpd),在这个文件中加入: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/jdk/jre/lib/i386/server:/usr/local/jdk/jre/lib 正如你所看到的,我的 JDK 装在 /usr/local/jdk 目录下,如果你的不是在此目录,请做相应改动(下同)。 4) 修改 PHP 配置文件 php.ini,找到 [Java] 部分进行修改: [Java] java.class.path = /usr/lib/php/php_java.jar java.home = /usr/local/jdk ;java.library = ;java.library.path = extension_dir=/usr/lib/php/20020429/ extension=java.so 我将 java.library 及 java.library.path 都注释掉了,PHP 会自动认为 java.library=/usr/local/jdk/jre/lib/i386/libjava.so。 5) 重新启动 Apache httpd 服务: service httpd restart http://www.lucene.com.cn/sj.htm 2009-9-3
  3. 3. Lucene 全文检索实践(1) 页码,3/12 测试 测试脚本 java.php 源代码: getProperty('java.version').'<br />'; print 'Java vendor=' . $system->getProperty('java.vendor').'<br />'; print 'OS=' . $system->getProperty('os.name') . ' ' . $system->getProperty('os.version') . ' on ' . $system->getProperty('os.arch') . '<br />'; ?> 总结 安装配置还算简单,但是在 PHP 运行 Java 的速度感觉较慢,所以下定决心开始实践第二个方案。(待续) Lucene 全文检索实践(3) 今天总算有些空闲时间,正好说说第二种方案:使用 mod_jk 做桥接的方式,将 servlet 引擎结合到 httpd 中。 环境 * PHP 4.3.6 prefix=/usr * Apache 1.3.27 prefix=/usr/local/apache * j2sdk1.4.1_01 prefix=/usr/local/jdk * jakarta-tomcat-4.1.24 prefix=/usr/local/tomcat * 另外需要下载 jakarta-tomcat-connectors-jk-1.2.5-src.tar.gz 配置步骤 1) 安装 JDK 与 Tomcat,这些安装步骤就不多说了。 2) 编译 jakarta-tomcat-connectors-jk-1.2.5-src,生成 mod_jk.so,并将其复制到 apache 的 modules 存放目 录: tar xzf jakarta-tomcat-connectors-jk-1.2.5-src.tar.gz cd jakarta-tomcat-connectors-jk-1.2.5-src/jk/native ./configure --with-apxs=/usr/local/apache/bin/apxs make cp apache-1.3/mod_jk.so /usr/local/apache/libexec 3) 编辑 Apache 配置文件 /usr/local/apache/conf/httpd.conf,加入: LoadModule jk_module libexec/mod_jk.so AddModule mod_jk.c 这个 LoadModule 语句最好放在其他 LoadModule 语句后边。 同时在配置文件后边加入: # workers.properties 文件所在路径,后边将对此文件进行讲解 JkWorkersFile /usr/local/apache/conf/workers.properties # jk 的日志文件存放路径 JkLogFile /usr/local/apache/log/mod_jk.log http://www.lucene.com.cn/sj.htm 2009-9-3
  4. 4. Lucene 全文检索实践(1) 页码,4/12 # 设置 jk 的日志级别 [debug/error/info] JkLogLevel info # 选择日志时间格式 JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " # JkOptions 选项设置 JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories # JkRequestLogFormat 设置日志的请求格式 JkRequestLogFormat "%w %V %T" # 映射 /examples/* 到 worker1,worker1 在 workers.properties 文件中定义 JkMount /examples/* worker1 4) 在 /usr/local/apache/conf/ 目录下创建 workers.properties 文件,其内容如下: # 定义使用 ajp13 的 worker1 worker.list=worker1 # 设置 worker1 的属性(ajp13) worker.worker1.type=ajp13 worker.worker1.host=localhost worker.worker1.port=8009 worker.worker1.lbfactor=50 worker.worker1.cachesize=10 worker.worker1.cache_timeout=600 worker.worker1.socket_keepalive=1 worker.worker1.socket_timeout=300 5) 好了,启动 Tomcat,重启一下 Apache HTTPD Server,访问:http://localhost/examples/index.jsp,看看结 果如何,和 http://localhost:8080/examples/index.jsp 是一样的。 提示:如果不想让别人通过 8080 端口访问到你的 Tomcat,可以将 /usr/lcoal/tomcat/conf/server.xml 配置文件中 的如下代码加上注释: <!-- <Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="8080" minProcessors="5" maxProcessors="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" useURIValidationHack="false" disableUploadTimeout="true" /> --> 然后重新启动 Tomcat 即可。 总结 此方案安装配置稍微复杂些,但执行效率要比第一种方案要好很多。所以决定使用这种方案来完成我的 Lucene 全文检索 实践任务。 http://www.lucene.com.cn/sj.htm 2009-9-3
  5. 5. Lucene 全文检索实践(1) 页码,5/12 Tomcat Service 脚本 在 Linux (我用的是 Redhat)中,如果经常需要启动/关闭 Tomcat 的话,还是创建一个 daemon 来得比较方便,创 建步骤如下: 1) 在 /etc/init.d/ 目录下创建文件 tomcat,代码如下: # chkconfig: 345 91 10 # description: Tomcat daemon. # # 包含函数库 . /etc/rc.d/init.d/functions # 获取网络配置 . /etc/sysconfig/network # 检测 NETWORKING 是否为 "yes" [ "${NETWORKING}" = "no" ] && exit 0 # 设置变量 # $TOMCAT 指向 Tomcat 的安装目录 TOMCAT=/usr/local/tomcat # $STARTUP 指向 Tomcat 的启动脚本 STARTUP=$TOMCAT/bin/startup.sh # $SHUTDOWN 指向 Tomcat 的关闭脚本 SHUTDOWN=$TOMCAT/bin/shutdown.sh # 设置 JAVA_HOME 环境变量,指向 JDK 安装目录 export JAVA_HOME=/usr/local/jdk # 启动服务函数 start() { echo -n $"Starting Tomcat service: " $STARTUP RETVAL=$? echo } # 关闭服务函数 stop() { action $"Stopping Tomcat service: " $SHUTDOWN RETVAL=$? echo http://www.lucene.com.cn/sj.htm 2009-9-3
  6. 6. Lucene 全文检索实践(1) 页码,6/12 } # 根据参数选择调用 case "$1" in start) start ;; stop) stop ;; restart) stop start ;; *) echo $"Usage: $0 start|stop|restart" exit 1 esac exit 0 2) 修改 tomcat 文件的属性 chown a+x tomcat 3) 生成 service chkconfig --add tomcat 好了,现在可以通过 service tomcat start 命令启动 Tomcat 了,关闭及重启服务的命令也类似,只是将 start 换成 stop 或 restart。 Lucene 全文检索实践(4) 在几天的研究中,了解了 Lucene 全文检索的一些原理,同时进行了实践,编写了一个论坛的全文检索创建索引程序及用 于搜索的 JSP 程序,另外还写了一个 PHP 手册(简体中文)的全文检索,可以进行多关键字搜索。基本完成了最初定下 的实践任务。 Lucene 全文检索实践(5) 对于 Lucene 的初步研究已经过去一段时间,自己感觉还不是很深入,但由于时间的关系,一直也没再拿起。应网友的要 求,将自己实践中写的一些代码贴出来,希望能对大家有用。程序没有做进一步的优化,只是很简单的实现功能而已,仅供 参考。 在实践中,我以将 PHP 中文手册中的 HTML 文件生成索引,然后通过一个 JSP 对其进行全文检索。 生成索引的 Java 代码: /** * PHPDocIndexer.java * 用于对 PHPDoc 的 HTML 页面生成索引文件。 http://www.lucene.com.cn/sj.htm 2009-9-3
  7. 7. Lucene 全文检索实践(1) 页码,7/12 */ import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.io.IOException; import java.util.Date; import java.text.DateFormat; import java.lang.*; import org.apache.lucene.analysis.cjk.CJKAnalyzer; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.DateField; class PHPDocIndexer { public static void main(String[] args) throws ClassNotFoundException, IOException { try { Date start = new Date(); IndexWriter writer = new IndexWriter("/home/nio/indexes-phpdoc", new CJKAnalyzer(), true); //索引保存目录,必须存在 indexDocs(writer, new File("/home/nio/phpdoc-zh")); //HTML 文件保存目录 System.out.println("Optimizing ...."); writer.optimize(); writer.close(); Date end = new Date(); System.out.print("Total time: "); System.out.println(end.getTime() - start.getTime()); } catch (Exception e) { System.out.println("Class " + e.getClass() + " throws error!n errmsg: " + e.getMessage()); } //end try } //end main public static void indexDocs(IndexWriter writer, File file) throws Exception { if (file.isDirectory()) { String[] files = file.list(); for (int i = 0; i < files.length; i++) { indexDocs(writer, new File(file, files[i])); http://www.lucene.com.cn/sj.htm 2009-9-3
  8. 8. Lucene 全文检索实践(1) 页码,8/12 } //end for } else if (file.getPath().endsWith(".html")) { //只对 HTML 文件做索引 System.out.print("Add file:" + file + " ...."); // Add html file .... Document doc = new Document(); doc.add(Field.UnIndexed("file", file.getName())); //索引文件名 doc.add(Field.UnIndexed("modified", DateFormat.getDateTimeInstance().format (new Date(file.lastModified())))); //索引最后修改时间 String title = ""; String content = ""; String status = "start"; FileReader fReader = new FileReader(file); BufferedReader bReader = new BufferedReader(fReader); String line = bReader.readLine(); while (line != null) { content += line; //截取 HTML 标题 <title> if ("start" == status && line.equalsIgnoreCase("><TITLE")) { status = "match"; } else if ("match" == status) { title = line.substring(1, line.length() - 7); doc.add(Field.Text("title", title)); //索引标题 status = "end"; } //end if line = bReader.readLine(); } //end while bReader.close(); fReader.close(); doc.add(Field.Text("content", content.replaceAll("<[^<>]+>", ""))); //索引内 容 writer.addDocument(doc); System.out.println(" [OK]"); } //end if } } //end class 索引生成完之后,就需要一个检索页面,下边是搜索页面(search.jsp)的代码: <%@ page language="java" import="javax.servlet.*, javax.servlet.http.*, java.io.*, java.util.Date, java.util.ArrayList, java.util.regex.*, org.apache.lucene.analysis.*, org.apache.lucene.document.*, org.apache.lucene.index.*, org.apache.lucene.search.*, org.apache.lucene.queryParser.*, org.apache.lucene.analysis.Token, http://www.lucene.com.cn/sj.htm 2009-9-3
  9. 9. Lucene 全文检索实践(1) 页码,9/12 org.apache.lucene.analysis.TokenStream, org.apache.lucene.analysis.cjk.CJKAnalyzer, org.apache.lucene.analysis.cjk.CJKTokenizer, com.chedong.weblucene.search.WebLuceneHighlighter" %> <%@ page contentType="text/html;charset=GB2312" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>PHPDoc - PHP 简体中文手册全文检索</title> <base target="main"><!-- 由于使用了 Frame,所以指定 target 到 main 窗口显示 --> <style> body {background-color: white; margin: 4px} body, input, div {font-family: Tahoma; font-size: 9pt} body, div {line-height: 18px} u {color: red} b {color: navy} form {padding: 0px; margin: 0px} .txt {border: 1px solid black} .f {padding: 4px; margin-bottom: 16px; background-color: #E5ECF9; border-top: 1px solid #3366CC; border-bottom: 1px solid #3366CC; text-align: center;} .d, .o {padding-left: 16px} .d {color: gray} .o {color: green} .o a {color: #7777CC} </style> <script language="JavaScript"> function gotoPage(i) { document.frm.page.value = i; document.frm.submit(); } //end function </script> </head> <body> <% String keyVal = null; String pageVal = null; int offset = 0; int curPage = 0; int pages; final int ROWS = 50; //获取 GET 参数 try { http://www.lucene.com.cn/sj.htm 2009-9-3
  10. 10. Lucene 全文检索实践(1) 页码,10/12 byte[] keyValByte = request.getParameter("key").getBytes("ISO8859_1"); //查找关键字 keyVal = new String(keyValByte); pageVal = request.getParameter("page"); //页码 } catch (Exception e) { //do nothing; } if (keyVal == null) keyVal = new String(""); %> <div class="f"> <form name="frm" action="./index.jsp" method="GET" onsubmit="this.page.value='0';return true;" target="_self"> <input type="text" name="key" class="txt" size="40" value="<%=keyVal%>" /> <input type="hidden" name="page" value="<%=pageVal%>" /> <input type="submit" value="搜 索" /><br /> <font color="green">提示:可使用多个关键字(使用空格隔开)提高搜索的准确率。</font> </form> <script language="JavaScript"> document.frm.key.focus(); </script> </div> <% if (keyVal != null && keyVal.length() > 0) { try { curPage = Integer.parseInt(pageVal); //将当前页转换成整数 } catch (Exception e) { //do nothing; } //end try try { Date startTime = new Date(); keyVal = keyVal.toLowerCase().replaceAll("(or|and)", "").trim().replaceAll ("s+", " AND "); Searcher searcher = new IndexSearcher("/home/nio/indexes-phpdoc"); //索引目录 Analyzer analyzer = new CJKAnalyzer(); String[] fields = {"title", "content"}; Query query = MultiFieldQueryParser.parse(keyVal, fields, analyzer); Hits hits = searcher.search(query); StringReader in = new StringReader(keyVal); TokenStream tokenStream = analyzer.tokenStream("", in); ArrayList al = new ArrayList(); for (Token token = tokenStream.next(); token != null; token = tokenStream.next()) { al.add(token.termText()); } //end for http://www.lucene.com.cn/sj.htm 2009-9-3
  11. 11. Lucene 全文检索实践(1) 页码,11/12 //总页数 pages = (new Integer(hits.length()).doubleValue() % ROWS != 0) ? (hits.length() / ROWS) + 1 : (hits.length() / ROWS); //当前页码 if (curPage < 1) curPage = 1; else if (curPage > pages) curPage = pages; //起始、终止下标 offset = (curPage - 1) * ROWS; int end = Math.min(hits.length(), offset + ROWS); //循环输出查询结果 WebLuceneHighlighter hl = new WebLuceneHighlighter(al); for (int i = offset; i < end; i++) { Document doc = hits.doc(i); %> <div class="t"><a href="/~nio/phpdoc-zh/<%=doc.get("file")%>"><%=hl.highLight (doc.get("title"))%></a></div> <div class="d"><%=hl.highLight(doc.get("content").replaceAll("n", " "), 100)%> ……</div> <div class="o"> /~nio/phpdoc-zh/<%=doc.get("file")%> - <%=doc.get("modified")%> </div> <br /> <% } //end for searcher.close(); Date endTime = new Date(); %> <div class="f"> 检索总共耗时 <b><%=((endTime.getTime() - startTime.getTime()) / 1000.0)% ></b> 秒,约有 <b><%=hits.length()%></b> 项符合条件的记录,共 <b><%=pages%></b> 页 <% if (curPage > 1 && pages > 1) { %> | <a href="javascript:gotoPage(<%=(curPage-1)%>);" target="_self">上一页</a> <% } //end if if (curPage < pages && pages > 1) { %> | <a href="javascript:gotoPage(<%=(curPage+1)%>)" target="_self">下一页</a> http://www.lucene.com.cn/sj.htm 2009-9-3
  12. 12. Lucene 全文检索实践(1) 页码,12/12 <% } //end if } catch (Exception e) { %> <!-- <%=e.getClass()%> 导致错误:<%=e.getMessage()%> --> <% } //end if } //end if %> </body> </html> 在线示例:PHP 手册(简体中文)。 http://www.lucene.com.cn/sj.htm 2009-9-3

×