分词器

1005 发布于: 2021-05-11 读完约需 4 分钟

概述

有时,我们希望以自定义的方式分析文本,在NEST中,可以通过配置Elasticsearch内置分词器的工作方式,或者可以将分词组件组合在一起来构建自定义的分析器。

分词工作链

在Elasticsearch中,分词器由三部分组件:

  • 0个或多个字符过滤器
  • 1个分词器
  • 0个或多个令牌过滤器

analysis chain

为字段指定特定的分词器

在索引上创建新字段时,可以在文本数据类型(text)字段映射上指定分析器,通常是在索引创建和创建类型映射时,以及使用Put API向索引添加新字段时。

特别注意:尽管可以向索引添加新类型,或向类型添加新字段,但不能向现有字段添加或者修改新的分词器。如果你这样做,已经被索引的数据将是不正确的,你的搜索也将不能再按预期工作。

当你需要更改已在字段的分词器,最好的做法是使用reindex来重建索引。

以下是一个为name字段指定特定分词器的示例,其中将POCO实体ProjectName属性映射到了Elasticsearch的name字段,同时指定了分词器whitespace

var createIndexResponse = _client.Indices.Create("my-index", c => c
    .Map<Project>(mm => mm
        .Properties(p => p
            .Text(t => t
                .Name(n => n.Name)
                .Analyzer("whitespace")
            )
        )
    )
);

配置内置的分词器

可以配置几个内置分析器来改变它们的行为。例如,可以将standard分词器配置为支持包含停词令牌过滤器的停词列表。

配置内置分析器需要基于内置分析器创建一个分析器:

var createIndexResponse = _client.Indices.Create("my-index", c => c
    .Settings(s => s
        .Analysis(a => a
            .Analyzers(aa => aa
                .Standard("standard_english", sa => sa
                    .StopWords("_english_") // Elasticsearch中预定义的英文停词列表
                )
            )
        )
    )
    .Map<Project>(mm => mm
        .Properties(p => p
            .Text(t => t
                .Name(n => n.Name)
                .Analyzer("standard_english") // 使用standard_english分词器
            )
        )
    )
);

生成的Elasticsearch映射JSON结构为:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "standard_english": {
          "type": "standard",
          "stopwords": [
            "_english_"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "standard_english"
      }
    }
  }
}

创建自定义的分词器

当没有一个内置分词器适合你的需求时,可以创建/组合自定义分词器。自定义分析器是分词链中看到的组件和位置增量间隙构建的,当一个字段可以包含多个值时,比如实体类中的List<string> 属性,它决定了Elasticsearch应该在数组元素之间插入的间隙大小。

对于本例,假设我们正在索引编程问题,其中问题内容是HTML格式并包含源代码:

public class Question
{
    public int Id { get; set; }
    public DateTimeOffset CreationDate { get; set; }
    public int Score { get; set; }
    public string Body { get; set; }
}

基于我们对编程语言的领域知识,我们希望能够搜索包含“c#”的问题,但是使用standard分词器,“c#”将被分词器分解并产生令牌“C”。这不适用于我们的用例,因为没有办法区分关于“c#”的问题和关于另一种流行编程语言“C”的问题。

我们可以用定制的分词器来解决我们的问题

var createIndexResponse = _client.Indices.Create("questions", c => c
    .Settings(s => s
        .Analysis(a => a
            .CharFilters(cf => cf
                .Mapping("programming_language", mca => mca
                    .Mappings(new []
                    {
                        "c# => csharp",
                        "C# => Csharp"
                    })
                )
            )
            .Analyzers(an => an
                .Custom("question", ca => ca
                    .CharFilters("html_strip", "programming_language")
                    .Tokenizer("standard")
                    .Filters("lowercase", "stop")
                )
            )
        )
    )
    .Map<Question>(mm => mm
        .AutoMap()
        .Properties(p => p
            .Text(t => t
                .Name(n => n.Body)
                .Analyzer("question")
            )
        )
    )
);

我们的自定义问题分词器将对问题主体应用以下分词:

  • 清洗HTML标签
  • C#c#分别映射成CSharpcsharp(所以#不会被标记器去掉)
  • 使用standard标记器进行标记
  • 使用standard令牌筛选器筛选令牌
  • 使用lowercase进行过滤
  • 删除停词标记

在本示例中,由于配置了自定义的分词映射关系C#==>Csharp, c#==>csharp,所以在对Body字段进行搜索时,搜索C#同时也会得到包含Csharp的结果。

索引分词与搜索分词

在前面的示例中,我们可能不想对一个问题主体的索引和全文搜索应用相同的分词。我们知道,对于我们的问题域,查询输入将不包含HTML标签,因此我们希望在搜索时应用不同的分词。

除了在查询时使用的分词器之外,还可以在创建索引时使用的字段映射时指定分词器

ar createIndexResponse = _client.Indices.Create("questions", c => c
    .Settings(s => s
        .Analysis(a => a
            .CharFilters(cf => cf
                .Mapping("programming_language", mca => mca
                    .Mappings(new[]
                    {
                        "c# => csharp",
                        "C# => Csharp"
                    })
                )
            )
            .Analyzers(an => an
                .Custom("index_question", ca => ca // 在索引时使用index_question分词器以去掉HTML标签
                    .CharFilters("html_strip", "programming_language")
                    .Tokenizer("standard")
                    .Filters("lowercase", "stop")
                )
                .Custom("search_question", ca => ca // 在查询时使用`search_question`分词器,此分词器不会去掉HTML标签
                    .CharFilters("programming_language")
                    .Tokenizer("standard")
                    .Filters("lowercase", "stop")
                )
            )
        )
    )
    .Map<Question>(mm => mm
        .AutoMap()
        .Properties(p => p
            .Text(t => t
                .Name(n => n.Body)
                .Analyzer("index_question")
                .SearchAnalyzer("search_question")
            )
        )
    )
);

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册