0
2

Elasticsearchを触ってみた

Last updated at Posted at 2023-10-08

Elasticsearchがどんなものか全く知らなかったので、インストールして触ってみました。

準備

まずローカルに動作環境を構築します。

OS: Windows 11 Home 22H2
Engine: elasticsearch 8.10.2
Tool: kibana 8.10.2
Language: C#

以下のサイトからelasticsearchをダウンロードしてzipを適当なフォルダに解凍します。
https://www.elastic.co/jp/downloads/elasticsearch

また、以下のサイトからkibanaをダウンロードしてzipを適当なフォルダに解凍します。
kibanaはElasticsearchと連携してデータを探索、可視化、分析するツールだそうです。
https://www.elastic.co/jp/downloads/kibana

configファイルの設定は、ローカル環境で色々試すだけなのでelasticsearch.ymlのsecurityをfalseにしたのと、Elasticsearchの起動でOutOfMemoryが出たのでjvm.optionsをイジりました。

elasticsearch-8.10.2\config\elasticsearch.yml
xpack.security.enabled: false
elasticsearch-8.10.2\config\jvm.options
-Xms4g
-Xmx4g

また、コマンドプロンプトを立ち上げてelasticsearch-8.10.2\binに移動し、以下のコマンドを叩いてプラグインをインストールします。

elasticsearch-plugin
elasticsearch-plugin install analysis-kuromoji
elasticsearch-plugin install analysis-icu

コマンドプロンプトを立ち上げてelasticsearch-8.10.2\binに移動し、Elasticsearchを起動させます。

elasticsearch
elasticsearch.bat

ブラウザを起動させて以下のURLを叩き、諸情報が表示されればOKです。

URL
http://localhost:9200/

image.png
Elasticsearchが起動が終わったら、別のコマンドプロンプトを立ち上げてkibana-8.10.2\binに移動し、kibanaを起動させます。

kibana
kibana.bat

ブラウザを起動させて以下のURLを叩き、ページが表示されればOKです。

URL
http://localhost:5601/

image.png

NGramを試してみる

以下のC#プログラムを実行するとTokenizerにNGramを利用したインデックス設定が作成されます。

.csproj
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.10.0" />
        <PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
    </ItemGroup>
</Project>
C# console application
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);

var isa = new IndexSettingsAnalysis();

// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
    MinGram = 1,
    MaxGram = 10
});

// Tokenizers
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_ngram_tokenizer"
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Index(
            new IndexSettings()
            {
                Analysis = isa,
                MaxNgramDiff = 10
            })
    )
);

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

kibanaのDev Toolsを叩いて先ほど作成したインデックス設定を確認します。
image.png

kibana: Dev Tools > console
GET /my-index/_settings

settings配下にC#プログラムから作成したインデックス設定があることを確認できました。
image.png
続いてNGramのTokenizerの動きを確認します。先ほどC#プログラムを実行して作成したcustom_ngram_analyzerを使って、「ロキソプロフェン錠60mg」をトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。

kibana: Dev Tools > console
GET /my-index/_analyze
{
  "analyzer": "custom_ngram_analyzer",
  "text": "ロキソプロフェン錠60mg"
}

MaxGram = 10でNGramTokenizerを作成しているので最大10文字でトークン化されていることが確認できます。
image.png

kuromojiを試してみる

今度は日本語に対応したkuromojiのTokenizerを使ってみます。以下のC#プログラムを実行するとcustom_kuromoji_analyzerが作成されます。

C# console application
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);

var isa = new IndexSettingsAnalysis();

// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
    MinGram = 1,
    MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
    Mode = KuromojiTokenizationMode.Search,
    DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_kuromoji_tokenizer",
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Index(
            new IndexSettings()
            {
                Analysis = isa,
                MaxNgramDiff = 10
            })
    )
);

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。

kibana: Dev Tools > console
GET /my-index/_analyze
{
  "analyzer": "custom_kuromoji_analyzer",
  "text": "ロキソプロフェン錠60mg"
}

良い感じにトークン化されていると思います。
image.png

kuromojiを使って読み仮名に変換してみる

続いてkuromojiのTokenFilterを使って読み仮名に変換します。以下のC#プログラムを実行するとcustom_kuromoji_yomi_analyzerが作成されます。

C# console application
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);

var isa = new IndexSettingsAnalysis();

// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
    MinGram = 1,
    MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
    Mode = KuromojiTokenizationMode.Search,
    DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_kuromoji_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_kuromoji_tokenizer",
    Filter = new List<string>{ "kuromoji_readingform" }
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Index(
            new IndexSettings()
            {
                Analysis = isa,
                MaxNgramDiff = 10
            })
    )
);

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_yomi_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。

kibana: Dev Tools > console
GET /my-index/_analyze
{
  "analyzer": "custom_kuromoji_yomi_analyzer",
  "text": "ロキソプロフェン錠60mg"
}

読み仮名でトークン化されていると思います。
image.png

不要な文字を削除してみる

CharFilterを使って不要な文字を削除してみます。

C# console application
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);

var isa = new IndexSettingsAnalysis();

// Charfilters
isa.CharFilters = new CharFilters();
isa.CharFilters.Add("custom_char_filter", new PatternReplaceCharFilter()
{
    Pattern = "[0-9]|mg",
    Replacement = ""
});
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
    MinGram = 1,
    MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
    Mode = KuromojiTokenizationMode.Search,
    DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_ngram_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
    Tokenizer = "custom_kuromoji_tokenizer"
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_kuromoji_tokenizer",
    Filter = new List<string>{ "kuromoji_readingform" }
});
// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Index(
            new IndexSettings()
            {
                Analysis = isa,
                MaxNgramDiff = 10
            })
    )
);

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

