开始使用 Zend_Search_Lucene
目录

•介绍 Zend_Search_Lucene
•Lucene 索引结构
•索引开始和创建
•索引
•搜索
•被支持的查询
•搜索结果分页
返回目录

介绍 Zend_Search_Lucene
Zend_Search_Lucene 组件的目的是提供一个即可使用的全文搜索解决方案。它不要求任何 PHP 扩展 UTF-8mbstring 或者安装额外的软件,在 Zend_Framework 安装以后立刻能被使用。

Zend_Search_Lucene 是流行开源全文搜索引擎 Apache Lucene 的一个纯 PHP 端口。查看 http://lucene.apache.org 获得更多信息。

为了可以搜索,信息必须被建立索引。Zend_Search_Lucene 和 Java Lucene 使用一个文件概念,称之为 atomic indexing item。

每一个文件是一套字段:<name, value> 一对,name 和 value 是 UTF-8 字符串。文件字段的任何子集可以被标记为 indexed 来包括在建立文字索引的过程中的字段数据。

当建立索引的时候,字段值可以或不必被标记。如果一个字段不被标记,字段值以一个词组保存;否则,当前的分析器被用作标记。

在 Zend_Search_Lucene 包中提供了几个分析器。默认的分析器工作在 ASCII 文字(因为 UTF-8 分析器需要打开 mbstring 扩展)。它是大小写敏感,而且它会忽略数字。如果你需要改变这个行为,可以使用其它的分析器或者创建你自己的分析器。

注意:在索引和搜索的过程中使用分析器
重要提示!搜索语句同样使用当前的分析器标记,所以在索引和搜索过程中必须设置相同的默认分析器。这将保证源文件和搜索文字以同样的方式转化成词组。

字段值可选的被保存在一个索引内。这允许当搜索的时候,原始的字段数据可以从索引中被检索。这个是把搜索结果和原始数据关联起来的唯一方法(在索引优化或者自动优化以后,内部的文件 ID 可能被改变)。

要记住的是,Lucene 索引不是一个数据库。除了备份文件系统目录,它不提供索引备份机制。它不提供事务缓存(transactional)机制,虽然迸发索引更新和迸发更新和读取被支持。在数据检索速度上它比不上数据库。

所以好的主意:

•不要把 Lucene 索引当储存器使用,因为它可能戏剧性的降低搜索的命中率(search hit retrieving performance)。只保存独一无二的文件标识符(文件路径,URL,数据库唯一 ID)和在一个索引内的相关数据。比如,标题,注解,目录,语言信息,化身。(注意:一个字段可能被包括在索引过程,但不被储存,或者被储存,但不被索引)。
•如果因为某种原因它被破坏,写下功能可能完整的重建一个索引。
在索引中的独立的文件,可能有完全不同的字段组合。不同文件中的同样的字段不必拥有相同的属性。例如,一个字段可以为一个文件被索引,同时被其它文件的索引忽略。存储,标识,或者把字段值当作一个二进制字符串处理,也是如此。

返回目录

Lucene 索引结构
为了全面的使用 Zend_Search_Lucene 的最大功能,你应该了解它的内置索引结构

一个索引以一套文件的形式被保存在一个单独的目录中。

一个索引由数量众多的独立片断组成,这些片断储存有信息,这些信息是关于被索引文件的一个子集。每一个片断有它自己的词组字典,词组字典索引,和文件存储(保存字段值)Zend_Search_Lucene。所有的片断数据保存在 _xxxxx.cfs 文件中,xxxxx 是片断的名字。

一旦一个索引片断文件被创建,它不能被更新。新的文件被加到新的片断中。删除的文件只被以 deleted 标记在一个可选择的 <segmentname>.del 文件中。

文件更新将会以删除和添加这两个独立的操作来完成,虽然它是通过调用 update() API Zend_Search_Lucene API 做到的。这简化了添加新文件,并允许和搜索操作同时更新。

