Eclipseやantを使わないLucene入門
後輩に煽られたのでLuceneを使えるようにしてみた.ようやく積ん読になっていた "Lucene in Action" がついに火を噴くときがきた模様.
長らくJavaは触っていなかったけれど,JavaライブラリってEclipseのようなIDE使わないとimport地獄にはまったり,ant使わないとそもそもコンパイルできなかったりと,なかなかゆとりには厳しい印象がある.実際,前回のチャレンジではそれで挫折してLuceneが嫌いになった.
今回はantやIDEに頼らずにメモ帳プログラムでLuceneを動かしてみた.
やってみるとハマるところはあるものの,とても簡単だったので備忘録程度にメモ.コードはLucene in Actionの(pp.20-25)あたりのサンプルコードを参考にした.
インストールと準備
まず準備から.最新のlucene-3.1.0の例で説明.
インデクス構築
基本的には難しいことを考えず,
- 各文書はdocid,bodyの情報を持つ
- トークン化は特に考えない (今回はスペース区切り)
という検索インデクス構築と,そのインデクスに対する検索するサンプルコードを書いた.文書追加部分を適当なハードコーディングしているけれど,この部分を好きなファイルから読み込めば,自分の用途に合ったインデクスを構築することができる.
import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.analysis.WhitespaceAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import java.io.File; class HogeIndex { public static void main (String [] args) throws Exception { // Directory directory = new RAMDirectory(); Directory directory = FSDirectory.open(new File("./index")); IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED); Document doc1 = new Document(); doc1.add(new Field("id", "1", Field.Store.YES, Field.Index.NOT_ANALYZED)); doc1.add(new Field("body", "hoge fuga bar piyo", Field.Store.YES, Field.Index.ANALYZED)); writer.addDocument(doc1); Document doc2 = new Document(); doc2.add(new Field("id", "2", Field.Store.YES, Field.Index.NOT_ANALYZED)); doc2.add(new Field("body", "hoge あひゃひゃひゃ ほげ", Field.Store.YES, Field.Index.ANALYZED)); writer.addDocument(doc2); writer.close(); } }
- 何をimportすればよいのかということは,勉強も兼ねてLucene JavaDocを眺めて手打ちした.Eclipseの自動補完などを使うのがよいと思われる.
- FSDirectoryはファイルシステム上にインデクスを作成するDirectoryクラス
- IndexWriterクラスがインデクス構築を行う
- Documentは1個以上のFieldを持つ
- Fieldは,Field名と値をStringで持つ.それ以降の引数の意味は以下のとおり
- Field.Store.YES => インデクスにフィールドの値を保持する
- Field.Index.NOT_ANALYZED => トークナイズなどを行わない
- Field.Index.ANALYZED => IndexWriteにセットされたAnalyzerによって処理
- 超適当なイメージ
- Field < Document < Directory
検索
検索はこんな感じ.
import org.apache.lucene.util.Version; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.Query; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.analysis.WhitespaceAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import java.io.File; class HogeSearch { public static void main (String [] args) throws Exception { Directory dir = FSDirectory.open(new File("./index")); IndexSearcher is = new IndexSearcher(dir); QueryParser parser = new QueryParser(Version.LUCENE_30, "body", new WhitespaceAnalyzer()); Query query1 = parser.parse("hoge"); //Query query = parser.parse("hoge piyo"); //Query query = parser.parse("hoge fuga"); TopDocs docs1 = is.search(query1, 10); System.out.println("Found " + docs1.totalHits + " docs."); for (ScoreDoc scoreDoc : docs1.scoreDocs) { Document doc = is.doc(scoreDoc.doc); System.out.println(doc.get("id")); System.out.println(doc.get("body")); } Query query2 = parser.parse("ほげ"); TopDocs docs2 = is.search(query2, 10); System.out.println("Found " + docs2.totalHits + " docs."); for (ScoreDoc scoreDoc : docs2.scoreDocs) { Document doc = is.doc(scoreDoc.doc); System.out.println(doc.get("id")); System.out.println(doc.get("body")); } is.close(); } }
- Directoryのオープンはインデクス構築と一緒.
- 検索するのはIndexSearcherクラス
- クエリをパースするためにQueryParserクラスを用いる
- いきなりQueryTermクラスでクエリを作成してもいいけれど,インデクス構築に用いられたAnalyzerと検索に用いるAnalyzerが一致しないと,不整合を起こして検索結果を適切に取得できなくなることがある
- 用意したQueryParserインスタンスを通してQueryインスタンスを取得
- 予備検証で"hoge piyo"がきちんとパースされて検索されることを確認
- IndexSearcher.search()で検索.クエリと取得検索結果数を入力.
- 検索結果は TopDocs で返される (名称が行けてないと思うのは僕だけ?)
- TopDocs.totalHits => 検索結果件数
- TopDocs.scoreDocs => 検索結果の配列 ScoreDoc[]
- 個々の検索結果はScoreDocに格納されている
- IndexSearcher.doc(scoreDoc.doc) でDocumentを取得
- doc.get("xx") でフィールドxxの値を取得
実行
% javac HogeIndex.java % java HogeIndex % javac HogeSearch.java % java HogeSearch Found 2 docs. 1 hoge fuga bar piyo 2 hoge あひゃひゃひゃ ほげ Found 1 docs. 2 hoge あひゃひゃひゃ ほげ
ちゃんと動いていることを確認.Documentの重複は自動的にチェックしてくれないので,HogeIndexを2回実行すると,結果が2倍出力される
まとめ
2年くらい前にLuceneを触ってみたときは,イメージがつかなかったのですぐに挫折したものの,今回はLucene in Actionを読んでイメージが大分ついたので,簡単におもちゃコードを書くことができた.
イメージをつけることとサンプルコードがあるのは本当に大事だなぁと実感.
参考にしたのは,下記のLucen in Action 2nd edition.本書を購入すると電子版をダウンロードする権利もついてくるので (少なくとも僕の手元にあるものはそうなっている),紙の本と電子書籍両方を同時に手に入るのでおすすめ.
Lucene in Action: Covers Apache Lucene v.3.0
- 作者: Michael McCandless,Erik Hatcher,Otis Gospodnetic
- 出版社/メーカー: Manning Publications
- 発売日: 2010/06/30
- メディア: ペーパーバック
- 購入: 1人 クリック: 10回
- この商品を含むブログ (3件) を見る
TODO
- deprecated methodを除外する
- ややこしいimportをEmacs上でもなんとかできないか
- いろんなAnalyzerを試す
- CJKTokenizerでbigramができるっぽい
- 簡単な検索プログラムを書く