什么是 Analysis?

Analysis 是通过分词器(Analyzer) 来实现的,可使用 ES 内置的分析器或者按需定制化分析器

举一个分词简单的例子:比如你输入 Mastering Elasticsearch,会自动帮你分成两个单词,一个是 mastering,另一个是 elasticsearch,可以看出单词也被转化成了小写的。
image.png

分词器的组成

分词器是专门处理分词的组件,分词器由以下三部分组成:

  • Character Filters:针对原始文本处理,比如去除 html 标签
  • Tokenizer:按照规则切分为单词,比如按照空格切分
  • Token Filters:将切分的单词进行加工,比如大写转小写,删除 stopwords,增加同义语

image.png
同时 Analyzer 三个部分也是有顺序的,从图中可以看出,从上到下依次经过 Character Filters,Tokenizer 以及 Token Filters,这个顺序比较好理解,一个文本进来肯定要先对文本数据进行处理,再去分词,最后对分词的结果进行过滤。
其中,ES 内置了许多分词器:

  • Standard Analyzer - 默认分词器,按词切分,小写处理
  • Simple Analyzer - 按照非字母切分(符号被过滤),小写处理
  • Stop Analyzer - 小写处理,停用词过滤(the ,a,is)
  • Whitespace Analyzer - 按照空格切分,不转小写
  • Keyword Analyzer - 不分词,直接将输入当做输出
  • Pattern Analyzer - 正则表达式,默认 W+
  • Language - 提供了 30 多种常见语言的分词器
  • Customer Analyzer - 自定义分词器

接下来会对以上分词器进行讲解,在讲解之前先来看下很有用的 API:_analyzer API:

Analyzer API