在另外一方面,使用几个片断()增加了搜索时间:

•如果词组字典到达了一个饱合点,大多数情况下,那么搜索一个片断 N 次将比搜索 N 个片断的速度要快。
•索引优化把两个或者更多的片断合并进一个单独的新片断。一个新的片断被添加到索引片断列表,旧的片断被排除在外。
•片断列表更新的过程如同一个原子操作。这样可以同时添加新的文件,进行索引优化,和在索引内搜索。
•索引自动优化在每一个新的片断产生以后进行。它把最小的片断合并进大的片断,然后大的片断合并到更大的片断,如果我们有足够的片断来合并的话。
索引自动优化被这三个选项控制:

MaxBufferedDocs(在缓冲内存中文件被写入到一个新的片断之前,要求的文件的最小数量)

MaxMergeDocs(被一个优化操作合并的文件的最多的数量)

MergeFactor(用来决定由自动优化操作合并的片断标记体的频繁程度)。

如果每执行一个脚本我们添加一个文件,那么 MaxBufferedDocs 事实上没有被使用(在脚本执行结束的时候只有一个新片断和一个文件被创建,这时自动优化进程开始)。

返回目录

打开和创建索引
全部的索引操作(例如,创建一个新的索引,给索引添加一个文件,删除一个文件,搜索整个索引)需要一个索引对象。可以使用以下两种方法的任何一个:

例一 Lucene 索引创建

view plaincopy to clipboardprint?
01.$index = Zend_Search_Lucene::create($indexPath); 
  $index = Zend_Search_Lucene::create($indexPath);

例二 Lucene 索引打开

view plaincopy to clipboardprint?
01.$index = Zend_Search_Lucene::open($indexPath); 
  $index = Zend_Search_Lucene::open($indexPath);

返回目录

索引
索引由添加一个新文件到一个已经存在或者新的索引完成:

view plaincopy to clipboardprint?
01.$index->addDocument($doc); 
$index->addDocument($doc);

有两种办法来创建一个文件对象。第一个是手动的

例一 手动文件建造

view plaincopy to clipboardprint?
01.$doc = new Zend_Search_Lucene_Document();  
02.$doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));  
03.$doc->addField(Zend_Search_Lucene_Field::Text('title', $docTitle));  
04.$doc->addField(Zend_Search_Lucene_Field::unStored('contents', $docBody));  
05.$doc->addField(Zend_Search_Lucene_Field::binary('avatar', $avatarData)); 
  $doc = new Zend_Search_Lucene_Document();
  $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
  $doc->addField(Zend_Search_Lucene_Field::Text('title', $docTitle));
  $doc->addField(Zend_Search_Lucene_Field::unStored('contents', $docBody));
  $doc->addField(Zend_Search_Lucene_Field::binary('avatar', $avatarData));

第二种办法是从 HTML 或者微软 Office 2007 文件中载入:

例二 文件载入

view plaincopy to clipboardprint?
01.$doc = Zend_Search_Lucene_Document_Html::loadHTML($htmlString);  
02.$doc = Zend_Search_Lucene_Document_Docx::loadDocxFile($path);  
03.$doc = Zend_Search_Lucene_Document_Pptx::loadPptFile($path);  
04.$doc = Zend_Search_Lucene_Document_Xlsx::loadXlsFile($path); 
  $doc = Zend_Search_Lucene_Document_Html::loadHTML($htmlString);
  $doc = Zend_Search_Lucene_Document_Docx::loadDocxFile($path);
  $doc = Zend_Search_Lucene_Document_Pptx::loadPptFile($path);
  $doc = Zend_Search_Lucene_Document_Xlsx::loadXlsFile($path);

如果一个文件以一个被支持的格式载入,它仍然可以通过新的用户定义字段来手动扩展。

索引策略
你应该在你的应用程序的结构设计中定义索引策略。

