Elasticsearch での N-gram 検索 (Part 2) 医薬品マスタの検索

BLOG

1. 前書き

前回は、N-gram 検索の基礎的な内容について記載しました。

今回は、より実践的なサンプルアプリをご紹介いたします。
なお、今回のサンプルアプリは、下記の GitHub リポジトリで公開しています。

blogs/2025-05-ngram-search-part2 at main · sios-elastic-tech/blogs
A sample code for blogs about elasticsearch. Contribute to sios-elastic-tech/blogs development by creating an account on...

対象者

  • Elasticsearch の初心者~中級者

できるようになること

  • Elasticsearch で N-gram を使ったサンプルアプリを動かせるようになる。
  • Flask を使った簡単なアプリを動かせるようになる。

動作に必要な環境

  • Elasticsearch (クラウド版でもオンプレミス版でも可。筆者は、version: 8.18.0 のクラウド版で動作確認しました。)
  • Docker コンテナ を動かせる環境 (筆者は、Windows 版の Rancher Desktop 1.18.2 で動作確認しました。)

その他、下記は、自動でダウンロードされます。

  • Python 3.13
  • elasticsearch 8.18.1 (Elasticsearch の Python用のクライアント)
  • flask 3.1.0
  • python-dotenv 1.0.1

(2025年5月1日時点の情報を元に記載しています。)

2. CSVファイルの準備

今回のサンプルアプリでは、医薬品マスタの検索を行います。検索対象となる医薬品マスタを準備します。

2.1. 下記の URL から「医薬品マスター」をダウンロードします。

診療報酬情報提供サービス

2.2. y.zip がダウンロードされるので、それを展開します。

2.3. y_20250415.csv というファイルが展開されます(2025-04-15版の場合)。

2.4. y_20250415.csv ファイルを Excel や LibreOffice などで開きます。

2.5. 3列目と5列目のみを残し、それ以外の列を削除します。

2.6. 先頭に行を挿入します。

2.7. 先頭行の値に、”medicine_code”, “medicine_name” をそれぞれ入力します。

2.8. y_20250415_col2.csv という名前で保存します。

2.9. y_20250415_col2.csv ファイルをメモ帳などで開き、UTF-8 で保存します。その際、ファイル名を y_20250415_col2_utf8.csv とします。

なお、上記で作成した y_20250415_col2_utf8.csv ファイルは、下記のリポジトリでも公開しています。

blogs/2025-05-ngram-search-part2/csv at main · sios-elastic-tech/blogs
A sample code for blogs about elasticsearch. Contribute to sios-elastic-tech/blogs development by creating an account on...

3. Elasticsearch へのCSVファイルの登録

先ほど作成したCSVファイル(y_20250415_col2_utf8.csv)を、Elasticsearch のインデックスへ登録します。

3.1. Elastic Cloud のログイン画面よりログインします。

(オンプレミス版の場合は、Kibana のログイン画面よりログインします。)

3.2. Elastic Cloud の Deployment 一覧より、CSV を登録したい Deployment を選択します。

(オンプレミス版の場合は、この操作は必要ありません。)

3.3. Elastic Cloud の Home 画面が表示されるので、[Upload a file]をクリックします。

3.4. ファイルのアップロード画面が表示されるので、さきほど作成した y_20250415_col2_utf8.csv ファイルを点線で囲まれたエリアにドラッグ&ドロップします。

3.5. ファイルアップロードの設定画面が表示されるので、[Override settings] をクリックします。

3.6. Override settings 画面が表示されます。

Number of lines to sample に 20000 を入力します(医薬品マスタが約18000件あるため)。

Has header row にチェックが入っていることを確認します(先頭行をフィールド名とするため)。

その後、右下の [Apply] をクリックします。

3.7. 元の画面に戻るので、[Import] をクリックします。

3.8. Import Data の画面が表示されます。

3.9. Advanced のタブをクリックします。

3.10. Import Data の Advanced タブが表示されます。

