🔍

Elasticsearch で法人名をサジェストする

2021/06/24に公開

Leaner Technologies の小松(@nomnel)です。

先日、Elasticsearch (+ elasticsearch-rails)を使って、Leaner 見積に法人名の入力補完機能を実装しました。
この記事ではどのように実装したかについて書きます。

ですが、Elasticsearch ほとんど分かってない状態から実装を始めたこともあり、今でも分かっていないことが多々あります(まとめの項を参照)。
識者にはぜひ突っ込んでいただきたい…あわよくば一緒に働きたい…!

機能の概要

Google のクエリサジェスト機能のような法人名の入力補完機能を実装します。
以下のような条件で検索し、結果の上位 10 件をサジェストとして表示します。

  • 法人番号の完全一致
  • 名称の部分一致
  • 英語名称の部分一致
  • 読み仮名の部分一致
    • 入力途中でも検索したい(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