先程と同様に「ロキソプロフェン錠60mg」をcustom_kuromoji_yomi_analyzerを使ってトークン化します。
以下のコマンドをkibanaのDev Toolsで叩いてみてください。

kibana: Dev Tools > console
GET /my-index/_analyze
{
  "analyzer": "custom_kuromoji_yomi_analyzer",
  "text": "ロキソプロフェン錠60mg"
}

全角の数字とmgが削除されていますね。
image.png
さらにEdgeNGramTokenFilterをかけます。

C# console application
isa.TokenFilters.Add("custom_edge_ngram_filter", new EdgeNGramTokenFilter()
{
    MinGram = 1,
    MaxGram = 10
});
C# console application
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_kuromoji_tokenizer",
    Filter = new List<string>{ "kuromoji_readingform", "custom_edge_ngram_filter" }
});

先頭文字からのNGramでトークン化されます。
image.png
合わせてcustom_ngram_analyzerとcustom_kuromoji_analyzerにもCharFilterを設定しました。

C# console application
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_ngram_tokenizer",
});
C# console application
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_kuromoji_tokenizer",
});

インデックスを作成して検索してみる

ここまでで3つのAnalyzerを作成しました。最後にcustom_ngram_analyzerとcustom_kuromoji_yomi_analyzerを使って医薬品検索用のインデックスを作成してみましょう。

医薬品のマスターは以下のサイトから全件分ファイル(ZIP:898KB)をダウンロードします。
https://www.ssk.or.jp/smph/seikyushiharai/tensuhyo/kihonmasta/kihonmasta_04.html

以下のプログラムにて、custom_ngram_analyzerとcustom_kuromoji_yomi_analyzerをフィールドとマッピングさせてインデックスを作成します。
※CSVのパスはご自身の環境に合わせ書き換えください。

C# console application
using System.Text;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Analysis;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Mapping;

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"));
var client = new ElasticsearchClient(settings);

var isa = new IndexSettingsAnalysis();

// Charfilters
isa.CharFilters = new CharFilters();
isa.CharFilters.Add("custom_char_filter", new PatternReplaceCharFilter()
{
    Pattern = "[0-9]|mg",
    Replacement = ""
});
// Tokenizers
isa.Tokenizers = new Tokenizers();
isa.Tokenizers.Add("custom_ngram_tokenizer", new NGramTokenizer()
{
    MinGram = 1,
    MaxGram = 10
});
isa.Tokenizers.Add("custom_kuromoji_tokenizer", new KuromojiTokenizer()
{
    Mode = KuromojiTokenizationMode.Search,
    DiscardPunctuation = true
});
// TokenFilters
isa.TokenFilters = new TokenFilters();
isa.TokenFilters.Add("custom_edge_ngram_filter", new EdgeNGramTokenFilter()
{
    MinGram = 1,
    MaxGram = 10
});
// Analyzers
isa.Analyzers = new Analyzers();
isa.Analyzers.Add("custom_ngram_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_ngram_tokenizer",
});
isa.Analyzers.Add("custom_kuromoji_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_kuromoji_tokenizer",
});
isa.Analyzers.Add("custom_kuromoji_yomi_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "custom_kuromoji_tokenizer",
    Filter = new List<string>{ "kuromoji_readingform", "custom_edge_ngram_filter" }
});
isa.Analyzers.Add("keyword_search_analyzer", new CustomAnalyzer()
{
    CharFilter = new List<string>{ "custom_char_filter" },
    Tokenizer = "whitespace"
});

// delete index & create index
var delRes = await client.Indices.DeleteAsync("my-index");
var creRes = client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Index(
            new IndexSettings()
            {
                Analysis = isa,
                MaxNgramDiff = 10
            })
    )
    .Mappings(map => map
        .Properties(
            new Properties<YakName>()
            {
                { "yakNameNgram", new TextProperty()
                    {
                        Analyzer = "custom_ngram_analyzer",
                        SearchAnalyzer = "keyword_search_analyzer"
                    }
                },
                { "yakNameKana", new TextProperty()
                    {
                        Analyzer = "custom_kuromoji_yomi_analyzer",
                        SearchAnalyzer = "keyword_search_analyzer"
                    }
                }
            }
        )
    )
);

using (StreamReader reader = new StreamReader(@"C:\es\y_ALL20230920.csv", Encoding.GetEncoding("Shift_JIS")))
{
    while (!reader.EndOfStream)
    {
        string line = reader.ReadLine();
        string[] values = line.Split(',');
        var yak_name = values[4].Trim(new char[] { '"' });
        if (String.IsNullOrEmpty(yak_name))
        {
            continue;
        }
        var yakName = new YakName
        {
            YakNameNgram = yak_name,
            YakNameKana = yak_name
        };
        var indexRes = await client.IndexAsync(yakName, "my-index");
    }
}

Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));

public class YakName
{
    public string YakNameNgram { get; set; }
    public string YakNameKana { get; set; }
}

数分でインデックスの作成が終わると思います。
終わったら以下のコマンドをkibanaのDev Toolsで叩いて、薬品を検索してみましょう。

kibana: Dev Tools > console
GET /my-index/_search
{
  "query": {
    "bool": {
      "should": [
	      { "match": { "yakNameNgram": "恵美須" } }
      ]
    }
  },
  "sort" : [{"_score":"desc"}]
}

image.png

kibana: Dev Tools > console
GET /my-index/_search
{
  "query": {
    "bool": {
      "should": [
	      { "match": { "yakNameKana": "ショウサンギン" } }
      ]
    }
  },
  "sort" : [{"_score":"desc"}]
}

image.png
以上でした。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2