你可能需要一个随选定制的索引设置(和 OLTP 系统相似的东西)。在这样的系统,你通常为每一用户请求添加一个文件。这样,MaxBufferedDocs 选项将对系统没有影响。在另外一方面,MaxMergeDocs 由于它允许你限制脚本执行时间的最大值,就真的有用了。MergeFactor 应该被设置到一个值,这个值保持平均索引时间(它也受平均自动优化时间影响)和搜索效率(索引优化水平依赖于片断的数量)之间的平衡。

如果你主要是进行批量索引更新,你的设置应该使用一个 MaxBufferedDocs 选项,把它设置到被可用的内存支持的最大值。MaxMergeDocs 和 MergeFactor 不得不设置到尽可能的降低自动优化相关的值。全部的索引拿破仑应该在索引以后进行。

例三 索引优化

view plaincopy to clipboardprint?
01.$index->optimize(); 
$index->optimize();

在一些配置中,更有效的是,通过组织更新请求到一个队列,和在一个单独的脚本执行中进行几个更新请求来序列化索引更新。这将降低索引过分打开,同时允许利用索引文件缓冲。

返回目录

搜索
搜索通过使用 find() 方法来运行。

例一 搜索全部索引

view plaincopy to clipboardprint?
01.$hits = $index->find($query);  
02.foreach ($hits as $hit) {  
03.    printf("%d %f %s\n", $hit->id, $hit->score, $hit->title);  
04.} 
$hits = $index->find($query);
foreach ($hits as $hit) {
    printf("%d %f %s\n", $hit->id, $hit->score, $hit->title);
}

这个例子展示了两个特殊搜索 hit 属性 -- id 和 score 的用法。

id 是在一个 Lucene 索引内使用的一个内置的文件标识符。它可以被用来执行许多操作,包括从索引删除一个文件。

例二 删除一个已经被索引的文件

view plaincopy to clipboardprint?
01.$index->delete($id); 
$index->delete($id);

或者从索引中检索文件:

view plaincopy to clipboardprint?
01.$doc = $index->getDocument($id); 
$doc = $index->getDocument($id);

注意:内置文件标识符
重要提示!内置的文件标识符可以通过索引优化或者自动优化进程改变,但是它在一个单一的脚本执行过程中它不会改变,除非 addDocument()(可能包括一个自动拿破仑过程)或者 optimize() 方法被调用。

score 字段是一个命中得分。默认的,搜索结果将按照 score 排序(最好的结果首先返回)。

按照特定的字段值来给结果排序也是可以的。查看 Zend_Search_Lucene 文档来获得更多信息。

这个例子也展示了一个能力,访问储存字段的能力(例如,$hit->title)。在第一次访问非 id 或者 score 的 hit 属性的时候,文件存储字段被加载,然后返回相应的字段值。

这引起一个歧义,对于拥有自己 id 或者 score 字段的文件而言;因此,不建议在存储文件内使用这些字段名。尽管如此,仍然可以通过 getDocument() 方法来访问他们:

例四 访问文件内的原始 id 和 score 字段

view plaincopy to clipboardprint?
01.$id    = $hit->getDocument()->id;   
02.$score = $hit->getDocument()->score;  
03.   
$id    = $hit->getDocument()->id;
$score = $hit->getDocument()->score;
  

返回目录

支持的查询
Zend_Search_Lucene 和 Jave Lucene 支持一个强有力的查询语言。它允许搜索单独的词组,短语,词组范围;使用外卡和模糊搜索,使用布尔值操作符来合并查询,诸如此类。

更详细的查询语言资料可以在 Zend_Search_Lucene 组件文档中找到。

下面是一些普通查询类型和策略的例子。

例一 查询一个单独的字

hello

在全部文件字段中搜索 hello 这个单词

注意:默认搜索字段
重要记号!Jave Lucene 默认的只搜索全部的 contents 字段,但是 Zend_Search_Lucene 搜索全部的字段。这个行为可以使用 Zend_Search_Lucene::setDefaultSearchField($fieldName) 方法来改变。

