• 3、Elasticsearch分词器简介与使用(二)


    我们知道通过 Elasticsearch 实现全文搜索,在文档被导入到 ES 后,文档的每个字段都需要被分析,而这个分析阶段就会涉及到分词。上篇介绍了分词器的概念和常见分词器的使用,然而有些特定场景中,之前的分词器并不能满足我们的实际需求,那么就要进行定制分析器了。

    ES 已经提供了丰富多样的开箱即用的分词 plugin,通过这些 plugin 可以创建自己的 token Analyzer,甚至可以利用已经有的 Char Filter,Tokenizer 及 Token Filter 来重新组合成一个新的 Analyzer,并对文档中的每个字段分别定义自己的 Analyzer。基于这些思路,我们则可以实现一个定制的分析器。

    举个例子:

     使用 standard 分词器对该文本字符串进行分词,得结果如下:

    1. {
    2. "tokens": [
    3. {
    4. "token": "the",
    5. "start_offset": 0,
    6. "end_offset": 3,
    7. "type": "<ALPHANUM>",
    8. "position": 0
    9. },
    10. {
    11. "token": "third",
    12. "start_offset": 4,
    13. "end_offset": 9,
    14. "type": "<ALPHANUM>",
    15. "position": 1
    16. },
    17. {
    18. "token": "dose",
    19. "start_offset": 10,
    20. "end_offset": 14,
    21. "type": "<ALPHANUM>",
    22. "position": 2
    23. },
    24. {
    25. "token": "of",
    26. "start_offset": 15,
    27. "end_offset": 17,
    28. "type": "<ALPHANUM>",
    29. "position": 3
    30. },
    31. {
    32. "token": "covid",
    33. "start_offset": 18,
    34. "end_offset": 23,
    35. "type": "<ALPHANUM>",
    36. "position": 4
    37. },
    38. {
    39. "token": "19",
    40. "start_offset": 24,
    41. "end_offset": 26,
    42. "type": "<NUM>",
    43. "position": 5
    44. },
    45. {
    46. "token": "vaccine",
    47. "start_offset": 27,
    48. "end_offset": 34,
    49. "type": "<ALPHANUM>",
    50. "position": 6
    51. }
    52. ]
    53. }

    这段文本字符串中的"COVID-19",经过 standard 分词器处理后,却拆分成了"covid"和"19",明显不是我想要的结果,我预期得到的结果是"COVID19"或者"covid19"。那么,该如何实现我预期的分词结果呢?

    这里有必要先普及下,我们平时创建某个索引的几个操作姿势:

    方式一、简单不做作,直接 PUT 

    PUT 192.168.150.130:9200/mytest_index

    方式二、创建索引同时指定"settings"

    1. PUT 192.168.150.130:9200/mytest_index
    2. {
    3. "settings":{
    4. "index":{
    5. // 创建索引的同时设置分片数、副本数
    6. "number_of_shards":"3",
    7. "number_of_replicas":"1"
    8. }
    9. }
    10. }

    方式三、创建索引同时指定"mappings"

    1. PUT 192.168.150.130:9200/mytest_index
    2. {
    3. "mappings":{
    4. "_doc":{
    5. "properties":{
    6. "city":{
    7. "type":"keyword"
    8. },
    9. "date":{
    10. "type":"keyword"
    11. },
    12. "quantity":{
    13. "type":"integer"
    14. },
    15. "description":{
    16. "type":"text"
    17. }
    18. }
    19. }
    20. }
    21. }

    其他方式:创建索引同时指定"settings"、"mappings"、"aliases"等(看实际需求)

    1. PUT 192.168.150.130:9200/mytest_index
    2. {
    3. "mappings":{
    4. ........
    5. },
    6. "aliases": {
    7. ........
    8. },
    9. "settings": {
    10. ........
    11. }
    12. }

    有了这些知识点储备之后,实现预期的分词结果就简单了,基本思路是:创建索引的同时,通过 "mappings" 设置该字段的属性并为该字段自定义一个分析器,然后通过 "settings" 设置 Char Filter、Tokenizer 及 Token Filter 来重新组合成一个新的 Analyzer。具体实现如下:

    1. PUT 192.168.150.130:9200/mytest_index
    2. {
    3. "mappings": {
    4. "_doc": {
    5. "properties": {
    6. "city": {
    7. "type": "keyword"
    8. },
    9. "date": {
    10. "type": "keyword"
    11. },
    12. "quantity": {
    13. "type": "integer"
    14. },
    15. "description": {
    16. "type": "text",
    17. "analyzer": "my_description_analyzer"
    18. }
    19. }
    20. }
    21. },
    22. "settings": {
    23. "analysis": {
    24. "char_filter": {
    25. "covid19_filter": {
    26. "type": "mapping",
    27. "mappings": [
    28. "COVID-19 => COVID19"
    29. ]
    30. }
    31. },
    32. "analyzer": {
    33. "my_description_analyzer": {
    34. "type": "custom",
    35. "char_filter": [
    36. "covid19_filter"
    37. ],
    38. "tokenizer": "standard",
    39. "filter": [
    40. // 转小写输出covid19,如果注释掉的话则会输出COVID19
    41. "lowercase"
    42. ]
    43. }
    44. }
    45. }
    46. }
    47. }

    请注意,由于我使用的 ES 版本是6.8.6,通过"mappings"设置字段属性时,需要加上文档类型"_doc",不然会报错的,而在高版本的 ES 中创建索引时,则不会再加上"_doc"(高版本 ES 的文档 type 已被抛弃)。

    测试效果如下:

     这样就实现了预期的分词结果。另外,像 "the"、"of" 这些停用词,standard 分词器是不会过滤掉的,如果我就要过滤掉这些停用词,或者加入我认为要过滤掉的单词,这个该如何实现呢?

    上篇文章分词器概念中,介绍说明过分词器的分析阶段(Analysis Phase),剔除已拆分的单词可在 Token Filter 实现。还是以 mytest_index 索引创建为例,在原来基础上加入以下内容(原设置已省略,重点在"filter"、"my_stop"):

    1. PUT 192.168.150.130:9200/mytest_index1
    2. {
    3. "mappings": {
    4. ......
    5. },
    6. "settings": {
    7. "analysis": {
    8. "char_filter": {
    9. ......
    10. },
    11. "analyzer": {
    12. "my_description_analyzer": {
    13. "type": "custom",
    14. "char_filter": [
    15. "covid19_filter"
    16. ],
    17. "tokenizer": "standard",
    18. "filter": [
    19. // 转小写输出covid19,如果注释掉的话则会输出COVID19
    20. //"lowercase",
    21. "my_stop"
    22. ]
    23. }
    24. },
    25. "filter":{
    26. "my_stop":{
    27. "type":"stop",
    28. "stopwords": ["the", "a", "of", "is"]
    29. }
    30. }
    31. }
    32. }
    33. }

    去掉停用词后,测试效果如下:

    1. {
    2. "tokens": [
    3. {
    4. "token": "third",
    5. "start_offset": 4,
    6. "end_offset": 9,
    7. "type": "<ALPHANUM>",
    8. "position": 1
    9. },
    10. {
    11. "token": "dose",
    12. "start_offset": 10,
    13. "end_offset": 14,
    14. "type": "<ALPHANUM>",
    15. "position": 2
    16. },
    17. {
    18. "token": "COVID19",
    19. "start_offset": 18,
    20. "end_offset": 26,
    21. "type": "<ALPHANUM>",
    22. "position": 4
    23. },
    24. {
    25. "token": "vaccine",
    26. "start_offset": 27,
    27. "end_offset": 34,
    28. "type": "<ALPHANUM>",
    29. "position": 5
    30. }
    31. ]
    32. }

    最后,请注意 Token Filter 过滤顺序的问题,假如我把文本字符串修改为"The third dose of COVID-19 vaccine",并把"lowercase" 和 "my_stop"调换下顺序,如下:

    1. "analyzer": {
    2. "my_description_analyzer": {
    3. "type": "custom",
    4. "char_filter": [
    5. "covid19_filter"
    6. ],
    7. "tokenizer": "standard",
    8. "filter": [
    9. "my_stop",
    10. "lowercase"
    11. ]
    12. }
    13. }

    那么得到的分词结果为:["the" "third" "dose" "covid19" "vaccine"],看出问题了吧?"the" 是被定义成停用词的,但分词结果却有这个单词。经过分析不难看出:这是由于执行"my_stop"时并没有把"The"看作是停止词,接着经过"lowercase"处理时输出了"the"。这就提示我们过滤顺序的重要性啊!!

    在上篇文章提及过,Analyzer 将文本字符分解为 token 的过程,通常会发生在以下两种场景:一是索引建立的时候,二是进行文本搜索的时候。

    第一种场景刚刚已经演示过了,在索引建立后,如果要录入一个文档的话,该文档在被写入索引之前,会将文档中的文本字符串分解成一个个 token,这些 token 是存放到数据库的;

    第二种场景则是进行文本搜索,文本搜索的时候也会对该字符串进行分词,也会建立  token,但不会存放到数据库。默认情况下,我们查询搜索时会使用到第一种场景定制的分析器,很明显会导致某些查询问题,比如查询 "of" 是查不到结果的,因此有必要区分第一种场景的分析器。我们可以通过 search_analyzer 实现:

    1. PUT 192.168.150.130:9200/mytest_index
    2. {
    3. "mappings": {
    4. "_doc": {
    5. "properties": {
    6. "city": {
    7. "type": "keyword"
    8. },
    9. "date": {
    10. "type": "keyword"
    11. },
    12. "quantity": {
    13. "type": "integer"
    14. },
    15. "description": {
    16. "type": "text",
    17. "analyzer": "my_description_analyzer",
    18. "search_analyzer": "standard"
    19. // 也可以使用自定义的分词器 "search_analyzer": "my_search_analyzer"
    20. }
    21. }
    22. }
    23. },
    24. "settings": {
    25. ......
    26. }
    27. }

    最后

    本文重点在介绍说明如何实现自定义的分析器,以满足特定场景的需求,并通过演示说明了实现的思路。然而,实际需求总是五花八门多种多样的,掌握实现的思路和原理才是最重要的。比如,如何实现多个不同的分析器搜索查询相同的文本内容(思路是使用 multi-field 实现,即 fields 设置多字段),建议参考学习【Elasticsearch:将精确搜索与词干混合】这篇博文;又比如,在定制分析器时考虑定制相关性(通过 function_score 实现定制相关性),建议参考学习【Elasticsearch:定制分词器(analyzer)及相关性】这篇博文。

    下篇将开始介绍说明 ES 索引及索引文档的 CRUD 操作,属于基础内容,但也是平时工作必备的知识点。

  • 相关阅读:
    linux配置ssh无密码登录失败的一种原因
    DIN EN ISO 4589-2塑料 用氧指数法测定燃烧行为 第2 部分:室温试验
    Ubuntu QtCreator不能输入中文,可以从其他位置复制中文
    有关自动化测试,你应该要了解这些..
    iOS 关于UITableView常见使用方法
    【数据结构】冒泡,快速,直接插入,归并,选择排序
    持续集成和持续部署(CI/CD)
    2021中国科学院文献情报中心期刊分区表 计算机(2)
    Wordpress 如何添加 Ads.txt 文件
    synchronized代码块使用练习
  • 原文地址:https://blog.csdn.net/qq_29119581/article/details/125441460