我正在使用雪球分析器来提取多个文档的标题。一切都运行良好,但有一些怪癖。
示例:
搜索“valv”、“valve”或“valves”会返回相同数量的结果。这是有道理的,因为雪球分析器将所有内容减少到“valv”。
我在使用通配符时遇到问题。搜索“valve*”或“valve*”不会返回任何结果。搜索“valv*”按预期工作。
我明白为什么会发生这种情况,但我不知道如何解决。
我考虑编写一个分析器来存储词干和非词干标记。基本上应用两个分析器并组合两个令牌流。但我不确定这是否是一个实用的解决方案。
我也考虑过使用 AnalyzingQueryParser,但我不知道如何将其应用于多字段查询。此外,在搜索“valve*”时,使用 AnalyzingQueryParser 会返回“valve”的结果,但这不是预期的行为。
是否有一种同时使用通配符和词干算法的“首选”方式?
我之前用过两种不同的方法来解决这个问题
使用两个字段,一个包含词干术语,另一个包含由
StandardAnalyzer
生成的术语。当您解析搜索查询时,如果它是“标准”字段中的通配符搜索,如果不是,则使用带有词干术语的字段。如果您让用户直接在 Lucene 的 QueryParser 中输入查询,这可能会更难使用。编写自定义分析器并索引重叠标记。它基本上包括使用
PositionIncrementAttribute
在索引中的同一位置对原始术语和词干进行索引。您可以查看 SynonymFilter 来获取一些如何正确使用 PositionIncrementAttribute
的示例。 我更喜欢解决方案#2。
这是最简单的解决方案,并且可行 -
在“索引”分析器中添加 solr.KeywordRepeatFilterFactory。
还在“索引”分析器的末尾添加RemoveDuplicatesTokenFilterFactory
现在,在您的索引中,您将始终在同一位置上拥有每个标记的词干形式和非词干形式,您就可以开始了。
我认为没有一种简单(且正确)的方法可以做到这一点。
我的解决方案是编写一个自定义查询解析器,用于查找索引中的术语和搜索条件所共有的最长字符串。
class MyQueryParser : Lucene.Net.QueryParsers.QueryParser
{
IndexReader _reader;
Analyzer _analyzer;
public MyQueryParser(string field, Analyzer analyzer,IndexReader indexReader) : base(field, analyzer)
{
_analyzer = analyzer;
_reader = indexReader;
}
public override Query GetPrefixQuery(string field, string termStr)
{
for(string longestStr = termStr; longestStr.Length>2; longestStr = longestStr.Substring(0,longestStr.Length-1))
{
TermEnum te = _reader.Terms(new Term(field, longestStr));
Term term = te.Term();
te.Close();
if (term != null && term.Field() == field && term.Text().StartsWith(longestStr))
{
return base.GetPrefixQuery(field, longestStr);
}
}
return base.GetPrefixQuery(field, termStr);
}
}
您还可以尝试在
GetPrefixQuery
中调用您的分析器,这不是为 PrefixQuery
s 调用的
TokenStream ts = _analyzer.TokenStream(field, new StringReader(termStr));
Lucene.Net.Analysis.Token token = ts.Next();
var termstring = token.TermText();
ts.Close();
return base.GetPrefixQuery(field, termstring);
但是请注意,您总能找到返回结果不正确的情况。这就是为什么 Lucene 在使用通配符时不考虑分析器。
除了其他答案之外,我唯一的潜在想法是对两个字段使用 dismax,这样你就可以设置两个字段的相对权重。唯一需要注意的是,某些版本的 dismax 不处理通配符,并且某些解析器是 Solr 特定的。
另一个运行时解决方案是在搜索之前通过查询解析器处理搜索词。像这样的东西:
检测通配符术语并删除通配符
searchTerm = "阀门*".Replace("*", ""); ->“阀门”
通过查询解析器传递搜索词
searchTerm = queryParser.Parse(searchTerm).ToString().Split(':')[1]; ->“阀门”
在搜索词中添加通配符:
搜索词 = 搜索词 + "*";