ElasticSearch NEST自定义词连接分析器返回结果异常排查
嘿,我来帮你搞定这个问题!你现在遇到的核心问题是:当用户搜索Transhex这类连写词时,没法返回Trans Hex这种带空格的结果,本质是你的索引分析器没有为带空格的词条生成对应的连写形式token,导致搜索连写词时匹配不到。
先梳理下你当前配置的问题:
之前配置的问题分析
最初用
standard分词器的问题:standard分词器会把Trans Hex拆成两个独立token["Trans", "Hex"],但你的word-joiner-filter(WordDelimiter过滤器)是作用于单个token的——它只会拆分单个token里的大小写/数字分隔(比如TransHex会拆成trans和hex),但没法把两个独立的token(trans和hex)连接成transhex。所以索引里根本没有transhex这个token,自然搜不到。改成
keyword分词器后的问题:
这个分词器会把整个Trans Hex当成一个完整token,里面的空格不会被拆分。WordDelimiter过滤器没法识别空格作为分隔符,所以还是不会生成连写的transhextoken。再加上你把EdgeNGram的min_gram设为1,导致大量短ngram(比如p、pe)被生成,搜索perfectpools时会匹配到所有包含这些短ngram的无关词条(比如Perfect Hideaways里的perfect)。
正确的配置方案
要实现“搜连写词返回带空格结果”,我们需要让索引时同时保留原带空格的词条、拆分后的单个词、以及连写形式的词。具体要调整分析器的过滤器链,新增shingle过滤器生成相邻词的组合,再用pattern_replace去掉空格得到连写token,最后用EdgeNGram生成补全需要的ngram。
这里是调整后的NEST代码:
var response = this.Client.CreateIndex("company-index", index => index .Mappings(ms => ms .Map<CompanyDocument>(m => m .Properties(p => p .Text(t => t .Name(n => n.CompanyName) .Analyzer("auto-complete") .Fields(ff => ff .Keyword(k => k.Name("keyword")) ) ) ) ) ) .Settings(f => f .Analysis(analysis => analysis .Analyzers(analyzers => analyzers .Custom("auto-complete", a => a .Tokenizer("standard") .Filters( "lowercase", "word-delimiter", "shingle-join", "remove-space", "auto-complete-filter" ) ) ) .TokenFilters(tokenFilter => tokenFilter // 处理单个token的大小写/数字拆分,同时生成连写形式(比如TransHex→transhex) .WordDelimiter("word-delimiter", t => t .SplitOnCaseChange(true) .CatenateAll(true) ) // 生成相邻词的组合(比如trans+hex→trans hex) .Shingle("shingle-join", s => s .MinShingleSize(2) .MaxShingleSize(2) .OutputUnigrams(true) // 保留原单个词,不影响正常空格搜索 ) // 把shingle生成的带空格组合变成连写形式(trans hex→transhex) .PatternReplace("remove-space", pr => pr .Pattern(@" ") .Replacement("") ) // 生成EdgeNGram用于自动补全,min_gram设为3减少无关匹配 .EdgeNGram("auto-complete-filter", t => t .MinGram(3) .MaxGram(30) ) ) ) ) );
为什么这个配置能解决问题?
拿Perfect Pools举例,索引时的处理流程:
standard分词器拆分出["Perfect", "Pools"]lowercase转成["perfect", "pools"]word-delimiter处理单个token(这里没有拆分,保持原token)shingle-join生成["perfect", "pools", "perfect pools"]remove-space把perfect pools变成perfectpools,最终token列表是["perfect", "pools", "perfectpools"]auto-complete-filter为每个token生成3到30长度的ngram(比如perfect生成per、perf...perfect;perfectpools生成per、perf...perfectpools)
当你搜索perfectpools时,分析器会把它处理成["perfectpools"],并生成对应的ngram,这些ngram会和索引里Perfect Pools生成的perfectpools的ngram匹配,从而正确返回目标结果。
另外,把min_gram设为3能避免大量短ngram带来的无关匹配,让搜索结果更精准。
内容的提问来源于stack exchange,提问作者R4nc1d




