Lucene: фразы из нескольких слов в качестве поисковых терминов
Я пытаюсь сделать доступный для поиска телефон / местный бизнес-справочник, используя Apache Lucene.
У меня есть поля для названия улицы, названия компании, номера телефона и т. Д. Проблема, с которой я сталкиваюсь, заключается в том, что когда я пытаюсь выполнить поиск по улице, где название улицы содержит несколько слов (например, "полумесяц"), результаты не возвращаются. Но если я попытаюсь выполнить поиск только одним словом, например, "полумесяц", я получу все результаты, которые мне нужны.
Я индексирую данные с помощью следующего:
String LocationOfDirectory = "C:\\dir\\index";
StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_34);
Directory Index = new SimpleFSDirectory(LocationOfDirectory);
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE.34, analyzer);
IndexWriter w = new IndexWriter(index, config);
Document doc = new Document();
doc.add(new Field("Street", "the crescent", Field.Store.YES, Field.Index.Analyzed);
w.add(doc);
w.close();
Мои поиски работают так:
int numberOfHits = 200;
String LocationOfDirectory = "C:\\dir\\index";
TopScoreDocCollector collector = TopScoreDocCollector.create(numberOfHits, true);
Directory directory = new SimpleFSDirectory(new File(LocationOfDirectory));
IndexSearcher searcher = new IndexSearcher(IndexReader.open(directory);
WildcardQuery q = new WildcardQuery(new Term("Street", "the crescent");
searcher.search(q, collector);
ScoreDoc[] hits = collector.topDocs().scoreDocs;
Я попытался поменять местами шаблонный запрос для запроса фразы, сначала со всей строкой, а затем разделить строку на пустое пространство и обернуть их в BooleanQuery следующим образом:
String term = "the crescent";
BooleanQuery b = new BooleanQuery();
PhraseQuery p = new PhraseQuery();
String[] tokens = term.split(" ");
for(int i = 0 ; i < tokens.length ; ++i)
{
p.add(new Term("Street", tokens[i]));
}
b.add(p, BooleanClause.Occur.MUST);
Однако это не сработало. Я попытался использовать KeywordAnalyzer вместо StandardAnalyzer, но затем все другие типы поиска перестали работать. Я попытался заменить пробелы другими символами (+ и @) и преобразовать запросы в и из этой формы, но это все равно не работает. Я думаю, что это не работает, потому что + и @ являются специальными символами, которые не индексируются, но я не могу найти список где-нибудь, какие символы такие.
Я начинаю немного сходить с ума, кто-нибудь знает, что я делаю не так?
Спасибо Рик
5 ответов
Я обнаружил, что моя попытка сгенерировать запрос без использования QueryParser не сработала, поэтому я перестал пытаться создавать свои собственные запросы и вместо этого использовал QueryParser. Все рекомендации, которые я видел в Интернете, показали, что вы должны использовать тот же Analyzer в QueryParser, который вы используете во время индексации, поэтому я использовал StandardAnalyzer для создания QueryParser.
Это работает в этом примере, потому что StandardAnalyzer удаляет слово "the" с улицы "crescent" во время индексации, и, следовательно, мы не можем искать его, потому что его нет в индексе.
Однако, если мы решим выполнить поиск "Дорога в роще", у нас возникнет проблема с готовой функциональностью, а именно, что запрос вернет все результаты, содержащие либо "Грув", либо "Дорога". Это легко исправить, настроив QueryParser так, чтобы его операцией по умолчанию было И вместо ИЛИ.
В итоге правильное решение было следующим:
int numberOfHits = 200;
String LocationOfDirectory = "C:\\dir\\index";
TopScoreDocCollector collector = TopScoreDocCollector.create(numberOfHits, true);
Directory directory = new SimpleFSDirectory(new File(LocationOfDirectory));
IndexSearcher searcher = new IndexSearcher(IndexReader.open(directory);
StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_35);
//WildcardQuery q = new WildcardQuery(new Term("Street", "the crescent");
QueryParser qp = new QueryParser(Version.LUCENE_35, "Street", analyzer);
qp.setDefaultOperator(QueryParser.Operator.AND);
Query q = qp.parse("grove road");
searcher.search(q, collector);
ScoreDoc[] hits = collector.topDocs().scoreDocs;
Причина, по которой вы не получаете свои документы обратно, заключается в том, что при индексации вы используете StandardAnalyzer
, который преобразует токены в нижний регистр и удаляет стоп-слова. Таким образом, единственный термин, который индексируется для вашего примера, это "полумесяц". Однако подстановочные запросы не анализируются, поэтому в качестве обязательной части запроса указывается "the". То же самое касается запросов фраз в вашем сценарии.
KeywordAnalyzer
вероятно, не очень подходит для вашего варианта использования, потому что он принимает содержимое всего поля как один токен. Ты можешь использовать SimpleAnalyzer
для поля улицы - разделит ввод на все не-буквенные символы, а затем преобразует их в строчные. Вы также можете рассмотреть возможность использования WhitespaceAnalyzer
с LowerCaseFilter
, Вам нужно попробовать разные варианты и решить, что лучше всего подходит для ваших данных и пользователей.
Кроме того, вы можете использовать различные анализаторы для каждого поля (например, с PerFieldAnalyzerWrapper
) если изменение анализатора для этого поля прерывает другие поиски.
Решение @RikSaunderson для поиска документов, в которых должны выполняться все подзапросы запроса, все еще работает с Lucene 9.
QueryParser queryParser = new QueryParser(LuceneConstants.CONTENTS, new StandardAnalyzer());
queryParser.setDefaultOperator(QueryParser.Operator.AND);
Если вы хотите, чтобы точные слова соответствовали улице, вы можете установить поле "Улица" NOT_ANALYZED, которое не будет фильтровать стоп-слово "the".
doc.add(new Field("Street", "the crescent", Field.Store.YES, Field.Index.Not_Analyzed);
Нет необходимости использовать какие-либо Analyzer
здесь Coz Hibernate неявно использует StandardAnalyzer
который разделит слова на основе white spaces
поэтому решение здесь установлено Analyze
в NO
он будет автоматически выполнять Multi Phrase Search
@Column(name="skill")
@Field(index=Index.YES, analyze=Analyze.NO, store=Store.NO)
@Analyzer(definition="SkillsAnalyzer")
private String skill;