日本語全文検索(Google App Engine Search API)
日本語で全文検索可能なSearch APIについて説明します。
実際に動くサーブレットと運用まで
はじめに
すでに登録しているデーターを検索できるわけではありません。
別途 検索データ(インデックス)を作成して、そのデーターを検索します。
Search APIにかかる料金
公式サイトのSearch API Price(2014.01.31版)
ドル円100円で計算 |
Free(無料枠) 1日当たり | Free(無料枠) 月間 | 料金 1日当たり | 料金 月間 | 100円でできること |
---|---|---|---|---|---|
基本操作(Other API Calls) | 1千回 | 1万回につき10円 | 10万回の基本操作(1日当たり) | ||
ドキュメントの追加(Adding documents to Indexes) | 10MB | 1GBにつき200円 | 500MBの送信 | ||
ドキュメントの保存容量(Total Storage) | 250MB | 1GBにつき18円 | 約5.5GBの保存(月間) | ||
複雑な検索(Simple Queries) | 100回 | 1万回につき60円 | 約1万6千回の複雑な検索 | ||
簡単な検索(Complex Queries) | 1千回 | 1万回につき13円 | 約7万6千回の簡単な検索 |
基本操作
ほんんど何をやっても1回は発生します。 あと、送信と削除をすると、ドキュメントの数だけ発生します。
ドキュメントの送信
インデックスにPUTすると発生します。
元のテキストの大きさは関係なく、テキストの内容で容量は変化します。
間違ってGWTファイルを送るとひどい目にあります。 あとHTMLをsetText()として登録してもインデックスが肥大化します。
ただし、毎日リセットされます。蓄積されません。
初期の大量データーを登録時にのみ、数十円の出費は仕方ありません。
また登録データを消したからと言って減るものではありません。減るのはドキュメントの保存容量
の方です。
ドキュメントの保存容量
元のドキュメントと、作成されたインデックスの双方の容量です。
複雑な検索
未テスト
簡単な検索
Queryもlimitぐらいなら、簡単な検索で収まりました。
無料枠内で運用できるか?
アクセスが1日に1千回のサイト(無料枠で運用できるウェブサイト・サービス)で、検索が1千回超えることはないだろう。 更新内容も数十件程度だし、ドキュメントの送信も問題にならないでしょう。
ただし、初めて使う際にホームページの内容をすべて登録する際に、無料枠を超える可能性が高い。(HTML100ページ 700kbぐらいでも、10M以上になることもある)
費用は、数十円程度だが、有料化していないと、面倒だ。
HTMLを間違ってテキストとして登録するだけで、軽く無料枠を超えることもある。
(元のテキストが小さくでも、インデックスに対応したデーター量は数十倍に膨れ上がる)
元のテキスト
My name is aki.
インデックスに登録後(想像)
My
My name
My name is
name is
name is aki
...中略
is
aki.
使い方
注意事項
一度作成したSearch-Indexを削除する方法は無さそうです。 (将来修正されるでしょう:stackoverflow質問 および 公式issueを確認してみてください。)
無駄に作りまくったり、変な名前付けると悲しい目に合うでしょう。
その他にも、公式issueが多々あります。
テキストの登録 テキストはドキュメントクラスを作成して登録します。
ドキュメントクラスはインデックスクラスに追加します。 手順は
- インデックスの取得
- ドキュメントの作成
- ドキュメントの追加
インデックスの取得
登録するためのIndexを取得します。
IndexSpec indexSpec = IndexSpec.newBuilder().setName(HTML_DB).build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
ドキュメントの作成
DocumentBuilderで作成します。 ドキュメントにはユニークなID((IDはアスキーのみ)および、項目フィールドを作成します。 項目フィールドには、TextあるいはHTML(タグは無視)として設定します。
Document document = Document.newBuilder().setId(entity.getPath()).addField(Field.newBuilder().setName("text").setHTML(text))
.addField(Field.newBuilder().setName("title").setText(title)).build();
ドキュメントの追加
同じIDがあれば上書きされます
この時に、indexing documents/Adding documents to Indexes
が発生します。
try {
index.put(document);
} catch (PutException e) {
if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())) {
System.out.println("TRANSIENT_ERROR"); } }
全文検索
- インデックスを取り出す。
- 検索する
- 返って来たドキュメントを処理する
インデックスを取り出す
Index index=SearchServiceFactory.getSearchService().getIndex(IndexSpec.newBuilder().setName(HTML_DB).build());
検索する
単純な検索な場合、文字を渡すだけでいいです。
ただ内部で処理される文字がいくつかあるようです。
Results<ScoredDocument> result= index.search(query);
ドキュメントを処理する
返って来たリザルトから、ドキュメントを処理して出力内容を作成します。
Results<ScoredDocument> result= index.search(query);
String ret="search="+query+"<hr/>";
for(ScoredDocument doc:result.getResults()){
String title=doc.getOnlyField("title").getText();
String id=doc.getId();
String alink="<a href='"+id+"'>"+title+"</a>"; ret+=alink+"<br/>";
}
登録ドキュメントの削除
まとめて削除する方法はありません。 一つづつ削除します。
インデックスを取得します。
IndexSpec indexSpec = IndexSpec.newBuilder().setName(HTML_DB).build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
すべて消す場合。検索して取り出します。
QueryOptions options = QueryOptions.newBuilder() .setLimit(300).setReturningIdsOnly(true).build();
com.google.appengine.api.search.Query query = com.google.appengine.api.search.Query.newBuilder() .setOptions(options) .build("");
Results<ScoredDocument> result =index.search(query);
IDを渡して消します。IDがわかっている場合、requestからidを取り出して、そのままIndexに渡してもいい。
for(ScoredDocument doc:result){
index.delete(doc.getId());
}
実例サーブレット
Google App Engineで全文検索サーブレットの例 - 公式のサンプル見ずに作ったのでIndexをstatic化してないなど欠陥が多少有ります。いずれ修正します。
運用
処理はそこそこ重いので、Entityを更新した時にではなく Backendsサービスをcronから数時間おきに呼び出して、まとめてインデックスを更新します。 処理内容としては
- 前回の更新日時を調べる
- それ以降に追加されたコンテンツを取り出す。
- それらをまとめて更新する
- 現在の時間を保存する
具体例は、Google App Engineで全文検索サーブレットの例 のdoUpdate()
エラー
これまでに経験したエラー内容を列挙しておきます。
いずれ改善策も示したいです。
java.lang.IllegalArgumentException: Document id must be ASCII visible printable:
ドキュメントのidに日本語が有るとき
com.google.appengine.api.search.SearchQueryException: Unable to parse query:
<
から検索のqueryに渡すテキストが始まる時
com.google.apphosting.api.ApiProxy$OverQuotaException: The API call search.IndexDocument() required more quota than is available.
Quotaを超えて規制状態になっている時に、ドキュメントをputした時