Zend_Search で遊んでみた

Zend_Search は、PHPフレームワークZend Framework」に含まれているサーチエンジンです。
インデックスファイルが、Javaで実装された「Lucene」とバイナリ互換になっているそうです。
最初、JavaLucene と連携が必要なのかと思ってましたが、そんなことはなくて、完全に PHP のコードのみで動作します。

しかし、今のところ日本語には対応していないので、がっかりだったわけですが、一応いろいろやってみたメモを残してみます。

ライブラリとしての使用

Zend_Search は「Zend_Framework」の一部分な訳ですが、単独でライブラリ的に使用することも可能です。というか、簡単です。

今回は、PHP フレームワークSymfony」で開発中のプロジェクトに組み込んでみました。
必要な作業は、

  • Zend Framework 全体をダウンロード
  • $SF_PROJECT/lib/Zend を作成
  • Zend.php を $SF_PROJECT/lib にコピー
  • Zend/Exception.php を $SF_PROJECT/lib/Zend にコピー
  • Zend/Search を $SF_PROJECT/lib/Zend にコピー

これだけです(Spin Drop -を参考にしてます)。

あとは、こんな感じでインデックスを作成(今回は、バッチスクリプトを作成してターミナルから実行しました)。

// インデックスファイルを開く
$index = new Zend_Search_Lucene(sfConfig::get('app_search_artist_index_file'), true);

foreach (ドキュメント毎に繰り返す)
{
 // ドキュメントオブジェクトに検索用のキーをセット
 $doc = new Zend_Search_Lucene_Document();
 $doc->addField(Zend_Search_Lucene_Field::Keyword('artist_id', $artist->getId()));

 $doc->addField(Zend_Search_Lucene_Field::Text('name', $artist->getName()));

 // ドキュメントをインデックスに追加
 $index->addDocument($doc);
}

// インデックスを更新
$index->commit();

検索を行う場合は、例えば action の中で

public function executeSearch()
 {
   require_once('Zend/Search/Lucene.php');
   $query = $this->getRequestParameter('query');
 
   // インデックスを開く   
   $index = new Zend_Search_Lucene(sfConfig::get('sf_root_dir') . "/" . sfConfig::get('app_search_artist_index_file'));
    
   // 検索実行
   $this->search_results = $index->find($query);
 }

として、template で

<ul>
<?php foreach ($search_results as $hit): ?>
<li>
スコア: <?php echo $hit->score ?> /  
名前: <?php echo link_to($hit->name, 'artist/show?id='.$hit->artist_id) ?> / 
</li>
<?php endforeach ?>
</ul>

という感じで結果を出力します。簡単。

日本語対応の可能性を探る(途中経過)

ただ、このままでは日本語の検索ができないので、あれこれやってみました。

Zend_Search には、独自のテキスト→トークン化コードを登録仕組みがあるので、これを使って日本語対応のアナライザーを作成してみます。
まず、インデックス作成時に日本語のテキストを分かち書きしないといけないという、おなじみの問題があります。ここはひとまず、mecab を使うことにします。PHP から mecab を使うための拡張を提供されている方がいらっしゃったので、インストール。

デフォルトのアナライザーを参考に、分かち書きされた要素を Zend_Search_Lucene_Analisis_Token として登録してみたのですが、どうやらさらに深い部分でも日本語を含むマルチバイト文字列に対応していないところがあるようで、うまく動作しませんでいた。

他にも、検索実行時のクエリ解析部分もASCIIキャラクタを前提としているようで、修正が必要な模様。ざっと見たところ、strlen() や substr() などのマルチバイト対応してない文字列操作関数が各所で使われている(これは、mb_string 系に置き換えればOK?)。あとは、テキストの解析に ctype_alpha() を使用するけどこれがマルチバイト対応してないとか。

なんとなく、ばっさり改善できそうにも思いますが、いったん放置。

その他

インデックス作成や検索処理のスピードは検証できてませんが、PHP のみの実装ということで、プロジェクトに組み込みやすいことが魅力です。
すでに最終段階の某プロジェクトでは Symfony + MySQL with Senna で検索を実現してますが、MySQL にパッチをあてるのが、(実作業じゃなくて運用の総合コストが)結構面倒だったりしたので。
また挑戦したいと思います。