Материал по работе с Apache Lucene и созданию простейшего нечёткого поиска

Пост расcчитан на начинающих, на людей незнакомых с технологией Apache Lucene. В нем нет материала о том, как устроен Apache Lucene внутри, какие алгоритмы, структуры данных и методы использовались для создания фреймворка. Пост является обучающим материалом-тизером, написанным для того, чтобы показать, как организовать простейший нечёткий поиск по тексту.
В качестве материала для обучения предоставлен код на github, сам пост в качестве документации и немного данных для тестирования поисковых запросов.
Введение
Подробно о библиотеке Apache Lucene написано здесь и здесь. В статье будут встречаться такие термины как: запрос, индексация, анализатор, нечеткие совпадения, токены, документы. Советую сначала прочитать вот эту статью. В ней эти термины описывают в контексте фреймворка Elasticsearch, который базируется на библиотеках Apache Lucene. Поэтому базовая терминология и определения совпадают.
Инструментарий
В статье описывается использование Apache Lucene 5.4.1. Исходный код доступен на github, в репозитории есть небольшой набор данных для тестирования. По сути статья является подробной документацией к коду в репозитории. Начать «играть» с проектом можно с запуска тестов в классе BasicSearchExamplesTest.
Создание индексов
Проиндексировать документы можно с помощью класса MessageIndexer. В нём есть метод index:
public void index(final Boolean create, List documents) throws IOException
Он принимает на вход переменную create и documents. Переменная create отвечает за поведение индексатора. Если она равна true, то индексатор будет создавать новый индекс даже если индекс уже существовал. Если false, то индекс будет обновляться.
Переменная documents это список объектов Document. Document это объект индексации и поиска. Он представляет собой набор полей, каждое поле имеет имя и текстовое значение. Для того чтобы получить список документов создан класс MessageToDocument. Его задача создавать Document используя два строковых поля: body и title.
public static Document createWith(final String titleStr, final String bodyStr) < final Document document = new Document(); final FieldType textIndexedType = new FieldType(); textIndexedType.setStored(true); textIndexedType.setIndexOptions(IndexOptions.DOCS); textIndexedType.setTokenized(true); //index title Field title = new Field("title", titleStr, textIndexedType); //index body Field body = new Field("body", bodyStr, textIndexedType); document.add(title); document.add(body); return document; >
Обратите внимание что метод index по умолчанию использует RussianAnalyzer, доступный в библиотеке lucene-analyzers-common.
Для того чтобы поиграть с созданием индекса перейдите к классу MessageIndexerTest.
Поиск
Для демонстрации базовых возможностей поиска создан класс BasicSearchExamples. В нём реализованы два метода поиска: простой поиск по токенам и нечеткий поиск. За простой поиск отвечают методы searchIndexWithTermQuery() и searchInBody(), за нечеткий поиск метод fuzzySearch().
В Lucene существует много способов создать запрос, но для простоты методы обычного поиска реализованы только с помощью классов QueryParser и TermQuery. Методы нечеткого поиска используют FuzzyQuery, которая зависит от одного важного параметра: maxEdits. Этот параметр отвечает за нечеткость поиска, подробности здесь. Грубо говоря, чем он больше, тем более расплывчатым/нечетким будет поиск. Погрузиться в многообразие способов сделать запрос можно здесь.
Для того чтобы поиграть с поиском перейдите к классу BasicSearchExamplesTest
Задание
Чтобы играть с проектом было не скучно попробуйте выполнить несколько заданий:
- Сделайте интерактивный консольный поиск. Поиск должен показывать выдачу и спрашивать следующий запрос.
- Сейчас поиск работает только с полем body. Сделайте так, чтобы поиск работал по полям title и body одновременно.
- Подсчитайте количество проиндексированных слов (токенов)
- Расширьте модель Message, добавьте в неё регион (region) и дату создания сообщения (creationDate). Не забудьте добавить новые поля для индексации в классе MessageToDocument. Добавьте новые способы поиска с фильтром по региону и дате
- Посмотрите на класс запросов MoreLikeThisQuery. Попробуйте сгруппировать все документы по похожести используя значение score.
- Скачайте вот этот файл, в нем около 5000 различных сообщений. Проверьте как работает группировка, новые запросы и фильтры.
Заключение
Преимущество Apache Lucene в его простоте, высокой скорости работы и низких требованиях к ресурсам. Недостаток в отсутствии хорошей документации, особенно на русском языке. Проект очень быстро развивается, поэтому книги, туториалы и Q/A, которыми забит интернет, давно потеряли актуальность. К примеру, у меня ушло 4-5 дней только на то, чтобы понять, как вытащить векторную модель TF-IDF из индексов Lucene. Надеюсь что этот пост привлечет внимание специалистов к этой проблеме недостатка информации.
Для тех же кто хочет погрузиться в мир Apache Lucene советую взглянуть на документацию Elasticsearch. Многие вещи там очень хорошо описаны, со ссылками на авторитетные источники и с примерами.
Оффтоп
Это мой первый более или менее серьезный пост. Поэтому прошу высказывать критику, отзывы и предложения. Я мог бы написать еще несколько статей, так как сейчас вплотную работаю с Apache Lucene.
Lucene
The Apache Lucene — это свободная библиотека для высокоскоростного полнотекстового поиска, написанная на Java. Может быть использована для поиска в интернете и других областях компьютерной лингвистики (аналитическая философия).
Основные возможности
- Масштабируемая и высокоскоростная индексация
- свыше 95GB в час на современном оборудовании
- требуется малый объем RAM — «heap» всего 1MB
- размер индекса примерно 20-30 % от размера исходного текста
- Мощный, точный и эффективный поисковый алгоритм
- ранжированный поиск — лучшие результаты показываются первыми
- множество мощных типов запросов: запрос фразы, wildcard запросы, поиск интервалов и т. д.
- поиск основанный на «полях» (таких как, заголовок, автор, текст)
- возможность сортировать по различным полям
- multiple-index поиск с возможностью объединения результатов
- возможность одновременного поиска и обновления индекса
- кроссплатформное решение
- исходный код полностью написан на Java
- наличие портов на другие языки программирования
Порты на другие языки
Lucene портирована на другие языки программирования:
Ссылки
- The Apache Lucene
- Lucene API
- Lucene FAQ
- Формат файлов
- Как начать
- Синтаксис запросов
- Scoring
- Lucene Wiki
- Краткий курс на русском
- Solr-Lucene FAQ
Литература
- Erik Hatcher and Otis Gospodnetic, «Lucene in Action», Manning, ISBN 1-932394-28-1
Apache Commons BCEL • BSF • JCS Apache Lucene Lucene Java • Lucene4c • Lucy• Solr Apache DB Derby • Torque • DdlUtils • OJB • JDO Ask.com (Ask Jeeves, механизм Teoma) • Blekko • Cuil (закрыт) • DuckDuckGo • Exalead • Gigablast • Google • Bing (Live Search/MSN Search) • Yahoo! Search (Inktomi • AltaVista • Alltheweb) • Яндекс
AskNet.ru • Brainboost • Clusty • Dogpile • FarSEER • exactus.ru • Excite • HotBot • Info.com • Ixquick • Krozilo • Mamma • Metacrawler • MetaLib • Нигма • Myriad Search • SideStep • Surfwax • Turbo10 • WebCrawler • GlobalFileSearch
DataparkSearch • Egothor • Gonzui • Grub • Ht://dig • locust • Isearch • Lucene • Lemur Toolkit & Indri Search Engine • mnoGoSearch • Namazu • Nutch • OpenFTS • Sciencenet (научная, на технологии YaCy) • Wikia Search • Sphinx • SWISH-E • Terrier Search Engine • Xapian • YaCy • Zettair
AGAKIDS (Россия) • Ask Kids (Великобритания) • Frag Finn (Германия) • Kids AOL (США) • Kids Yahoo! (США) • Quintura Дети (Россия) • Семейный Яндекс (Россия) • Гогуль (Россия)
- Программное обеспечение по алфавиту
- Поисковые системы
- Свободные библиотеки программ
- Библиотеки C
- Библиотеки C++
- Модули Perl
- Библиотеки PHP
Wikimedia Foundation . 2010 .
Введение в Apache Lucene
Apache Lucene — это полнотекстовый поисковый движок, который можно использовать с различными языками программирования.
В этой статье мы попытаемся понять основные концепции библиотеки и создать простое приложение.
2. Настройка Мавена
Для начала давайте сначала добавим необходимые зависимости:
dependency> groupId>org.apache.lucenegroupId> artifactId>lucene-coreartifactId> version>7.1.0version> dependency>Последнюю версию можно найти здесь .
Также для разбора наших поисковых запросов нам понадобится:
dependency> groupId>org.apache.lucenegroupId> artifactId>lucene-queryparserartifactId> version>7.1.0version> dependency>Проверьте наличие последней версии здесь .
3. Основные концепции
3.1. Индексация
Проще говоря, Lucene использует «обратное индексирование» данных — вместо сопоставления страниц с ключевыми словами ключевые слова сопоставляются со страницами точно так же, как глоссарий в конце любой книги.
Это позволяет быстрее получать ответы при поиске, поскольку поиск выполняется по индексу, а не по тексту напрямую.
3.2. Документы
Здесь документ представляет собой набор полей, и каждое поле имеет связанное с ним значение.
Индексы обычно состоят из одного или нескольких документов, а результаты поиска представляют собой наборы наиболее подходящих документов.
Это не всегда простой текстовый документ, это также может быть таблица базы данных или коллекция.
3.3. Поля
Документы могут иметь данные поля, где поле обычно представляет собой ключ, содержащий значение данных:
title: Goodness of Tea body: Discussing goodness of drinking herbal tea.Обратите внимание, что здесь title и body являются полями, и их можно искать вместе или по отдельности.
3.4. Анализ
Анализ преобразует данный текст в более мелкие и точные единицы для облегчения поиска.
Текст проходит различные операции по извлечению ключевых слов, удалению общеупотребительных слов и знаков препинания, переводу слов в нижний регистр и т. д.
Для этого есть несколько встроенных анализаторов:
- StandardAnalyzer — анализирует на основе базовой грамматики, удаляет стоп-слова, такие как «a», «an» и т. д. Также преобразует в нижний регистр
- SimpleAnalyzer — разбивает текст на основе символов без букв и преобразует в нижний регистр.
- WhiteSpaceAnalyzer — разбивает текст на основе пробелов
Мы также можем использовать и настраивать больше анализаторов.
3.5. Идет поиск
После того, как индекс построен, мы можем искать этот индекс, используя Query и IndexSearcher. Результатом поиска обычно является набор результатов, содержащий извлеченные данные.
Обратите внимание, что IndexWritter отвечает за создание индекса, а IndexSearcher — за поиск в индексе.
3.6. Синтаксис запроса
Lucene предоставляет очень динамичный и простой в написании синтаксис запросов.
Чтобы найти свободный текст, мы просто использовали текстовую строку в качестве запроса.
Для поиска текста в определенном поле мы будем использовать:
fieldName:text eg: title:teatimestamp:[1509909322,1572981321]Мы также можем искать с помощью подстановочных знаков:
dri?nkбудет искать один символ вместо подстановочного знака «?»
ищет слова, начинающиеся с «d» и заканчивающиеся на «k», с несколькими символами между ними.
найдет слова, начинающиеся с «uni».
Мы также можем комбинировать эти запросы и создавать более сложные запросы. И включите логический оператор, такой как И, НЕ, ИЛИ:
title: "Tea in breakfast" AND "coffee"Подробнее о синтаксисе запросов здесь .
4. Простое приложение
Давайте создадим простое приложение и проиндексируем некоторые документы.
Во-первых, мы создадим индекс в памяти и добавим в него несколько документов:
... Directory memoryIndex = new RAMDirectory(); StandardAnalyzer analyzer = new StandardAnalyzer(); IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); IndexWriter writter = new IndexWriter(memoryIndex, indexWriterConfig); Document document = new Document(); document.add(new TextField("title", title, Field.Store.YES)); document.add(new TextField("body", body, Field.Store.YES)); writter.addDocument(document); writter.close();Здесь мы создаем документ с TextField и добавляем их в индекс с помощью IndexWriter. Третий аргумент в конструкторе TextField указывает, должно ли также сохраняться значение поля.
Анализаторы используются для разделения данных или текста на куски, а затем отфильтровывают из них стоп-слова. Стоп-слова — это такие слова, как «а», «ам», «есть» и т. д. Они полностью зависят от данного языка.
Далее создадим поисковый запрос и поищем в индексе добавленный документ:
public ListDocument> searchIndex(String inField, String queryString) Query query = new QueryParser(inField, analyzer) .parse(queryString); IndexReader indexReader = DirectoryReader.open(memoryIndex); IndexSearcher searcher = new IndexSearcher(indexReader); TopDocs topDocs = searcher.search(query, 10); ListDocument> documents = new ArrayList>(); for (ScoreDoc scoreDoc : topDocs.scoreDocs) documents.add(searcher.doc(scoreDoc.doc)); > return documents; >В методе search() второй целочисленный аргумент указывает, сколько лучших результатов поиска он должен вернуть.
Теперь давайте проверим это:
@Test public void givenSearchQueryWhenFetchedDocumentThenCorrect() InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer()); inMemoryLuceneIndex.indexDocument("Hello world", "Some hello world"); ListDocument> documents = inMemoryLuceneIndex.searchIndex("body", "world"); assertEquals( "Hello world", documents.get(0).get("title")); >Здесь мы добавляем в индекс простой документ с двумя полями «заголовок» и «тело», а затем пытаемся найти его с помощью поискового запроса.
6. Запросы Lucene
Поскольку мы уже знакомы с основами индексации и поиска, давайте копнем немного глубже.
В предыдущих разделах мы рассмотрели базовый синтаксис запроса и то, как преобразовать его в экземпляр Query с помощью метода QueryParser.
Lucene также предоставляет различные конкретные реализации:
6.1. TermQuery
Термин — это базовая единица для поиска, содержащая имя поля вместе с текстом для поиска.
TermQuery — самый простой из всех запросов, состоящий из одного термина:
@Test public void givenTermQueryWhenFetchedDocumentThenCorrect() InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer()); inMemoryLuceneIndex.indexDocument("activity", "running in track"); inMemoryLuceneIndex.indexDocument("activity", "Cars are running on road"); Term term = new Term("body", "running"); Query query = new TermQuery(term); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query); assertEquals(2, documents.size()); >6.2. ПрефиксЗапрос
Чтобы найти документ со словом «начинается с»:
@Test public void givenPrefixQueryWhenFetchedDocumentThenCorrect() InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer()); inMemoryLuceneIndex.indexDocument("article", "Lucene introduction"); inMemoryLuceneIndex.indexDocument("article", "Introduction to Lucene"); Term term = new Term("body", "intro"); Query query = new PrefixQuery(term); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query); assertEquals(2, documents.size()); >6.3. Подстановочный знак
Как следует из названия, мы можем использовать подстановочные знаки «*» или «?». для поиска:
// . Term term = new Term("body", "intro*"); Query query = new WildcardQuery(term); // .6.4. Фразезапрос
Он используется для поиска последовательности текстов в документе:
// . inMemoryLuceneIndex.indexDocument( "quotes", "A rose by any other name would smell as sweet."); Query query = new PhraseQuery( 1, "body", new BytesRef("smell"), new BytesRef("sweet")); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query); // .Обратите внимание, что первый аргумент конструктора PhraseQuery называется slop и представляет собой расстояние в количестве слов между сопоставляемыми терминами.
6.5. нечеткий запрос
Мы можем использовать это при поиске чего-то похожего, но не обязательно идентичного:
// . inMemoryLuceneIndex.indexDocument("article", "Halloween Festival"); inMemoryLuceneIndex.indexDocument("decoration", "Decorations for Halloween"); Term term = new Term("body", "hallowen"); Query query = new FuzzyQuery(term); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query); // .Мы попытались найти текст «Хэллоуин», но с ошибкой в написании «хэллоуин».
6.6. логический запрос
Иногда нам может понадобиться выполнить сложный поиск, объединяя два или более разных типа запросов:
// . inMemoryLuceneIndex.indexDocument("Destination", "Las Vegas singapore car"); inMemoryLuceneIndex.indexDocument("Commutes in singapore", "Bus Car Bikes"); Term term1 = new Term("body", "singapore"); Term term2 = new Term("body", "car"); TermQuery query1 = new TermQuery(term1); TermQuery query2 = new TermQuery(term2); BooleanQuery booleanQuery = new BooleanQuery.Builder() .add(query1, BooleanClause.Occur.MUST) .add(query2, BooleanClause.Occur.MUST) .build(); // .7. Сортировка результатов поиска
Мы также можем сортировать документы результатов поиска по определенным полям:
@Test public void givenSortFieldWhenSortedThenCorrect() InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer()); inMemoryLuceneIndex.indexDocument("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument("Mekong", "This river flows in south Asia"); inMemoryLuceneIndex.indexDocument("Amazon", "Rain forest river"); inMemoryLuceneIndex.indexDocument("Rhine", "Belongs to Europe"); inMemoryLuceneIndex.indexDocument("Nile", "Longest River"); Term term = new Term("body", "river"); Query query = new WildcardQuery(term); SortField sortField = new SortField("title", SortField.Type.STRING_VAL, false); Sort sortByTitle = new Sort(sortField); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query, sortByTitle); assertEquals(4, documents.size()); assertEquals("Amazon", documents.get(0).getField("title").stringValue()); >Мы попытались отсортировать полученные документы по полям заголовков, которые являются названиями рек. Логический аргумент конструктора SortField предназначен для изменения порядка сортировки.
8. Удалить документы из индекса
Попробуем удалить некоторые документы из индекса на основе заданного Term:
// . IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(memoryIndex, indexWriterConfig); writer.deleteDocuments(term); // .Мы проверим это:
@Test public void whenDocumentDeletedThenCorrect() InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer()); inMemoryLuceneIndex.indexDocument("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument("Mekong", "This river flows in south Asia"); Term term = new Term("title", "ganges"); inMemoryLuceneIndex.deleteDocument(term); Query query = new TermQuery(term); ListDocument> documents = inMemoryLuceneIndex.searchIndex(query); assertEquals(0, documents.size()); >9. Заключение
Эта статья была кратким введением в начало работы с Apache Lucene. Также мы выполнили различные запросы и отсортировали полученные документы.
Как всегда, код примеров можно найти на Github .
Exploring Java: Lucene
Lucene – это open-source библиотека для полнотекстового поиска. К сожалению, документация к нему не всегда хорошо организована, и новичку бывает сложно сориентироваться среди десятков новых понятий и названий. В этом посте я попробую немного исправить ситуацию, вкратце описав, что есть что в мире Lucene.
Apache Lucene и смежные проекты
Сам по себе Lucene – это Java-библиотека, однако если вы не пишите для JVM, не спешите закрывать статью. Во-первых, существует огромное количество портов этой библиотеки на другие языки программирования. В частности, для .NET есть class-per-class порт, который так и называется – Lucene.net. Во-вторых, если вы не хотите мучиться с тонкостями API, Apache предлагает готовый поисковый сервер – Solr. Если вы создаёте сайт на PHP и просто хотите добавить в него эффективный поиск а-ля Google, то вероятнее всего Solr – это именно то, что вам нужно. Нельзя не упомянуть и о самом «навороченном» проекте из ветки Lucene — Nutch. Этот проект представляет из себя синтез Solr и великого и ужасного Hadoop — распределённой файловой системы со встроенным MapReduce. Проще говоря, Nutch переносит возможности Solr’а с одного сервера на целый кластер, так что если ваша цель – создать мини-гугль, то вы попали по адресу. Тем более что кроме собственно поиска Nutch включает в себя свой собственный crawler и ряд парсеров для наиболее популярных форматов документов (HTML, PDF, DOC и др.). Так или иначе, все эти проекты построены вокруг одной архитектуры, имеют единый язык запросов и схожие компоненты. Обо всём этом и пойдёт речь далее.
Как работают поисковые движки
В Information Retrieval (IR) встречаются различные задачи, связанные с поиском по тексту, однако нас будет интересовать только одна – поиск наиболее релевантных документов по набору ключевых слов. Такой вид поиска обычно называют полнотекстовым. Важно понимать, что IR не сводится только к полнотекстовому поиску, и движки вроде Lucene могут оказаться бесполезны для других задач, таких задач, как, например, поиск подстроки в строке. Задача эффективного полнотекстового поиска делится на две части: индексирование и собственно поиск. Обе эти стадии мы ещё обсудим ниже, но вначале поговорим о том, что находится между ними – о хранилище. Хранилище в большинстве современных поисковых движков организовано в виде так называемого обратного индекса (inverted index). Обратный индекс организован аналогично предметному указателю в конце книги – каждому слову соответствует список документов, в которых он встречается (если быть более точным, список ID этих документов):

Такая структура позволяет практически за константное время извлекать список документов, в которых встречается определённое слово. Сам список слов организован в виде либо префиксного дерева, либо хеш-мэпа. Кроме собственно номеров документов для каждого слова сохраняется ряд атрибутов, таких как все позиции, на которых встречается данное слово в данном документе (необходимы для подсветки и создания сниппетов), сдвиги относительно предыдущего слова (для обработки синонимов), ряд других встроенных атрибутов, а также произвольный payload – набор байт, которые программист может использовать по своему усмотрению:

Хотя при стандартном сценарии программисту нет необходимости работать напрямую с обратным индексом, Lucene предоставляет доступ к нему через методы termDocs и termPositions класса IndexReader (подробнее об API ниже). Обычно под текстовым документом мы понимаем просто именованный текст. Однако в терминологии Lucene документ представляет собой нечто большее, а именно набор пар поле-значение, где текст документа является всего лишь значением одного из полей. Среди других полей могут быть заголовок, авторы, краткое описание и вообще что угодно, что может быть представлено в текстовом формате. Кроме того, значением поля может быть число, и в том числе дата. Это оказывается особо полезным в сочетании с возможностью Lucene’а искать по интервалу. Например, можно указать год издания статьи и затем найти все статьи в период между 2005 и 2010-м годами.

По сути, индекс Lucene представляет собой базу данных с объектами и полями этих объектов. Однако база эта – документно-ориентированная, а это значит, что в отличие от реляционных баз объекты в ней не обязаны подчиняться какой-либо схеме. Каждый документ может иметь сколько угодно каких угодно полей, независимо от того, какие поля имеют другие документы. Здесь же стоит отметить, что Lucene имеет один большой недостаток по сравнению с традиционными БД – он не создаёт ID для документов. Все документы внутри Lucene имеют свой номер, доступный извне, однако при удалении документа и оптимизации индекса Lucene может переставить номера. Поэтому если необходимо ввести уникальный идентификатор для каждого документа, то лучше добавить дополнительное поле и генерировать ID-шники самостоятельно.
Индексирование
Индексирование документов, таких как PDF-файлы, происходит в 2 этапа: вначале из них извлекается текст, а затем уже этот текст анализируется, и полученные данные попадают в поисковый индекс.
К сожалению, сам по себе Lucene, в отличие от Solr и Nutch, не умеет работать ни с PDF, ни с каким бы то ни было другим сложным форматом файлов. Для извлечения текста из таких файлов вам придётся использовать другие библиотеки. При поиске таких библиотек можно отталкиваться от соответствующих плагинов Nutch’а. При индексировании текста он проходит несколько стадий. Во-первых, текст разбивается на токены, то есть токенизируется (tokenizing). В простейшем случае разбиение происходит по пробелам и знакам препинания, однако это поведение может регулироваться. Во-вторых, каждый токен приводится к своей морфологической основе (stemming): обрезаются окончания, распространённые суффиксы заменяются на стандартную форму и т.д. Например, слово «questioning» из примера выше будет преобразовано в «question». В дальнейшем это позволит игнорировать при поиске форму слова, выдавая более точные результаты. Преобразование происходит на основе ряда простых логических правил и не всегда даёт правильный с точки зрения лингвистики результат, однако в большинстве случаев это не играет значительной роли. В-третьих, из списка токенов выбрасываются так называемые stop-words – наиболее распространённые, но не несущие особой смысловой нагрузки слова. Для английского языка это такие слова как “a», “the», “and» и т.д. В-четвёртых, каждому токену присваивается ряд атрибутов, часть из которых в дальнейшем попадает в индекс, а часть – используется на более поздних стадиях, но отбрасывается при сохранении в индекс. (На самом деле в последних версиях Lucene даже сам токен рассматривается просто как атрибут некоторого объекта.) В Lucene все эти стадии объединены в так называемых анализаторах. Выбор анализатора зависит от нескольких параметров, и в первую очередь от языка текста. Ветвь contrib из репозитория Lucene предоставляет довольно интересный анализатор – SnowballAnalyzer. В отличие от прочих анализаторов, SnowballAnalyzer не заточен ни на один конкретный язык, а принимает название языка в виде строки в своём конструкторе. В сочетании с автоматическим определителем языка из проекта Tika, он может оказаться прекрасным решением для многоязычных корпусов текстов. Среди других анализаторов стоит выделить KeywordAnalyzer, который индексирует всё анализируемое поле как единый токен. Это может быть полезно для таких полей как номер телефона, ID, различные коды и т.д.
Поиск
- title: Einstein
- content: The important thing is not to stop questioning.
- content: important
- content: is
- content: not
- content: questioning
- content: stop
- content: The
- content: thing
- content: to
- title: Einstein
API
- Analyzer, а также его потомки – отвечают за токенезацию, морфологические преобразования, фильтрация стоп-слов и т.д.
- TokenStream – представляет интерфейс для работы с потоком токенов. В последних версиях представляет их себя набор ленивых списков атрибутов, для чего наследуется от AttributeSource. Наследники же самого TokenStream делятся на две крупные группы, представленные классами Tokenizer и TokenFilter.
- Directory – интерфейс, отвечает за хранилище для индекса. Чаще всего используются две имплементации – FSDirectory для дискового индекса и RAMDirectory для индекса в памяти.
- Document – документ Lucene.
- Field – поле документа. В конструкторе принимает как минимум 4 параметра – имя поля, значение и два флага, отвечающие за хранение оригинального поля и за способ анализа.
- IndexWriter – класс, используемый для добавления документов в индекс, а также для их удаления.
- IndexReader – инкапсулирует весь функционал по чтению из индекса. Используется другими классами для поиска, предоставляет доступ к обратному индексу и многое другое.
- IndexSearcher – название говорит само за себя.
- Query – внутреннее представление запроса.
- QueryParser – то, что преобразует String в Query.
- TopScoreDocCollector и ScoreDoc – используются для сохранения и итерирования по результатам поиска.