在Lucene中搜索产品代码、电话号码

bmvo0sr5  于 2022-11-07  发布在  Lucene
关注(0)|答案(1)|浏览(204)

我正在寻找如何在ApacheLucene8.x中搜索标识符、产品代码或电话号码的一般建议。(如ISBN,例如978-3-86680-192-9)。如果有人输入9783978 3978-3,则会出现978-3-86680-192-9。如果标识符使用字母、空格数字、标点符号(例如:TS 123123.abc。我该怎么做?
我想我可以用一个自定义的分析器来解决这个问题,它可以删除所有的标点符号和空格,但是结果是好坏参半的:

public class IdentifierAnalyzer extends Analyzer {
    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        Tokenizer tokenizer = new KeywordTokenizer();
        TokenStream tokenStream = new LowerCaseFilter(tokenizer);
        tokenStream = new PatternReplaceFilter(tokenStream, Pattern.compile("[^0-9a-z]"), "", true);
        tokenStream = new TrimFilter(tokenStream);
        return new TokenStreamComponents(tokenizer, tokenStream);
    }

    @Override
    protected TokenStream normalize(String fieldName, TokenStream in) {
        TokenStream tokenStream = new LowerCaseFilter(in);
        tokenStream = new PatternReplaceFilter(tokenStream, Pattern.compile("[^0-9a-z]"), "", true);
        tokenStream = new TrimFilter(tokenStream);
        return tokenStream;
    }
}

因此,当我用TS1*执行PrefixQuery时,虽然我得到了想要的结果,但TS 1*(带空格)并没有产生令人满意的结果。myField:TS myField:1*WordDelimiterGraphFilter看起来很有趣,但我不知道在这里应用它。

hyrbngr7

hyrbngr71#

这不是一个全面的答案--但是我同意WordDelimiterGraphFilter可能对这种类型的数据有帮助。
下面是我的自定义分析器,使用WordDelimiterGraphFilter

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import org.apache.lucene.analysis.core.LowerCaseFilter;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilterFactory;
import java.util.Map;
import java.util.HashMap;

public class IdentifierAnalyzer extends Analyzer {

    private WordDelimiterGraphFilterFactory getWordDelimiter() {
        Map<String, String> settings = new HashMap<>();
        settings.put("generateWordParts", "1");   // e.g. "PowerShot" => "Power" "Shot"
        settings.put("generateNumberParts", "1"); // e.g. "500-42" => "500" "42"
        settings.put("catenateAll", "1");         // e.g. "wi-fi" => "wifi" and "500-42" => "50042"
        settings.put("preserveOriginal", "1");    // e.g. "500-42" => "500" "42" "500-42"
        settings.put("splitOnCaseChange", "1");   // e.g. "fooBar" => "foo" "Bar"
        return new WordDelimiterGraphFilterFactory(settings);
    }

    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        Tokenizer tokenizer = new KeywordTokenizer();
        TokenStream tokenStream = new LowerCaseFilter(tokenizer);
        tokenStream = getWordDelimiter().create(tokenStream);
        return new TokenStreamComponents(tokenizer, tokenStream);
    }

    @Override
    protected TokenStream normalize(String fieldName, TokenStream in) {
        TokenStream tokenStream = new LowerCaseFilter(in);
        return tokenStream;
    }

}

它使用WordDelimiterGraphFilterFactory辅助对象以及参数Map来控制要应用的设置。
您可以在WordDelimiterGraphFilterFactoryJavaDoc中查看可用设置的完整列表。您可能需要尝试设置/取消设置不同的设置。
下面是一个用于以下3个输入值的测试索引生成器:
第一个
这将创建以下标记:

为了查询上面的索引数据,我使用了以下代码:

public static void doSearch() throws IOException, ParseException {
    Analyzer analyzer = new IdentifierAnalyzer();
    QueryParser parser = new QueryParser("identifiers", analyzer);

    List<String> searches = Arrays.asList("9783", "9783*", "978 3", "978-3", "TS1*", "TS 1*");

    for (String search : searches) {
        Query query = parser.parse(search);
        printHits(query, search);
    }
}

private static void printHits(Query query, String search) throws IOException {
    System.out.println("search term: " + search);
    System.out.println("parsed query: " + query.toString());
    IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(INDEX_PATH)));
    IndexSearcher searcher = new IndexSearcher(reader);
    TopDocs results = searcher.search(query, 100);
    ScoreDoc[] hits = results.scoreDocs;
    System.out.println("hits: " + hits.length);
    for (ScoreDoc hit : hits) {
        System.out.println("");
        System.out.println("  doc id: " + hit.doc + "; score: " + hit.score);
        Document doc = searcher.doc(hit.doc);
        System.out.println("  identifier: " + doc.get("identifiers"));
    }
    System.out.println("-----------------------------------------");
}

这使用了以下搜索词--我将所有这些词都传递给了经典的查询解析器(当然,您也可以通过API使用更复杂的查询类型):

9783
9783*
978 3
978-3
TS1*
TS 1*

第一个查询没有找到任何匹配的文档:

search term: 9783
parsed query: identifiers:9783
hits: 0

这并不奇怪,因为这是一个不完整的标记,没有通配符。第二个查询(添加了通配符)如预期的那样找到了一个文档。
我测试的最后一个查询TS 1*找到了三个匹配项--但是我们想要的那个具有最佳匹配分数:

search term: TS 1*
parsed query: identifiers:ts identifiers:1*
hits: 3

  doc id: 1; score: 1.590861
  identifier: TS 123

  doc id: 0; score: 1.0
  identifier: 978-3-86680-192-9

  doc id: 2; score: 1.0
  identifier: 123.abc

相关问题