次のように入力します。

  • Index name : medicine_20250415
  • Create data view : off
  • Index settings : 次の内容を張り付けます。
  {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "refresh_interval": "5s"
    },
    "analysis": {
      "char_filter": {
        "ja_normalizer": {
          "type": "icu_normalizer",
          "name": "nfkc",
          "mode": "compose"
        }
      },
      "tokenizer": {
        "ja_ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 2,
          "token_chars": [
            "letter",
            "digit",
            "punctuation",
            "symbol"
          ]
        }
      },
      "analyzer": {
        "ja_ngram_index_analyzer": {
          "type": "custom",
          "char_filter": [
            "ja_normalizer"
          ],
          "tokenizer": "ja_ngram_tokenizer",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  }
  • mappings : 次の内容を張り付けます。
{
  "properties": {
    "medicine_code": {
      "type": "long"
    },
    "medicine_name": {
      "type": "keyword",
      "fields": {
        "ngram": {
          "type": "text",
          "analyzer": "ja_ngram_index_analyzer",
          "search_analyzer": "ja_ngram_index_analyzer"
        }
      }
    }
  }
}

3.11. 左下の [Import] をクリックします。

3.12. CSV のインポート処理が行われます(少し時間がかかります)。

3.13. CSV のインポートが完了すると、完了画面が表示されます。

これで、medicine_20250415 インデックスに、医薬品マスタの内容が登録されました。

4. エイリアスの作成

今回は、medicine_20250415 という名前のインデックスにインポートしましたが、医薬品マスタは年に数回更新されます。今後、更新があった場合でも、検索しやすくなるよう、エイリアスを作成しておきます。

Dev Tools から Console を表示し、下記のリクエストを実行します。

POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "medicine_20250415",
        "alias": "medicine"
      }
    }
  ]
}

5. 検索用テンプレートの作成

今回の医薬品マスタの検索アプリは Python で作成しますが、Python から検索しやすくなるよう、検索用テンプレートを Elasticsearch 側で登録しておきます。

Dev Tools から Console を表示し、下記のリクエストを実行します。

PUT _scripts/search_medicine_with_ngram_202505
{
  "script": {
    "lang": "mustache",
    "source": """{
      "_source": false,
      "fields": [ "medicine_code", "medicine_name" ],
      "size": "{{size}}{{^size}}10{{/size}}",
      "query": {
        "bool": {
          "must": [
            {
              "match_phrase": {
                "medicine_name.ngram": "{{search_medicine_name1}}"
              }
            }
            {{#search_medicine_name2}}
            ,
            {
              "match_phrase": {
                "medicine_name.ngram": "{{search_medicine_name2}}"
              }
            }
            {{/search_medicine_name2}}
          ]
        }
      }
    }
    """
  }
}

上記の検索用テンプレートを使った検索例です。

GET /medicine/_search/template
{
  "id": "search_medicine_with_ngram_202505",
  "params": {
    "size": 30,
    "search_medicine_name1": "アス",
    "search_medicine_name2": "100mg"
  }
}

6. 読み取り用 Access Key の生成

Python から検索処理を呼び出すための Access Key を生成しておきます。

Dev Tools から Console を表示し、下記のリクエストを実行します。

POST /_security/api_key
{
  "name": "medicine_read",
  "role_descriptors": {
    "medication_read": {
      "cluster": ["all"],
      "indices": [
        {
          "names": ["medicine*"],
          "privileges": ["read"]
        }
      ]
    }
  }
}

成功すると、エンコードされた Access Key が表示されるので、メモ帳などにコピー&ペーストしておきます(後で使います)。

7. Elasticsearch Endpoint URL の取得

Python のプログラムからアクセスするための Endpoint URL を取得します。Home 画面に戻り、Elasticsearch の大きいボタンをクリックします。

すると、次のような画面が表示されるので、Elasticsearch の Endpoint が表示されている欄のコピーボタンを押します。

