概述
有时,我们希望以自定义的方式分析文本,在NEST中,可以通过配置Elasticsearch内置分词器的工作方式,或者可以将分词组件组合在一起来构建自定义的分析器。
分词工作链
在Elasticsearch中,分词器由三部分组件:
- 0个或多个字符过滤器
- 1个分词器
- 0个或多个令牌过滤器
为字段指定特定的分词器
在索引上创建新字段时,可以在文本数据类型(text
)字段映射上指定分析器,通常是在索引创建和创建类型映射时,以及使用Put
API向索引添加新字段时。
特别注意:尽管可以向索引添加新类型,或向类型添加新字段,但不能向现有字段添加或者修改新的分词器。如果你这样做,已经被索引的数据将是不正确的,你的搜索也将不能再按预期工作。
当你需要更改已在字段的分词器,最好的做法是使用
reindex
来重建索引。
以下是一个为name
字段指定特定分词器的示例,其中将POCO实体Project
的Name
属性映射到了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#
分别映射成CSharp
和csharp
(所以#不会被标记器去掉) - 使用
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")
)
)
)
);
发表评论
登录用户才能发表评论, 请 登 录 或者 注册