Перейти к основному содержимому

Полнотекстовый поиск с Solr

· 7 мин. чтения

1. Обзор

В этой статье мы рассмотрим фундаментальную концепцию поисковой системы Apache Solr — полнотекстовый поиск.

Apache Solr — это платформа с открытым исходным кодом, предназначенная для работы с миллионами документов. Мы рассмотрим его основные возможности на примерах с использованием библиотеки Java — SolrJ .

2. Конфигурация Maven

Учитывая тот факт, что Solr с открытым исходным кодом — мы можем просто скачать бинарник и запустить сервер отдельно от нашего приложения.

Для связи с сервером мы определим зависимость Maven для клиента SolrJ:

<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>6.4.2</version>
</dependency>

Вы можете найти последнюю зависимость здесь .

3. Индексирование данных

Для индексации и поиска данных нам нужно создать ядро ; мы создадим один именованный элемент для индексации наших данных.

Прежде чем мы это сделаем, нам нужно проиндексировать данные на сервере, чтобы они стали доступными для поиска.

Есть много разных способов, которыми мы можем индексировать данные. Мы можем использовать обработчики импорта данных для импорта данных непосредственно из реляционных баз данных, загрузки данных с помощью Solr Cell с помощью Apache Tika или загрузки данных XML/XSLT, JSON и CSV с помощью обработчиков индексов.

3.1. Индексирование документа Solr

Мы можем индексировать данные в ядро , создав SolrInputDocument . Во-первых, нам нужно заполнить документ нашими данными, а затем вызвать API SolrJ только для индексации документа:

SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", id);
doc.addField("description", description);
doc.addField("category", category);
doc.addField("price", price);
solrClient.add(doc);
solrClient.commit();

Обратите внимание, что id , естественно, должен быть уникальным для разных элементов . Наличие идентификатора уже проиндексированного документа обновит этот документ.

3.2. Индексирующие компоненты

SolrJ предоставляет API для индексации Java-бинов. Чтобы проиндексировать bean-компонент, нам нужно аннотировать его аннотациями @Field :

public class Item {

@Field
private String id;

@Field
private String description;

@Field
private String category;

@Field
private float price;
}

Когда у нас есть bean-компонент, индексация выполняется прямо:

solrClient.addBean(item); 
solrClient.commit();

4. Solr-запросы

Поиск — самая мощная возможность Solr. После того, как мы проиндексировали документы в нашем репозитории, мы можем искать ключевые слова, фразы, диапазоны дат и т. д. Результаты сортируются по релевантности (оценке).

4.1. Основные запросы

Сервер предоставляет API для операций поиска. Мы можем вызвать обработчики запросов /select или /query .

Выполним простой поиск:

SolrQuery query = new SolrQuery();
query.setQuery("brand1");
query.setStart(0);
query.setRows(10);

QueryResponse response = solrClient.query(query);
List<Item> items = response.getBeans(Item.class);

SolrJ будет внутренне использовать основной параметр запроса q в своем запросе к серверу. Количество возвращаемых записей будет равно 10, проиндексировано с нуля, если начало и строки не указаны.

Приведенный выше поисковый запрос будет искать любые документы, содержащие полное слово «brand1» в любом из проиндексированных полей. Обратите внимание, что простой поиск не чувствителен к регистру.

Давайте посмотрим на другой пример. Мы хотим найти любое слово, содержащее «rand» , которое начинается с любого количества символов и заканчивается только одним символом. Мы можем использовать подстановочные знаки * и ? в нашем запросе:

query.setQuery("*rand?");

Запросы Solr также поддерживают логические операторы, как в SQL:

query.setQuery("brand1 AND (Washing OR Refrigerator)");

Все логические операторы должны быть написаны заглавными буквами; те, которые поддерживаются синтаксическим анализатором запросов, это AND , OR , NOT , + и – .

Более того, если мы хотим выполнять поиск по определенным полям, а не по всем проиндексированным полям, мы можем указать их в запросе:

query.setQuery("description:Brand* AND category:*Washing*");

4.2. фразовые запросы

До этого момента наш код искал ключевые слова в проиндексированных полях. Мы также можем выполнять поиск по фразам в проиндексированных полях:

query.setQuery("Washing Machine");

Когда у нас есть такая фраза, как « Стиральная машина », стандартный анализатор запросов Solr анализирует ее как « Стиральная машина ИЛИ машина ». Для поиска целой фразы мы можем только добавить выражение в двойные кавычки:

query.setQuery("\"Washing Machine\"");

Мы можем использовать поиск по близости, чтобы найти слова на определенном расстоянии . Если мы хотим найти слова, которые разделены хотя бы двумя словами, мы можем использовать следующий запрос:

query.setQuery("\"Washing equipment\"~2");

4.3. Запросы диапазона

Запросы диапазона позволяют получить документы, поля которых находятся между определенными диапазонами.

Допустим, мы хотим найти товары, цена которых колеблется от 100 до 300:

query.setQuery("price:[100 TO 300]");

