Symfoware

Symfowareについての考察blog

Elasticsearch 日本語データの分かち書きと検索(kuromoji使用)

ElasticsearchをDebianにインストール。
Pythonから操作してみました。

Elasticsearch 1.4をDebian 7にインストールする
Elasticsearch 1.4にPythonから接続する

日本語ドキュメントを登録して検索してみます。


検索用のデータ



こちらで登録した郵便番号データを検索してみます。
Elasticsearch + Pythonでバルクインサート(helpers.bulk)

登録用のソースはこんな感じでした。


  1. # -*- coding:utf-8 -*-
  2. import zipfile
  3. import cStringIO
  4. from elasticsearch import Elasticsearch
  5. from elasticsearch import helpers
  6. def read_all():
  7.     
  8.     """ 郵便番号辞書の読み込み """
  9.     with zipfile.ZipFile('ken_all.zip', 'r') as post:
  10.         
  11.         # KEN_ALL.CSVの内容を取得し、StringIOでくるむ
  12.         f = cStringIO.StringIO(post.read('KEN_ALL.CSV'))
  13.         
  14.         for line in f:
  15.             # ms932からunicodeオブジェクトに変換
  16.             ary = unicode(line, 'ms932').strip().split(',')
  17.             ary = [item.replace('"', '') for item in ary]
  18.             
  19.             zipcode = ary[2]
  20.             address = ary[6] + ary[7] + ary[8]
  21.             
  22.             yield (zipcode, address)
  23. def main():
  24.     
  25.     # コネクション確率
  26.     es = Elasticsearch(host='192.168.1.103', port=9200)
  27.     
  28.     # bulkに渡すリスト
  29.     actions = []
  30.     
  31.     for zipcode, address in read_all():
  32.         
  33.         doc = {'zipcode':zipcode, 'address':address}
  34.         
  35.         # index => _index、doc_type => _type、body => _sourceに読み替えてセット
  36.         actions.append({'_index':'test-index', '_type':'zip-code', '_source':doc})
  37.         
  38.         # 1000件たまったらbulk
  39.         if len(actions) > 1000:
  40.             helpers.bulk(es, actions)
  41.             actions = []
  42.             
  43.     
  44.     # 残っているデータを登録
  45.     if len(actions) > 0:
  46.         helpers.bulk(es, actions)
  47.     
  48. if __name__ == '__main__':
  49.     main()








郵便番号検索



検索の方法を調べるため、まずは郵便番号で検索してみます。


  1. # -*- coding:utf-8 -*-
  2. from elasticsearch import Elasticsearch
  3. from elasticsearch import helpers
  4. def main():
  5.     
  6.     # コネクション確率
  7.     es = Elasticsearch(host='192.168.1.103', port=9200)
  8.     
  9.     res = es.search(index='test-index', body={'query': {'match': {'zipcode':'0600041'}}})
  10.     print("Got %d Hits:" % res['hits']['total'])
  11.     # 取得したデータを表示
  12.     for hit in res['hits']['hits']:
  13.         print("%(zipcode)s %(address)s" % hit["_source"])
  14.         
  15.         
  16. if __name__ == '__main__':
  17.     main()





ちゃんと検索できてますね。


$ python sample.py
Got 1 Hits:
0600041 北海道札幌市中央区大通東







住所検索




「銀座」という文字を含むデータを検索してみます。


  1. # -*- coding:utf-8 -*-
  2. from elasticsearch import Elasticsearch
  3. from elasticsearch import helpers
  4. def main():
  5.     
  6.     # コネクション確率
  7.     es = Elasticsearch(host='192.168.1.103', port=9200)
  8.     
  9.     res = es.search(index='test-index', body={'query': {'match': {'address':'銀座'}}}, size=30)
  10.     print("Got %d Hits:" % res['hits']['total'])
  11.     # 取得したデータを表示
  12.     for hit in res['hits']['hits']:
  13.         print("%(zipcode)s %(address)s" % hit["_source"])
  14.         
  15.         
  16. if __name__ == '__main__':
  17.     main()





279件も結果が見つかりました。


