diadia

興味があることをやってみる。自分のメモを残しておきます。

htmlタグに邪魔されずに検索したい

内容

  1. 実際にインデックスを作って確かめる
  2. analyzeエンドポイントを使っても確かめてみる

1. 実際にインデックスを作って確かめる

htmlタグがあると困る場合について

次にhtmlタグがついたデータをインデックスに格納するケースを考える。
具体的にはhtmlタグは検索対象ではなく、タグを除いた文章そのものを検索対象としたい場合について取り扱う。

たとえば、上の例で

<p>おは<b>よう</b></p>

のデータを登録すると、”おはよう”では検索できないのです。せっかく日本語文章を検索できるようになったとしてもhtmlタグが存在するとお手上げになってしまいます。
実際に確認してみましょう。

// 日本語の単語を識別するkuromoji_tokenizerをセット
PUT html_tag_sample
{ 
  "settings":{
    "analysis": {
      "analyzer": {
        "my_kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer"
        }
      }
    }
  },
  "mappings":{
    "properties": {
      "content":{
          "type":"text",
          "analyzer": "my_kuromoji_analyzer"
        }
    }
  }
}
// 当該データを登録する
PUT html_tag_sample/_doc/1
{
  "content":"<p>おは<b>よう</b></p>"
}
// 以下を実施しても検索結果は得られない。
// =>おはようで検索できるはずがタグを挟むことで日本語単語を認識できなくなっている😨
GET html_tag_sample/_search
{
  "query":{
    "match":{
      "content":"おはよう"
    }
  }
}

さらに言えば、pタグやbタグを検索にかけるとヒットします。つまり単語の識別としてhtmlタグも識別していることがわかります。

対処法

この場合、日本語文章に含まれるタグをまず除去してから日本語単語を識別するtokenizerを実行すればうまくいく。 htmlタグを文章から除去するにはhtml_stripを使えば良い。 HTML strip character filter | Elasticsearch Reference [7.11] | Elastic

生データ(文章)にhtml_stripをかけてからをしてからtokenizerを実行しますが、この生データに前処理を担うがchar_filterと呼ばれる。char_filterはtokenizerと同じ次元にある概念です。

したがってchar_filterにhtml_stripをセットする。また、char_filterのセットは0個でも1個でも10個でも良い。 Anatomy of an analyzer | Elasticsearch Reference [7.11] | Elastic

html_stripはマッピングするときに宣言して使うのでtokenizerをセットする要領で使います。

PUT sample
{ 
  "settings":{
    "analysis": {
      "analyzer": {
        "my_kuromoji_analyzer": {
          "type": "custom",
          "char_filter":["html_strip"],
          "tokenizer": "kuromoji_tokenizer"
        }
      }
    }
  },
  "mappings":{
    "properties": {
      "content":{
          "type":"text",
          "analyzer": "my_kuromoji_analyzer"
        }
    }
  }
}

当該データの登録

PUT sample/_doc/2
{
  "content":"<p>おは<b>よう</b></p>"
}
// おはようの検索は成功する
GET sample/_search
{
  "query":{
    "match":{
      "content":"おはよう"
    }
  }
}
// またpタグを検索しても結果は無いと表示される
GET sample/_search
{
  "query":{
    "match":{
      "content":"<p>"
    }
  }
}

これでhtmlタグが挟み込まれることによって単語を識別できなくなる弊害に対処することができました😆

2. analyzeエンドポイントを使っても確かめてみる

sample1とsample2を作成する。sample1はhtml_stripがないanalyzerを作りcontentフィールドにセットし、sample2はhtml_stripがあるanalyzerを作りcontentフィールドにセットしている。

analyzeエンドポイント:

Analyze API | Elasticsearch Reference [7.11] | Elastic

PUT sample1
{
  "settings": {
      "analysis": {
        "analyzer": {
          "kuromoji_standard": {                 
            "tokenizer": "kuromoji_tokenizer",
            "type": "custom"
          }
        }
      }
  },
  "mappings":{
    "properties":{
      "content":{
        "type": "text",
        "analyzer":"kuromoji_standard"
      }    
    }
  }
}



PUT sample2
{
  "settings": {
      "analysis": {
        "analyzer": {
          "kuromoji_html_strip": {                 
            "char_filter": [
              "html_strip"
            ],
            "tokenizer": "kuromoji_tokenizer",
            "type": "custom"
          }
        }
      }
  },
  "mappings":{
    "properties":{
      "content":{
        "type": "text",
        "analyzer":"kuromoji_html_strip"
      }    
    }
  }
}
GET sample1/_analyze
{
  "analyzer": "kuromoji_standard",
  "text":"<p>おは<b>よう</b></p>"
}

//// 結果
{
  "tokens" : [
    {
      "token" : "p",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "お",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "は",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "b",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "よう",
      "start_offset" : 8,
      "end_offset" : 10,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "b",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "p",
      "start_offset" : 16,
      "end_offset" : 17,
      "type" : "word",
      "position" : 6
    }
  ]
}

結果を見るとすべての文字が分解されているので、日本語単語のまとまりで区切られてはない。つまり検索キーワードに日本語単語が登録されてないことがわかる。

GET sample2/_analyze
{
   "analyzer": "kuromoji_html_strip",
  "text":"<p>おは<b>よう</b></p>"
}


//// 結果
{
  "tokens" : [
    {
      "token" : "おはよう",
      "start_offset" : 3,
      "end_offset" : 14,
      "type" : "word",
      "position" : 0
    }
  ]
}

結果として”おはよう”という単語のまとまりでトークン化できているので、検索時におはようで検索することができると結論付けられる。