Приведенный выше запрос найдет все элементы, цена которых находится в диапазоне от 100 до 300 включительно. Мы можем использовать « } » и « { », чтобы исключить конечные точки:

query.setQuery("price:{100 TO 300]");

4.4. Фильтровать запросы

Запросы с фильтрами можно использовать для ограничения расширенного набора результатов, которые могут быть возвращены. Запрос фильтра не влияет на оценку:

SolrQuery query = new SolrQuery();
query.setQuery("price:[100 TO 300]");
query.addFilterQuery("description:Brand1","category:Home Appliances");

Как правило, запрос фильтра будет содержать часто используемые запросы. Поскольку их часто можно использовать повторно, они кэшируются, чтобы сделать поиск более эффективным.

5. Фасетный поиск

Огранка помогает упорядочить результаты поиска по группам. Мы можем фасетировать поля, запросы или диапазоны.

5.1. Полевая огранка

Например, мы хотим получить агрегированное количество категорий в результатах поиска. Мы можем добавить поле категории в наш запрос:

query.addFacetField("category");

QueryResponse response = solrClient.query(query);
List<Count> facetResults = response.getFacetField("category").getValues();

FacetResults будет содержать количество каждой категории в результатах.

5.2. Границы запроса

Фасетирование запроса очень полезно, когда мы хотим вернуть количество подзапросов:

query.addFacetQuery("Washing OR Refrigerator");
query.addFacetQuery("Brand2");

QueryResponse response = solrClient.query(query);
Map<String,Integer> facetQueryMap = response.getFacetQuery();

В результате в facetQueryMap будет подсчитано число запросов фасетов.

5.3. Грань диапазона

Огранка диапазона используется для получения количества диапазонов в результатах поиска. Следующий запрос вернет количество диапазонов цен от 100 до 251 с промежутком 25:

query.addNumericRangeFacet("price", 100, 275, 25);

QueryResponse response = solrClient.query(query);
List<RangeFacet> rangeFacets = response.getFacetRanges().get(0).getCounts();

Помимо числовых диапазонов, Solr также поддерживает диапазоны дат, интервальные фасеты и сводные фасеты.

6. Нажмите «Выделение»

Мы можем захотеть, чтобы ключевые слова в нашем поисковом запросе были выделены в результатах. Это будет очень полезно, чтобы получить лучшее представление о результатах. Давайте проиндексируем некоторые документы и определим ключевые слова для выделения:

itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f);
itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f);
itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f);
itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f);

SolrQuery query = new SolrQuery();
query.setQuery("Appliances");
query.setHighlight(true);
query.addHighlightField("category");
QueryResponse response = solrClient.query(query);

Map<String, Map<String, List<String>>> hitHighlightedMap = response.getHighlighting();
Map<String, List<String>> highlightedFieldMap = hitHighlightedMap.get("hm0001");
List<String> highlightedList = highlightedFieldMap.get("category");
String highLightedText = highlightedList.get(0);

Мы получим highLightedText как «Домашняя <em>Техника</em>» . Обратите внимание, что ключевое слово для поиска « Бытовая техника » помечено тегом <em> . Тег выделения по умолчанию, используемый Solr, — <em> , но мы можем изменить его, установив теги pre и post :

query.setHighlightSimplePre("<strong>");
query.setHighlightSimplePost("</strong>");

7. Предложения по поиску

Одной из важных функций, которую поддерживает Solr, являются предложения. Если ключевые слова в запросе содержат орфографические ошибки или если мы хотим предложить автозаполнение ключевого слова для поиска, мы можем использовать функцию предложения.

7.1. Проверка орфографии

Стандартный обработчик поиска не включает компонент проверки орфографии; его нужно настраивать вручную. Есть три способа сделать это. Подробности конфигурации вы можете найти на официальной вики-странице . В нашем примере мы будем использовать IndexBasedSpellChecker , который использует проиндексированные данные для проверки орфографии ключевых слов.

Давайте найдем ключевое слово с орфографической ошибкой:

query.setQuery("hme");
query.set("spellcheck", "on");
QueryResponse response = solrClient.query(query);

SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse();
Suggestion suggestion = spellCheckResponse.getSuggestions().get(0);
List<String> alternatives = suggestion.getAlternatives();
String alternative = alternatives.get(0);

Ожидаемой альтернативой для нашего ключевого слова «hme» должно быть «home» , так как наш индекс содержит термин «home». Обратите внимание, что перед выполнением поиска необходимо активировать проверку орфографии .

7.2. Условия автоматического предложения

Мы можем захотеть получить предложения по неполным ключевым словам, чтобы облегчить поиск. Компонент предложения Solr должен быть настроен вручную. Подробности конфигурации вы можете найти на его официальной вики-странице .

Мы настроили обработчик запросов с именем /suggest для обработки предложений. Давайте получим предложения по ключевому слову «Хом» :

SolrQuery query = new SolrQuery();
query.setRequestHandler("/suggest");
query.set("suggest", "true");
query.set("suggest.build", "true");
query.set("suggest.dictionary", "mySuggester");
query.set("suggest.q", "Hom");
QueryResponse response = solrClient.query(query);

SuggesterResponse suggesterResponse = response.getSuggesterResponse();
Map<String,List<String>> suggestedTerms = suggesterResponse.getSuggestedTerms();
List<String> suggestions = suggestedTerms.get("mySuggester");

Список предложений должен содержать все слова и фразы. Обратите внимание, что в нашей конфигурации мы настроили подсказчик с именем mySuggester .

8. Заключение

Эта статья представляет собой краткое введение в возможности и функции поисковой системы Solr.

Мы коснулись многих функций, но это, конечно, только поверхностное представление о том, что мы можем сделать с помощью продвинутого и зрелого поискового сервера, такого как Solr.

Используемые здесь примеры доступны, как всегда, на GitHub .