このエントリーをはてなブックマークに追加

日本語で全文検索可能な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が多々あります。

テキストの登録 テキストはドキュメントクラスを作成して登録します。

ドキュメントクラスはインデックスクラスに追加します。 手順は

  1. インデックスの取得
  2. ドキュメントの作成
  3. ドキュメントの追加

インデックスの取得

登録するための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"); } }

全文検索

  1. インデックスを取り出す。
  2. 検索する
  3. 返って来たドキュメントを処理する

インデックスを取り出す

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化してないなど欠陥が多少有ります。いずれ修正します。

Google公式のサンプル

運用

処理はそこそこ重いので、Entityを更新した時にではなく Backendsサービスをcronから数時間おきに呼び出して、まとめてインデックスを更新します。 処理内容としては

  1. 前回の更新日時を調べる
  2. それ以降に追加されたコンテンツを取り出す。
  3. それらをまとめて更新する
  4. 現在の時間を保存する

具体例は、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した時