例二 查询多个字

hello dolly

搜索两个单词,两个单词都是可选的,结果必须出现两个单词中的一个。

例三 在一个查询中要求单词

+hello dolly

搜索两个单词,hello 是必须的,dolly 是可选的

例四 在查询的文件中禁止单词

+hello -dolly

搜索两个单词,hello 是必须的,dolly 是禁止的。也就是说,如果文件匹配 hello,但是包含 dolly 这个单词,它不会返回在搜索结果中。

例五 查询短语

"hello dolly"

搜索 hello dolly 这个短语;只有这个短语的文件才会匹配。

例六 在特殊的字段中查询

title:"The Right Way" AND text:go

在标题(title)内搜索 The Right Way 这个短语,和在正文(text)内出现的 word 这个单词。

例七 在特殊字段内搜索同时整个文件

title:"The Right Way" AND go

在标题内搜索 The Right Way 这个短语,同时在文件内任何字段中出现的 word 这个单词。

例八 在特殊字段内和整个文件内搜索(可选择)

title:Do it right

在标题字段内搜索 Do,同时在全部字段内搜索 it 和 right;与之任何一个匹配的文件将返回结果。

例九 使用通配符 ? 查询

te?t

搜索匹配 te?t 模式的单词,? 可以是任何一个字符。

例十 使用通配符 * 查询

test*

搜索匹配 test* 的单词,* 是任何的0或多个字母序列。

例十一 查询一个短语包括的范围

mod_date:[20020101 TO 20030101]

搜索一个短语的范围(包括在内的)

例十二 搜索一个短语之外的范围

title:{Aido to Carmen}

搜索一个短语的范围(排除在外的)

例十三 模糊搜索

roam~

模糊搜索 roam 单词

例十四 布尔搜索

(framework OR library) AND php

布尔搜索

所有的搜索可以通过 Zend_Search_Lucene 的 query construction API 来构建。还有,查询解析和查询构建可以结合:

例十五 合并查询解析和构建

view plaincopy to clipboardprint?
01.$userQuery = Zend_Search_Lucene_Search_QueryParser::parse($queryStr);   
02.$query = new Zend_Search_Lucene_Search_Query_Boolean();  
03.$query->addSubquery($userQuery, true /* required */ 
04.$query->addSubquery($constructedQuery, true /* required */); 
$userQuery = Zend_Search_Lucene_Search_QueryParser::parse($queryStr);
$query = new Zend_Search_Lucene_Search_Query_Boolean();
$query->addSubquery($userQuery, true /* required */
$query->addSubquery($constructedQuery, true /* required */);

返回目录

搜索结果分页
在之前提到的,搜索结果命中对象使用懒惰装载来储存文件字段。当任何被储存的字段被访问,整个文件将会被装载。

如果你只想对某些文件进行操作,不要检索全部的文件。

view plaincopy to clipboardprint?
01.$cacheId = md5($query);  
02. if (!$resultSet = $cache->load($cacheId)) {  
03.     $hits = $index->find($query);  
04.     $resultSet = array();  
05.     foreach ($hits as $hit) {  
06.         $resultSetEntry          = array();  
07.         $resultSetEntry['id']    = $hit->id;  
08.         $resultSetEntry['score'] = $hit->score;  
09.   
10.         $resultSet[] = $resultSetEntry;  
11.     }  
12.   
13.     $cache->save($resultSet, $cacheId);  
14. }  
15.   
16. $publishedResultSet = array();  
17. for ($resultId = $startId; $resultId < $endId; $resultId++) {  
18.     $publishedResultSet[$resultId] = array(  
19.         'id'    => $resultSet[$resultId]['id'],  
20.         'score' => $resultSet[$resultId]['score'],  
21.         'doc'   => $index->getDocument($resultSet[$resultId]['id']),  
22.     );  
23. }  

posted on 2010-09-20 14:17  Dufe王彬  阅读(254)  评论(0编辑  收藏  举报