它可以通过以下三种方式来查看分词器是怎么样工作的:

  • 直接指定 Analyzer 进行测试

    1. GET _analyze
    2. {
    3. "analyzer": "standard",
    4. "text" : "Mastering Elasticsearch , elasticsearch in Action"
    5. }
  • 指定索引的字段进行测试

    1. POST books/_analyze
    2. {
    3. "field": "title",
    4. "text": "Mastering Elasticesearch"
    5. }
  • 自定义分词进行测试

    1. POST /_analyze
    2. {
    3. "tokenizer": "standard",
    4. "filter": ["lowercase"],
    5. "text": "Mastering Elasticesearch"
    6. }

    ES 分词器

    首先来介绍下 Stamdard Analyzer 分词器:

    Stamdard Analyzer

    image.png
    它是 ES 默认的分词器,它会对输入的文本按词的方式进行切分,切分好以后会进行转小写处理,默认的 stopwords 是关闭的
    下面使用 Kibana 看一下它是怎么样进行工作的,在 Kibana 的开发工具(Dev Tools)中指定 Analyzer 为 standard,并输入文本 In 2020, Java is the best language in the world.,然后我们运行一下:

    1. GET _analyze
    2. {
    3. "analyzer": "standard",
    4. "text": "In 2020, Java is the best language in the world."
    5. }

    运行结果如下:

    1. {
    2. "tokens" : [
    3. {
    4. "token" : "in",
    5. "start_offset" : 0,
    6. "end_offset" : 2,
    7. "type" : "<ALPHANUM>",
    8. "position" : 0
    9. },
    10. {
    11. "token" : "2020",
    12. "start_offset" : 3,
    13. "end_offset" : 7,
    14. "type" : "<NUM>",
    15. "position" : 1
    16. },
    17. {
    18. "token" : "java",
    19. "start_offset" : 9,
    20. "end_offset" : 13,
    21. "type" : "<ALPHANUM>",
    22. "position" : 2
    23. },
    24. {
    25. "token" : "is",
    26. "start_offset" : 14,
    27. "end_offset" : 16,
    28. "type" : "<ALPHANUM>",
    29. "position" : 3
    30. },
    31. {
    32. "token" : "the",
    33. "start_offset" : 17,
    34. "end_offset" : 20,
    35. "type" : "<ALPHANUM>",
    36. "position" : 4
    37. },
    38. {
    39. "token" : "best",
    40. "start_offset" : 21,
    41. "end_offset" : 25,
    42. "type" : "<ALPHANUM>",
    43. "position" : 5
    44. },
    45. {
    46. "token" : "language",
    47. "start_offset" : 26,
    48. "end_offset" : 34,
    49. "type" : "<ALPHANUM>",
    50. "position" : 6
    51. },
    52. {
    53. "token" : "in",
    54. "start_offset" : 35,
    55. "end_offset" : 37,
    56. "type" : "<ALPHANUM>",
    57. "position" : 7
    58. },
    59. {
    60. "token" : "the",
    61. "start_offset" : 38,
    62. "end_offset" : 41,
    63. "type" : "<ALPHANUM>",
    64. "position" : 8
    65. },
    66. {
    67. "token" : "world",
    68. "start_offset" : 42,
    69. "end_offset" : 47,
    70. "type" : "<ALPHANUM>",
    71. "position" : 9
    72. }
    73. ]
    74. }

    可以看出是按照空格、非字母的方式对输入的文本进行了转换,比如对 Java 做了转小写,对一些停用词也没有去掉,比如 in。
    其中 token 为分词结果;start_offset 为起始偏移;end_offset 为结束偏移;position 为分词位置。
    下面来看下 Simple Analyzer 分词器:

    Simple Analyzer

    image.png
    它只包括了 Lower Case 的 Tokenizer,它会按照非字母切分非字母的会被去除,最后对切分好的做转小写处理,然后接着用刚才的输入文本,分词器换成 simple 来进行分词,运行结果如下:

    1. {
    2. "tokens" : [
    3. {
    4. "token" : "in",
    5. "start_offset" : 0,
    6. "end_offset" : 2,
    7. "type" : "word",
    8. "position" : 0
    9. },
    10. {
    11. "token" : "java",
    12. "start_offset" : 9,
    13. "end_offset" : 13,
    14. "type" : "word",
    15. "position" : 1
    16. },
    17. {
    18. "token" : "is",
    19. "start_offset" : 14,
    20. "end_offset" : 16,
    21. "type" : "word",
    22. "position" : 2
    23. },
    24. {
    25. "token" : "the",
    26. "start_offset" : 17,
    27. "end_offset" : 20,
    28. "type" : "word",
    29. "position" : 3
    30. },
    31. {
    32. "token" : "best",
    33. "start_offset" : 21,
    34. "end_offset" : 25,
    35. "type" : "word",
    36. "position" : 4
    37. },
    38. {
    39. "token" : "language",
    40. "start_offset" : 26,
    41. "end_offset" : 34,
    42. "type" : "word",
    43. "position" : 5
    44. },
    45. {
    46. "token" : "in",
    47. "start_offset" : 35,
    48. "end_offset" : 37,
    49. "type" : "word",
    50. "position" : 6
    51. },
    52. {
    53. "token" : "the",
    54. "start_offset" : 38,
    55. "end_offset" : 41,
    56. "type" : "word",
    57. "position" : 7
    58. },
    59. {
    60. "token" : "world",
    61. "start_offset" : 42,
    62. "end_offset" : 47,
    63. "type" : "word",
    64. "position" : 8
    65. }
    66. ]
    67. }

    从结果中可以看出,数字 2020 被去除掉了,说明非字母的的确会被去除,所有的词也都做了小写转换。
    现在,我们来看下 Whitespace Analyzer 分词器:

    Whitespace Analyzer

    它非常简单,根据名称也可以看出是按照空格进行切分
    image.png

  1. {
  2. "tokens" : [
  3. {
  4. "token" : "In",
  5. "start_offset" : 0,
  6. "end_offset" : 2,
  7. "type" : "word",
  8. "position" : 0
  9. },
  10. {
  11. "token" : "2020,",
  12. "start_offset" : 3,
  13. "end_offset" : 8,
  14. "type" : "word",
  15. "position" : 1
  16. },
  17. {
  18. "token" : "Java",
  19. "start_offset" : 9,
  20. "end_offset" : 13,
  21. "type" : "word",
  22. "position" : 2
  23. },
  24. {
  25. "token" : "is",
  26. "start_offset" : 14,
  27. "end_offset" : 16,
  28. "type" : "word",
  29. "position" : 3
  30. },
  31. {
  32. "token" : "the",
  33. "start_offset" : 17,
  34. "end_offset" : 20,
  35. "type" : "word",
  36. "position" : 4
  37. },
  38. {
  39. "token" : "best",
  40. "start_offset" : 21,
  41. "end_offset" : 25,
  42. "type" : "word",
  43. "position" : 5
  44. },
  45. {
  46. "token" : "language",
  47. "start_offset" : 26,
  48. "end_offset" : 34,
  49. "type" : "word",
  50. "position" : 6
  51. },
  52. {
  53. "token" : "in",
  54. "start_offset" : 35,
  55. "end_offset" : 37,
  56. "type" : "word",
  57. "position" : 7
  58. },
  59. {
  60. "token" : "the",
  61. "start_offset" : 38,
  62. "end_offset" : 41,
  63. "type" : "word",
  64. "position" : 8
  65. },
  66. {
  67. "token" : "world.",
  68. "start_offset" : 42,
  69. "end_offset" : 48,
  70. "type" : "word",
  71. "position" : 9
  72. }
  73. ]
  74. }

