Elasticsearch で法人名をサジェストする
Leaner Technologies の小松(@nomnel)です。
先日、Elasticsearch (+ elasticsearch-rails)を使って、Leaner 見積に法人名の入力補完機能を実装しました。
この記事ではどのように実装したかについて書きます。
ですが、Elasticsearch ほとんど分かってない状態から実装を始めたこともあり、今でも分かっていないことが多々あります(まとめの項を参照)。
識者にはぜひ突っ込んでいただきたい…あわよくば一緒に働きたい…!
機能の概要
Google のクエリサジェスト機能のような法人名の入力補完機能を実装します。
以下のような条件で検索し、結果の上位 10 件をサジェストとして表示します。
- 法人番号の完全一致
- 名称の部分一致
- 英語名称の部分一致
- 読み仮名の部分一致
- 入力途中でも検索したい(ex. 「りーn」で「リーナーテクノロジーズ」にヒットさせたい)
- 入力途中でも検索したい(ex. 「りーn」で「リーナーテクノロジーズ」にヒットさせたい)
実装
マッピング
ここでは名称と読み仮名の検索について取り上げます。
名称
以下の 2 つのフィールドを定義しました。
mappings dynamic: false do
indexes :name, type: 'text', analyzer: 'name_analyzer' do
indexes :ngram, type: 'text', analyzer: 'name_ngram_analyzer'
end
end
違いは tokenizer に kuromoji_tokenizer か ngram のどちらを使っているかです。
analyzer: {
name_analyzer: {
type: 'custom',
tokenizer: 'kuromoji_tokenizer',
char_filter: %w(icu_normalizer kuromoji_iteration_mark katakana),
filter: %w(ja_stop kuromoji_number kuromoji_stemmer),
},
name_ngram_analyzer: {
type: 'custom',
tokenizer: 'ja_ngram_tokenizer',
char_filter: %w(icu_normalizer kuromoji_iteration_mark katakana),
filter: %w(ja_stop kuromoji_number kuromoji_stemmer),
},
},
char_filter の katakana では正規化のためにひらがなをカタカナに変換しています。
char_filter: {
katakana: {
type: 'mapping',
mappings: HIRAGANA_TO_KATAKANA_MAPPING,
},
},
HIRAGANA_TO_KATAKANA_MAPPING はこのように列挙しています。
HIRAGANA_TO_KATAKANA_MAPPING = [
'ぁ=>ァ',
'ぃ=>ィ',
# 中略
].freeze
読み仮名
名称と同様に 2 つのフィールドも定義しました。
mappings dynamic: false do
indexes :kana, type: 'text', analyzer: 'kana_index_analyzer', search_analyzer: 'kana_search_analyzer' do
indexes :ngram, type: 'text', analyzer: 'name_ngram_analyzer'
end
end
インデックス時に edge_ngram filter を使うために、インデックス時と検索時の analyzer を分けています。
analyzer: {
kana_index_analyzer: {
type: 'custom',
char_filter: %w(icu_normalizer katakana romaji),
tokenizer: 'ja_normal_tokenizer',
filter: %w(
lowercase
trim
maxlength
romaji_readingform
asciifolding
edge_ngram
),
},
kana_search_analyzer: {
type: 'custom',
char_filter: %w(icu_normalizer katakana romaji),
tokenizer: 'ja_normal_tokenizer',
filter: %w(
lowercase
trim
maxlength
romaji_readingform
asciifolding
),
},
},
入力途中のローマ字が含まれているクエリ(ex. 「リーナー」の途中の「りーn」)でもヒットさせるために、char_filter でローマ字に正規化しています。
char_filter: {
# katakana は前掲のため省略
romaji: {
type: 'mapping',
mappings: KATAKANA_TO_ROMAJI_MAPPING,
},
},
KATAKANA_TO_ROMAJI_MAPPING も HIRAGANA_TO_KATAKANA_MAPPING と同様に列挙しています。
KATAKANA_TO_ROMAJI_MAPPING = [
'キャ=>kya',
# 中略
].freeze
クエリー
重み付けなどは行っていない、シンプルなクエリです。
query: {
bool: {
should: [
{ match: { kana: { query: query, fuzziness: 'AUTO', operator: 'and' } } },
{ match: { 'kana.ngram' => { query: query } } },
{ match: { name: { query: query } } },
{ match: { 'name.ngram' => { query: query } } },
# 英語名称と法人番号は省略
],
},
},
まとめ
ここまで、さも分かっているかのように書いてきましたが、最初に述べたとおりまだまだ分かっていないことだらけです。
具体的には、「リーナー」で「株式会社Leaner Technologies」がサジェストに含まれてこない問題が発生しています…!自社なのに…どうして…
クエストリストにも追加しましたので、原因 or 対応分かるよーという方からのご連絡お待ちしております。何卒…!
Leaner クエストリスト|プロダクト開発 β 版
また、もっとがっつり良くしたいと思ってくださる方は入社もお待ちしております!よろしくお願いします!
Leaner 採用情報
参考にした記事
これら以外にも沢山の記事を読みましたが、これらの記事には特にお世話になりました。ありがとうございました。
Discussion