Your SlideShare is downloading. ×
Lucene 全文检索实践
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Lucene 全文检索实践

3,039
views

Published on


0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,039
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
12
Comments
0
Likes
2
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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

×