Stop Analyzer

它由 Lowe Case 的 Tokenizer 和 Stop 的 Token Filters 组成的,相较于刚才提到的 Simple Analyzer,多了 stop 过滤,stop 就是会把 the,a,is 等修饰词去除
image.png

  1. {
  2. "tokens" : [
  3. {
  4. "token" : "java",
  5. "start_offset" : 9,
  6. "end_offset" : 13,
  7. "type" : "word",
  8. "position" : 1
  9. },
  10. {
  11. "token" : "best",
  12. "start_offset" : 21,
  13. "end_offset" : 25,
  14. "type" : "word",
  15. "position" : 4
  16. },
  17. {
  18. "token" : "language",
  19. "start_offset" : 26,
  20. "end_offset" : 34,
  21. "type" : "word",
  22. "position" : 5
  23. },
  24. {
  25. "token" : "world",
  26. "start_offset" : 42,
  27. "end_offset" : 47,
  28. "type" : "word",
  29. "position" : 8
  30. }
  31. ]
  32. }

Keyword Analyzer

它其实不做分词处理,只是将输入作为 Term 输出
image.png
我们可以看到,没有对输入文本进行分词,而是直接作为 Term 输出了。

{
  "tokens" : [
    {
      "token" : "In 2020, Java is the best language in the world.",
      "start_offset" : 0,
      "end_offset" : 48,
      "type" : "word",
      "position" : 0
    }
  ]
}

Pattern Analyzer

它可以通过正则表达式的方式进行分词,默认是用 \W+ 进行分割的,也就是非字母的符合进行切分的,由于运行结果和 Stamdard Analyzer 一样,就不展示了。
image.png

Language Analyzer

ES 为不同国家语言的输入提供了 Language Analyzer 分词器,在里面可以指定不同的语言,我们用 english 进行分词看下:
可以看出 language 被改成了 languag,同时它也是有 stop 过滤器的,比如 in,is 等词也被去除了。

{
  "tokens" : [
    {
      "token" : "2020",
      "start_offset" : 3,
      "end_offset" : 7,
      "type" : "<NUM>",
      "position" : 1
    },
    {
      "token" : "java",
      "start_offset" : 9,
      "end_offset" : 13,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "best",
      "start_offset" : 21,
      "end_offset" : 25,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "languag",
      "start_offset" : 26,
      "end_offset" : 34,
      "type" : "<ALPHANUM>",
      "position" : 6
    },
    {
      "token" : "world",
      "start_offset" : 42,
      "end_offset" : 47,
      "type" : "<ALPHANUM>",
      "position" : 9
    }
  ]
}