$ python sample.py
Got 279 Hits:
3600032 埼玉県熊谷市銀座
3940022 長野県岡谷市銀座
3950031 長野県飯田市銀座
9300991 富山県富山市新庄銀座
4750874 愛知県半田市銀座本町
4480845 愛知県刈谷市銀座
7450032 山口県周南市銀座
3220052 栃木県鹿沼市銀座
3940023 長野県岡谷市東銀座
4140028 静岡県伊東市銀座元町
5220088 滋賀県彦根市銀座町
3670052 埼玉県本庄市銀座
1040061 東京都中央区銀座
4130013 静岡県熱海市銀座町
7700916 徳島県徳島市銀座
6128089 京都府京都市伏見区銀座町
4240817 静岡県静岡市清水区銀座
0691331 北海道夕張郡長沼町銀座
8040076 福岡県北九州市戸畑区銀座
7450033 山口県周南市みなみ銀座
2520027 神奈川県座間市座間
0368207 青森県弘前市上白銀町
3700825 群馬県高崎市白銀町
6260054 京都府宮津市銀丘
3520006 埼玉県新座市新座
0381341 青森県青森市浪岡銀
0310832 青森県八戸市白銀台
5008874 岐阜県岐阜市銀町
6700902 兵庫県姫路市白銀町
0310822 青森県八戸市白銀町






正解は、以下の20件なのに。


0691331 北海道夕張郡長沼町銀座
3220052 栃木県鹿沼市銀座
3600032 埼玉県熊谷市銀座
3670052 埼玉県本庄市銀座
1040061 東京都中央区銀座
9300991 富山県富山市新庄銀座
3940022 長野県岡谷市銀座
3940023 長野県岡谷市東銀座
3950031 長野県飯田市銀座
4240817 静岡県静岡市清水区銀座
4130013 静岡県熱海市銀座町
4140028 静岡県伊東市銀座元町
4750874 愛知県半田市銀座本町
4480845 愛知県刈谷市銀座
5220088 滋賀県彦根市銀座町
6128089 京都府京都市伏見区銀座町
7450032 山口県周南市銀座
7450033 山口県周南市みなみ銀座
7700916 徳島県徳島市銀座
8040076 福岡県北九州市戸畑区銀座









kuromojiによる分かち書き



正しく日本語データが検索できるよう、kuromojiで分かち書きするよう指定します。

kuromojiは、以前Solrで使ったことがあります。
Debian に Tomcat 7 + Apache Solr 3.5.0 + 日本語検索対応環境を構築(Kuromoji使用)


kuromojiのプラグインをインストール。
こちらを参考に、Elasticsearchのバージョンにあったものを選びます。
https://github.com/elasticsearch/elasticsearch-analysis-kuromoji



# cd /usr/share/elasticsearch
# bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/2.4.1




プラグインを反映させるため、リスタート。


# /etc/init.d/elasticsearch restart




※リスタートがミソです。
リスタートしていないと、mapping登録時にこんなエラーになります。
めっちゃはまりました。


{"error":"IndexCreationException[[test-index] failed to create index];
nested: ElasticsearchIllegalArgumentException[failed to find tokenizer type [kuromoji_tokenizer]
for [kuromoji_search]]; nested: NoClassSettingsException[Failed to load class setting [type]
with value [kuromoji_tokenizer]];
nested: ClassNotFoundException[org.elasticsearch.index.analysis.kuromojitokenizer.KuromojiTokenizerTokenizerFactory]; ",
"status":400}









mapping



「このフィールドに対して分かち書きを行ってね」という設定を行います。
Solrを触っていたので、なんとなく言ってることはわかるのですが、
こちらを参考に考えます。

実践!Elasticsearch
Elasticsearchチュートリアル

最低限必要な項目がよくわからず。
ならば、今の設定を見てみればいいかも。


  1. # -*- coding:utf-8 -*-
  2. from elasticsearch import Elasticsearch
  3. from elasticsearch import helpers
  4. from elasticsearch.client import IndicesClient
  5. def main():
  6.     
  7.     # コネクション確率
  8.     es = Elasticsearch(host='192.168.1.103', port=9200)
  9.     
  10.     # 設定されているmappinを表示
  11.     indices = IndicesClient(es)
  12.     print indices.get_mapping(index='test-index')
  13.     
  14.         
  15. if __name__ == '__main__':
  16.     main()




登録時、mappingは指定していないので、これは自動生成された設定になります。
「properties」という項目は必須のようです。

※適度に改行、コメントしてます。


$ python sample.py
{u'test-index':
{u'mappings':
{u'zip-code':

{u'properties': {
u'zipcode': {u'type': u'string'},
u'address': {u'type': u'string'}
}}

} # zip-code
} # mappings
} # test-index