コピーした Endpoint の URL をメモ帳などにペーストしておきます(後で使います)。

8. コンテナの実行

サンプルアプリを実行するための下準備はできたので、いよいよ、サンプルアプリを動かしていきます。

ただし、サンプルアプリの各ソースコードを逐次説明していくと、内容が膨大になるため、今回は要点のみを後述しています。

8.1. ソースコードのダウンロード

GitHub のリポジトリからソースコードをダウンロードします。

下記の URL へアクセスします。

GitHub - sios-elastic-tech/blogs: A sample code for blogs about elasticsearch.
A sample code for blogs about elasticsearch. Contribute to sios-elastic-tech/blogs development by creating an account on...

画面上部の [<> Code] をクリックし、さらに [Download ZIP] をクリックします。

blogs-main.zip ファイルがダウンロードされるので、この zip ファイルを展開します。

8.2. ファイルの修正

環境に合わせてファイルを修正します。

さきほど展開した blogs-main/ フォルダの下の 2025-05-ngram-search-part2/flask_app/ フォルダへ移動します。

cd blogs-main/2025-05-ngram-search-part2/flask_app

.env ファイルを環境に合わせて修正します。

elasticsearch_endpoint=''

read_api_key_encoded=''

7. で取得したURL を elasticsearch_endpoint に、

6. で取得した Access Key を read_api_key_encoded に、

それぞれ転記します。

8.3. コンテナのビルド~検索アプリの実行

コンテナをビルドします。

docker compose build

コンテナを実行開始します。

docker compose up -d

コンテナに接続します。

docker exec -it search_with_ngram_sample_202505 /bin/bash

(search_with_ngram_sample_202505 は、コンテナ名です。)

検索アプリを実行します。

python run.py

8.4. 検索アプリの表示

Webブラウザで、localhost:5000 (あるいは、127.0.0.1:5000) にアクセスします。

次のような画面が表示されます。

試しに、検索キーワード1 に “アス” を入力してみます。すると、下部の医薬品リストに、”アス”を含む候補が表示されます(詳細は割愛しますが、2文字以上入力すると検索するようにプログラムしています)。

さらに、検索キーワード2に “10mg” と入力してみます。すると、医薬品リストが、”アス”を含み、かつ、”10mg”を含むものに更新されます。

ここで、検索キーワード2を “100mg” に変更してみます。すると、医薬品リストが “アス” を含み、かつ、”100mg” を含むものに更新されます。

画像だけではわかりにくいと思いますので、下記に動画を添付しています。

これを見て頂ければ、これらの更新が瞬時に行われ、利用者にとってもストレスなく操作できることがおわかりになるかと思います。

なお、医薬品をクリックすると、選択した医薬品のコードと名称を表示するようにしています(これは、おまけの機能です。N-gram 検索とは直接関係ありません)。

検索アプリの停止用ボタンは用意していないので、停止させたい場合は、Ctrl+C を押すなり、コンテナを停止させるなりしてください。

9. 検索処理の概要

Web ブラウザ上で検索キーワード1 または 2 に、何かのキーが入力されたら(onkeyupのイベントを検知したら)static/js/scripts.js の onkeyup1() または onkeyup2() が呼ばれます。

そこで、前回の入力値との比較を行い、違っていたら、/search_medicine へリクエストを投げます。

app.py の def search() で、検索キーワード1 および 検索キーワード2 を受け取り、検索用パラメータを生成します。Elasticsearch に生成した検索用パラメータを渡し、検索用テンプレートを使って N-gram 検索を行います。検索結果を呼び出し元 (JavaScript) に返します。

static/js/scripts.js で、検索結果を受け取り、画面に反映させます。

10. まとめ

N-gram 検索を利用すると、kuromoji が知らない単語でも検索することができます。

医薬品や商品マスタなど、2~3文字を入力すると対象項目を絞り込みたいような場面に適用しやすいことがわかっていただけたかと思います。