Lucene java.
Apache Lucene набор библиотек java при помощи которых можно организовать полноценный
поиск в интерисующих вас данных(текстовых данных). На данный момент существует несколько поисковых движков использующих api Lucene таких как Apache Solr, Nutch, Hibernate Search. Без основ трудно разобраться и настроить их (движки), как бы вам не хотелось нужна база, основа понимания Lucene. Эту статью я хочу посвятить основам поиска на Lucene и так начнем наше погружение. Начнем как не странно с теории. Пусть у нас есть 2 книги:
поиск в интерисующих вас данных(текстовых данных). На данный момент существует несколько поисковых движков использующих api Lucene таких как Apache Solr, Nutch, Hibernate Search. Без основ трудно разобраться и настроить их (движки), как бы вам не хотелось нужна база, основа понимания Lucene. Эту статью я хочу посвятить основам поиска на Lucene и так начнем наше погружение. Начнем как не странно с теории. Пусть у нас есть 2 книги:
Книга 1:
название первой книги - "Java in Action"
ISBN "1-932394-28-2"
год выпуска - "2011"
Книга 2:
название второй книги - "Java and Flex"
ISBN "1-932394-28-1"
год выпуска - "2010"
Давайте введем понятие Document. Document - это объект для поиска, который состоит
из полей(Fields). В данном примере Книга 1, Книга 2 - это Документы, которые состоят из полей:
Java in Action, 1-932394-28-2, 2011 и Java and Flex, 1-932394-28-1, 2010.
Структура Lucene индекса такова что он содержит последовательность документов(Documents),
котрые в свою очередь состоит из полей, поля имеют имена, количество и последовательность полей в каждом документе должна совпадать, а так же и их "тип данных", т. е. если в Документе первое поле содержит название книги, то и во втором Документе первое поле должно содержать название книги, а не год выпуска.
Процесс поиска Lucene состоит в том что текстовое поле с помощью анализатора Lucene
преобразуется в специальный индексный вид, terms и они сохраняются в индексе Lucene.
Эти terms в дальнейшем используются для поиска в запросах по индексу Lucene. Важную
роль при разбивки поля на terms играет анализатор, в зависимости от того какой анализатор
вы выбрали вы получите те или иные terms. Анализатор преобразует текстовое поле обычно
несколькими операциями, такими как извлечение "слова", сброс пунктуации (знаков припинания и т. д.), удаление частиц стоп-слов (stop words): in, a, the, at and e.t.c. приведение всех слов к нижнему регистру букв (lowercasing) - иногда называемую нормализацией (normalizing), удаление повторяющихся "слов", нахождение основы слова("корня") - стемминг (stemming), или получение из слова его нормальной/словарной формы - lemmatization. Процесс разбитетия текста по выше перечисленным правилам называется - tokenization, а сами части текста - tokens. Если кратко сказать term - это комбинация имени поля и token'a. Настало время закрепить всю теорию на практике и начнем все с анализаторов. Перед индексированием поле проходит через анализатор который разбивает поле на terms, анализаторы бывают разные и предназначенны они для различных случаев.
Перичислю некоторые из них:
WhitespaceAnalyzer - Разделяет на terms по пробелам.
SimpleAnalyzer - Производит деление текста по словам используя в качестве разделителей
любые символы кроме букв, переводит в нижний регистр.
StopAnalyzer - Убирает стоп-слова(Естественно Английские :) ) и переводит текст в нижний регистр.
StandardAnalyzer - Анализирует текст, используя сложные грамматические правила. Переводит текст в нижний регистр и убирает стоп-слова.
EnglishAnalyzer и RussianAnalyzer (в предыдущих версиях носил название SnowballAnalyzer)
- Переводит текст в нижний регистр, убирает стоп-слова и преобразует слово используя стемминг(stemming):
Давайте рассмотрим код - он разбивает три строки
"The quick brown fox jumped over the LaZy dogs",
"Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала",
"XY&Z Corporation - xyz@Example.com":
WhitespaceAnalyzer - Разделяет на terms по пробелам.
SimpleAnalyzer - Производит деление текста по словам используя в качестве разделителей
любые символы кроме букв, переводит в нижний регистр.
StopAnalyzer - Убирает стоп-слова(Естественно Английские :) ) и переводит текст в нижний регистр.
StandardAnalyzer - Анализирует текст, используя сложные грамматические правила. Переводит текст в нижний регистр и убирает стоп-слова.
EnglishAnalyzer и RussianAnalyzer (в предыдущих версиях носил название SnowballAnalyzer)
- Переводит текст в нижний регистр, убирает стоп-слова и преобразует слово используя стемминг(stemming):
Давайте рассмотрим код - он разбивает три строки
"The quick brown fox jumped over the LaZy dogs",
"Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала",
"XY&Z Corporation - xyz@Example.com":
package org.vit; import org.apache.lucene.analysis.*; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.en.EnglishMinimalStemmer; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.analysis.ru.RussianAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.util.Version; import org.apache.lucene.util.AttributeSource; import java.io.IOException; import java.io.StringReader; public class AnalysisDemo { private static final String[] strings = { "The quick brown fox jumped over the LaZy dogs", "Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала", "XY&Z Corporation - xyz@Example.com"}; private static final Analyzer[] analyzers = new Analyzer[]{ new WhitespaceAnalyzer(Version.LUCENE_31), new SimpleAnalyzer(Version.LUCENE_31), new EnglishAnalyzer(Version.LUCENE_31), new RussianAnalyzer(Version.LUCENE_31), new StopAnalyzer(Version.LUCENE_31), new StandardAnalyzer(Version.LUCENE_31) }; public static void main(String[] args) throws IOException { for (int i = 0; i < strings.length; i++) { analyze(strings[i]); } } private static void analyze(String text) throws IOException { System.out.println("Analzying \"" + text + "\""); for (int i = 0; i < analyzers.length; i++) { Analyzer analyzer = analyzers[i]; System.out.println("\t" + analyzer.getClass().getName() + ":"); System.out.print("\t\t"); TokenStream stream = analyzer.tokenStream("contents", new StringReader(text)); while (true) { if (!stream.incrementToken()) break; AttributeSource token = stream.cloneAttributes(); CharTermAttribute term =(CharTermAttribute) token.addAttribute(CharTermAttribute.class); System.out.print("[" + term.toString() + "] "); //2 } System.out.println("\n"); } } }
Результат работы программы:
Самый продвинутый StandardAnalyzer для английского текста но если копнуть глубже то любой анализатор это набор фильтров, так что для себя любимого можно написать свой анализатор если вас неустраивают стандартные. В lucene так же есть различные языковые анализаторы такие как EnglishAnalyzer и RussianAnalyzer и для других языков (snowball) - котрые производят обрезку окончаний для своей локали (stemming).
В принципе для поиска по индексу, нужно - первое создать индекс и второе написать програму поиска по индексу, чем мы и займемся. Прежде всего нужно выяснить что мы будем искать и где - работать будем с текстом, искать слова в тексте.
Analzying "The quick brown fox jumped over the LaZy dogs" org.apache.lucene.analysis.WhitespaceAnalyzer: [The] [quick] [brown] [fox] [jumped] [over] [the] [LaZy] [dogs] org.apache.lucene.analysis.SimpleAnalyzer: [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] org.apache.lucene.analysis.en.EnglishAnalyzer: [quick] [brown] [fox] [jump] [over] [lazi] [dog] org.apache.lucene.analysis.ru.RussianAnalyzer: [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] org.apache.lucene.analysis.StopAnalyzer: [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] org.apache.lucene.analysis.standard.StandardAnalyzer: [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] Analzying "Быстрая, рыжая лисица препрыгнула через ленивых собак и бысто убежала" org.apache.lucene.analysis.WhitespaceAnalyzer: [Быстрая,] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] org.apache.lucene.analysis.SimpleAnalyzer: [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] org.apache.lucene.analysis.en.EnglishAnalyzer: [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] org.apache.lucene.analysis.ru.RussianAnalyzer: [быстр] [рыж] [лисиц] [препрыгнул] [ленив] [собак] [быст] [убежа] org.apache.lucene.analysis.StopAnalyzer: [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] org.apache.lucene.analysis.standard.StandardAnalyzer: [быстрая] [рыжая] [лисица] [препрыгнула] [через] [ленивых] [собак] [и] [бысто] [убежала] Analzying "XY&Z Corporation - xyz@Example.com" org.apache.lucene.analysis.WhitespaceAnalyzer: [XY&Z] [Corporation] [-] [xyz@Example.com] org.apache.lucene.analysis.SimpleAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] org.apache.lucene.analysis.en.EnglishAnalyzer: [xy] [z] [corpor] [xyz] [example.com] org.apache.lucene.analysis.ru.RussianAnalyzer: [xy] [z] [corporation] [xyz] [example.com] org.apache.lucene.analysis.StopAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] org.apache.lucene.analysis.standard.StandardAnalyzer: [xy] [z] [corporation] [xyz] [example.com]
Самый продвинутый StandardAnalyzer для английского текста но если копнуть глубже то любой анализатор это набор фильтров, так что для себя любимого можно написать свой анализатор если вас неустраивают стандартные. В lucene так же есть различные языковые анализаторы такие как EnglishAnalyzer и RussianAnalyzer и для других языков (snowball) - котрые производят обрезку окончаний для своей локали (stemming).
В принципе для поиска по индексу, нужно - первое создать индекс и второе написать програму поиска по индексу, чем мы и займемся. Прежде всего нужно выяснить что мы будем искать и где - работать будем с текстом, искать слова в тексте.
Формирует Индекс объект класса IndexWriter - ему нужно указать где создавать индекс - в памяти, на диске, в БД; какой анализатор использовать, версию индекса, создать новый
индекс или добавить в существующий. Далее для каждого документа, создать документ, в него добавить поля, сам документ добавить в объект класса IndexWriter, в конце оптимизировать индекс для более быстрого поиска. Давайте расмотрим пример:
индекс или добавить в существующий. Далее для каждого документа, создать документ, в него добавить поля, сам документ добавить в объект класса IndexWriter, в конце оптимизировать индекс для более быстрого поиска. Давайте расмотрим пример:
package org.vit; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.ru.RussianAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.util.Version; import java.io.IOException; import java.io.File; public class IndexerDemo { public static void main(String[] args){ try { FSDirectory FSD = FSDirectory.open(new File(".//Index")); //индекс будем хранить в директории ./Index RussianAnalyzer analyzer = new RussianAnalyzer(Version.LUCENE_31); //какой используем анализатор IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_31,analyzer); //наш конфиг iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE); //содаем всегда новый индекс IndexWriter writer = new IndexWriter(FSD, iwc); //создаем объект IndexWriter - или по другому индекс Document doc = new Document(); //создаем документ doc.add(new Field("id","1",Field.Store.YES,Field.Index.NOT_ANALYZED)); //добавляем 1-е поле в документ doc.add(new Field("name","Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала" ,Field.Store.YES,Field.Index.ANALYZED)); //добавляем 2-е поле в документ writer.addDocument(doc); //добавляем документ в индекс Document doc1 = new Document(); //повторяем тежe действия doc1.add(new Field("id","2",Field.Store.YES,Field.Index.NOT_ANALYZED)); doc1.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.ANALYZED)); doc1.setBoost(10.0f); writer.addDocument(doc1); Document doc2 = new Document(); doc2.add(new Field("id","3",Field.Store.YES,Field.Index.NOT_ANALYZED)); doc2.add(new Field("name","Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала" ,Field.Store.YES,Field.Index.ANALYZED)); writer.addDocument(doc2); Document doc3 = new Document(); doc3.add(new Field("id","3",Field.Store.YES,Field.Index.NOT_ANALYZED)); doc3.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.NOT_ANALYZED)); writer.addDocument(doc3); writer.optimize(); //оптимизируем индекс writer.close(); //все закрываем FSD.close(); } catch (IOException e) { e.printStackTrace(); } } }Запустив программу вы получите lucene индекс в директории ./Index.Хочу обратить ваше внимание на еще одну важную деталь это как создаются поля:
new Field("name", - имя поля
"Рыжий лис" - текст поля
,Field.Store.YES, - текст поля будет сохраняться в индексе или нет Field.Store.NO
Field.Index.* - отвечает за то как будет индексироваться поле т.е. его текст
возможные варианты:
NO - текст поля не индексируется, поле не может участвовать в поиске оно просто сохраняется
используется как набор Field.Store.YES,Field.Index.NO, набор Field.Store.NO,Field.Index.NO -
не допустим;
ANALYZED - текст поля пропускается через наш анализатор и получаем tokens которые потом сохраняются в индексе, поле участвует в поиске + расчитывается приоритет который в дальнейшем может быть использован при поиске;
NOT_ANALYZED - текст поля не пропускается через анализатор, весь текст считается одним единственным token который потом сохраняется в индексе, поле участвует в поиске + расчитывается приоритет который в дальнейшем может быть использован при поиске;
NOT_ANALYZED_NO_NORMS - для экспертов, текст поля не пропускается через анализатор, весь текст считается одним единственным token который потом сохраняется в индексе, поле участвует в поиске + еще у поля отключена фишка как приоритет при поиске;
ANALYZED_NO_NORMS - для экспертов, текст поля пропускается через наш анализатор и получаем tokens которые потом сохраняются в индексе, поле участвует в поиске + еще у поля отключена фишка как приоритет при поиске; на счет приоритета по умолчанию он стоит 1.0f у всех полей, установить его можно setBoost(10.0f) и чем выше число тем выше приоритет при поиске.
Переходим к поиску, ищет в индексе - объект класса IndexSearcher, ему нужно указать где лежит индекс - в памяти, на диске, в БД; какой анализатор использовать/неиспользовать, версию индекса, поле поиска, что ищем (token). Расмотрим пример:
package org.vit; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.search.*; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.analysis.ru.RussianAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.util.Version; import java.io.File; import java.io.IOException; public class SearchDemo { public static void main(String[] args) { try { Directory directory = FSDirectory.open(new File(".//Index")); //где находится индекс IndexSearcher is = new IndexSearcher(directory); //объект поиска QueryParser parser = new QueryParser(Version.LUCENE_31, "name", new RussianAnalyzer(Version.LUCENE_31));//поле поиска + анализатор Query query = parser.parse("лиса"); //что ищем TopDocs results =is.search(query, null, 10); //включаем поиск ограничиваемся 10 документами, results содержит ... System.out.println("getMaxScore()="+results.getMaxScore()+" totalHits="+results.totalHits); // MaxScore - наилучший результат(приоритет), totalHits - количество найденных документов for (ScoreDoc hits:results.scoreDocs) { // получаем подсказки Document doc = is.doc(hits.doc); //получаем документ по спец сылке doc System.out.println("doc="+hits.doc+" score="+hits.score);//выводим спец сылку doc + приоритет System.out.println(doc.get("id")+" | "+doc.get("name"));//выводим поля найденного документа } directory.close(); } catch (ParseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
выполнив программу получаем результат:
getMaxScore()=7.0 totalHits=3 //наилучший приоритет 7.0 всего совпадений 3 doc=1 score=7.0 //внутреннея сылка lucene 1 приоритет 7.0 2 | Рыжий лис //поля найденного документа doc=0 score=0.3125 //если приоритет совпадает 0.3125(doc=0) и 0.3125(doc=2) то сортируются результаты по doc 1 | Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала doc=2 score=0.3125 3 | Быстрая, рыжая лиса препрыгнула через ленивых собак и бысто убежала
Обратите внимание еще на одну вещь поиск не вывел документ doc3, при создании индекса
мы указали характеристи поля так:
doc3.add(new Field("name","Рыжий лис",Field.Store.YES,Field.Index.NOT_ANALYZED));
так как при сохранении индекса было указано что поле не будет обрабатываться анализатором
(Field.Index.NOT_ANALYZED) то и значение поля "Рыжий лис" - это один целый token,
а так как мы ищем token "лис" то у нас не происходит совпадения этих token'ов,
и документ не находится.
в программе использовал следующие библиотеки:
lucene-analyzers-3.1.0.jar
lucene-core-3.1.0.jar
PS lucene работает с текстом в кодировке UTF-8 если вам захочется проиндексировать текст допустим в кодировке windows-1251 то вам прежде нужно перевести его в UTF-8 а затем индексировать.
Комментариев нет:
Отправить комментарий