試行錯誤して、こんな設定にしました。
curlコマンドで設定を叩き込みます。
※一回、test-indexを削除する必要があるかも。


curl -XPUT 'http://localhost:9200/test-index/' -d'
{
"settings": {
    "analysis": {
     "tokenizer": {
        "kuromoji_search": {
         "type": "kuromoji_tokenizer",
         "mode" : "search"
        }
     },
    
     "analyzer": {
        "my_analyzer": {
         "type": "custom",
         "tokenizer": "kuromoji_search"
        }
     }
    }
},


"mappings": {
    "zip-code": {
     "properties": {
        "zipcode": {
         "type": "string"
        },
        "address": {
         "type": "string",
         "index": "analyzed",
         "analyzer": "my_analyzer"
        }
     }
    }
}
}
'







settings部分から見てみます。


"settings": {
    "analysis": {
     "tokenizer": {
        "kuromoji_search": {
         "type": "kuromoji_tokenizer",
         "mode" : "search"
        }
     },
    
     "analyzer": {
        "my_analyzer": {
         "type": "custom",
         "tokenizer": "kuromoji_search"
        }
     }
    }
},




settings-analysisで解析に使用するオブジェクトの指定を行います。

tokenizer:文字を分割する機構を指定。

「kuromoji_search」は私が適当につけた名前です。
「type」で、kuromoji_tokenizerを指定します。これはこの文字列である必要があります。
「mode」はkuromoji_tokenizerに渡すオプションです。

https://github.com/elasticsearch/elasticsearch-analysis-kuromoji
ここのDifference tokenization mode outputsを参考にしました。


analyzerで、フィールドに指定する名前を決めます。

「my_analyzer」は私が適当につけた名前です。
「type」をcustom
「tokenizer」には上記で指定した「kuromoji_search」を指定します。



次にmappings部分です。



"mappings": {
    "zip-code": {
     "properties": {
        "zipcode": {
         "type": "string"
        },
        "address": {
         "type": "string",
         "index": "analyzed",
         "analyzer": "my_analyzer"
        }
     }
    }
}




mappingsが、フィールド名と各フィールドの属性指定です。
「zipcode」は文字列。
「address」も文字列ですが、indexをanalyzedに、インデックス作成を指示します。
解析には、settingsで作成した「my_analyzer」を指定します。



これでデータを再投入
(試行錯誤している段階で消してしまいました。データがある状態でも反映されるかも)


再度、検索プログラムを実行してみます。


  1. # -*- coding:utf-8 -*-
  2. from elasticsearch import Elasticsearch
  3. from elasticsearch import helpers
  4. def main():
  5.     
  6.     # コネクション確率
  7.     es = Elasticsearch(host='192.168.1.103', port=9200)
  8.     
  9.     res = es.search(index='test-index', body={'query': {'match': {'address':'銀座'}}}, size=30)
  10.     print("Got %d Hits:" % res['hits']['total'])
  11.     # 取得したデータを表示
  12.     for hit in res['hits']['hits']:
  13.         print("%(zipcode)s %(address)s" % hit["_source"])
  14.         
  15.         
  16. if __name__ == '__main__':
  17.     main()




狙い通り、20件の検索結果が得られました。長かった。


$ python sample.py
Got 20 Hits:
3670052 埼玉県本庄市銀座
7700916 徳島県徳島市銀座
3220052 栃木県鹿沼市銀座
5220088 滋賀県彦根市銀座町
9300991 富山県富山市新庄銀座
3940022 長野県岡谷市銀座
3950031 長野県飯田市銀座
4480845 愛知県刈谷市銀座
3600032 埼玉県熊谷市銀座
1040061 東京都中央区銀座
7450032 山口県周南市銀座
4140028 静岡県伊東市銀座元町
3940023 長野県岡谷市東銀座
4130013 静岡県熱海市銀座町
0691331 北海道夕張郡長沼町銀座
4240817 静岡県静岡市清水区銀座
4750874 愛知県半田市銀座本町
7450033 山口県周南市みなみ銀座
8040076 福岡県北九州市戸畑区銀座
6128089 京都府京都市伏見区銀座町




関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2014/11/08(土) 20:15:24|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Elasticsearch Pythonでsettinsとmappingを指定する | ホーム | Elasticsearch + Pythonでバルクインサート(helpers.bulk)>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1543-831f17ff
この記事にトラックバックする(FC2ブログユーザー)