Úvod do Apache Lucene

1. Prehľad

Apache Lucene je fulltextový vyhľadávací nástroj, ktorý je možné použiť v rôznych programovacích jazykoch.

V tomto článku sa pokúsime pochopiť základné pojmy knižnice a vytvoriť jednoduchú aplikáciu.

2. Nastavenie Maven

Na začiatok najskôr pridajte potrebné závislosti:

 org.apache.lucene lucenove jadro 7.1.0 

Najnovšiu verziu nájdete tu.

Na analýzu našich vyhľadávacích dotazov budeme tiež potrebovať:

 org.apache.lucene lucene-queryparser 7.1.0 

Tu nájdete najnovšiu verziu.

3. Základné koncepty

3.1. Indexovanie

Zjednodušene povedané, Lucene používa „inverznú indexáciu“ údajov - namiesto mapovania stránok na kľúčové slová mapuje kľúčové slová na stránky rovnako ako glosár na konci každej knihy.

To umožňuje rýchlejšie vyhľadávať, pretože prehľadáva index, namiesto priameho prehľadávania textu.

3.2. Dokumenty

Dokument je tu kolekciou polí a ku každému poli je priradená hodnota.

Indexy sa zvyčajne skladajú z jedného alebo viacerých dokumentov a výsledky vyhľadávania sú súbory dokumentov, ktoré sa najlepšie zhodujú.

Nie vždy ide o obyčajný textový dokument, môže to byť tiež databázová tabuľka alebo kolekcia.

3.3. Polia

Dokumenty môžu obsahovať údaje z polí, kde pole je zvyčajne kľúčom obsahujúcim údajovú hodnotu:

názov: Dobrota čaju: Diskutovanie o dobrote pitia bylinného čaju ...

Všimnite si to tu titul a telo sú polia a je možné ich vyhľadávať spoločne alebo jednotlivo.

3.4. Analýza

Analýza prevádza daný text na menšie a presné jednotky na uľahčenie vyhľadávania.

Text prechádza rôznymi operáciami extrakcie kľúčových slov, odstránenia bežných slov a interpunkčných znamienok, zmeny slov na malé písmená atď.

Na tento účel existuje niekoľko zabudovaných analyzátorov:

  1. StandardAnalyzer - analýzy založené na základnej gramatike, odstraňuje zastavovacie slová ako „a“, „an“ atď., Prevádza sa aj malými písmenami
  2. SimpleAnalyzer - rozbije text na základe znaku bez písmen a prevádza sa malými písmenami
  3. WhiteSpaceAnalyzer - rozbije text na základe medzier

Máme k dispozícii viac analyzátorov, ktoré môžeme tiež použiť a prispôsobiť.

3.5. Hľadám

Po vytvorení indexu ho môžeme prehľadať pomocou a Dopyt a an IndexSearcher. Výsledkom vyhľadávania je zvyčajne sada výsledkov obsahujúca načítané údaje.

Všimnite si, že IndexWritter je zodpovedný za vytvorenie indexu a IndexSearcher na prehľadanie indexu.

3.6. Syntax dopytu

Lucene poskytuje veľmi dynamickú a ľahko napísateľnú syntax dotazu.

Na hľadanie voľného textu stačí použiť text String ako dopyt.

Na hľadanie textu v konkrétnom poli by sme použili:

fieldName: text napr: nadpis: caj

Rozsah vyhľadávania:

časová pečiatka: [1509909322,1572981321] 

Môžeme tiež vyhľadávať pomocou zástupných znakov:

dri? nk

hľadal by namiesto zástupného znaku „?“ jeden znak

d * k

vyhľadáva slová začínajúce na „d“ a končiace na „k“, medzi ktorými je viac znakov.

uni *

nájde slová začínajúce sa „uni“.

Tieto dotazy môžeme tiež skombinovať a vytvoriť zložitejšie dotazy. A zahrňte logický operátor ako AND, NOT, OR:

názov: „Čaj na raňajky“ A „káva“

Viac informácií o syntaxi dopytu nájdete tu.

4. Jednoduchá aplikácia

Vytvorme jednoduchú aplikáciu a indexujme niektoré dokumenty.

Najskôr vytvoríme index v pamäti a pridáme do neho niekoľko dokumentov:

... adresár memoryIndex = nový RAMDirectory (); Analyzátor StandardAnalyzer = nový StandardAnalyzer (); IndexWriterConfig indexWriterConfig = nový IndexWriterConfig (analyzátor); Zapisovač IndexWriter = nový IndexWriter (memoryIndex, indexWriterConfig); Dokumentový dokument = nový dokument (); document.add (new TextField ("title", title, Field.Store.YES)); document.add (new TextField ("body", body, Field.Store.YES)); Writter.addDocument (dokument); Writter.close (); 

Tu vytvoríme dokument pomocou Textové pole a pridajte ich do indexu pomocou IndexWriter. Tretí argument v rámci Textové pole konštruktor označuje, či sa má hodnota poľa tiež uložiť alebo nie.

Analyzátory sa používajú na rozdelenie údajov alebo textu na bloky a potom z nich odfiltrujú stopové slová. Stopové slová sú slová ako „a“, „som“, „je“ atď. Tieto slová úplne závisia od daného jazyka.

Ďalej vytvoríme vyhľadávací dopyt a vyhľadáme v indexe pridaný dokument:

public List searchIndex (String inField, String queryString) {Query query = new QueryParser (inField, analyzer) .parse (queryString); IndexReader indexReader = DirectoryReader.open (memoryIndex); IndexSearcher searcher = nový IndexSearcher (indexReader); TopDocs topDocs = searcher.search (dopyt, 10); Zoznam dokumentov = new ArrayList (); pre (ScoreDoc scoreDoc: topDocs.scoreDocs) {documents.add (searcher.doc (scoreDoc.doc)); } vrátiť dokumenty; }

V Vyhľadávanie() metóda argument druhého celého čísla označuje, koľko najlepších výsledkov vyhľadávania by malo vrátiť.

Teraz to otestujme:

@Test public void givenSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nový InMemoryLuceneIndex (nový RAMDirectory (), nový StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Hello world", "Some hello world"); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex ("body", "svet"); assertEquals ("Hello world", documents.get (0) .get ("title")); }

Tu pridáme do indexu jednoduchý dokument s dvoma poľami „title“ a „body“ a potom sa pokúsime vyhľadať to isté pomocou vyhľadávacieho dotazu.

6. Lucenské dotazy

Keďže nám teraz vyhovujú základné informácie o indexovaní a vyhľadávaní, poďme sa venovať trochu hlbšie.

V predchádzajúcich častiach sme videli základnú syntax dotazu a spôsob, ako ho previesť na a Dopyt napríklad pomocou QueryParser.

Lucene poskytuje aj rôzne konkrétne implementácie:

6.1. TermQuery

A Termín je základná jednotka pre vyhľadávanie, ktorá obsahuje názov poľa spolu s textom, ktorý sa má vyhľadať.

TermQuery je najjednoduchší zo všetkých dotazov pozostávajúcich z jedného výrazu:

@Test public void givenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nový InMemoryLuceneIndex (nový RAMDirectory (), nový StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("aktivita", "spustenie v stope"); inMemoryLuceneIndex.indexDocument ("aktivita", "Autá jazdia po ceste"); Termín termínu = nový termín („body“, „beží“); Dotaz na dopyt = nový TermQuery (termín); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt); assertEquals (2, documents.size ()); }

6.2. PrefixQuery

Vyhľadanie dokumentu so slovom „začína na“:

@Test public void givenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nový InMemoryLuceneIndex (nový RAMDirectory (), nový StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("článok", "úvod do lucenčiny"); inMemoryLuceneIndex.indexDocument ("článok", "Úvod do lucenčiny"); Termín výraz = nový výraz („telo“, „úvod“); Dotaz na dopyt = nový PrefixQuery (výraz); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt); assertEquals (2, documents.size ()); }

6.3. Zástupný dopyt

Ako naznačuje názov, môžeme použiť zástupné znaky „*“ alebo „?“ na hľadanie:

// ... Termín term = nový Termín ("body", "úvod *"); Dotaz na dopyt = nový WildcardQuery (výraz); // ...

6.4. PhraseQuery

Používa sa na vyhľadávanie sekvencií textov v dokumente:

// ... inMemoryLuceneIndex.indexDocument ("quotes", "Rose pod akýmkoľvek iným menom by voňala ako sladká."); Dotaz na dopyt = nový PhraseQuery (1, "telo", nový BytesRef ("vôňa"), nový BytesRef ("sladký")); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt); // ...

Všimnite si, že prvý argument PhraseQuery volá sa konštruktor odfláknuť čo je vzdialenosť v počte slov medzi výrazmi, ktoré sa majú zhodovať.

6.5. FuzzyQuery

Môžeme to použiť pri hľadaní niečoho podobného, ​​ale nie nevyhnutne identického:

// ... inMemoryLuceneIndex.indexDocument ("článok", "halloweensky festival"); inMemoryLuceneIndex.indexDocument ("dekorácia", "Dekorácie na Halloween"); Termín výraz = nový výraz ("telo", "posvätiť"); Dotaz na dopyt = nový FuzzyQuery (výraz); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt); // ...

Skúsili sme vyhľadať text „Halloween“, ale s chybným pravopisom „hallowen“.

6.6. BooleanQuery

Niekedy možno budeme musieť vykonať komplexné vyhľadávanie kombinujúce dva alebo viac rôznych typov dotazov:

// ... inMemoryLuceneIndex.indexDocument ("Destination", "Las Vegas singapore car"); inMemoryLuceneIndex.indexDocument ("Dochádzanie v Singapure", "Bicykle do auta pre autobusy"); Termín term1 = nový Termín ("body", "singapore"); Termín term2 = nový výraz („karoséria“, „auto“); TermQuery query1 = nový TermQuery (term1); TermQuery query2 = nový TermQuery (term2); BooleanQuery booleanQuery = nový BooleanQuery.Builder () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...

7. Triedenie výsledkov vyhľadávania

Dokumenty s výsledkami vyhľadávania môžeme tiež triediť na základe určitých polí:

@Test public void givenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nový InMemoryLuceneIndex (nový RAMDirectory (), nový StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganga", "Rieka v Indii"); inMemoryLuceneIndex.indexDocument („Mekong“, „Táto rieka tečie v južnej Ázii“); inMemoryLuceneIndex.indexDocument ("Amazon", "Rieka dažďového pralesa"); inMemoryLuceneIndex.indexDocument ("Rýn", "patrí do Európy"); inMemoryLuceneIndex.indexDocument ("Níl", "najdlhšia rieka"); Termín výraz = nový výraz („telo“, „rieka“); Dotaz na dopyt = nový WildcardQuery (výraz); SortField sortField = nový SortField ("title", SortField.Type.STRING_VAL, false); Zoradiť sortByTitle = nové Zoradiť (sortField); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt, sortByTitle); assertEquals (4, documents.size ()); assertEquals ("Amazon", documents.get (0) .getField ("titul"). stringValue ()); }

Vyskúšané dokumenty sme sa pokúsili zoradiť podľa názvových polí, čo sú názvy riek. Logický argument pre SortField konštruktor slúži na obrátenie poradia zoradenia.

8. Odstráňte dokumenty z indexu

Pokúsime sa odstrániť niektoré dokumenty z indexu na základe daného Termín:

// ... IndexWriterConfig indexWriterConfig = nový IndexWriterConfig (analyzátor); Zapisovač IndexWriter = nový IndexWriter (memoryIndex, indexWriterConfig); writer.deleteDocuments (term); // ...

Toto otestujeme:

@Test public void whenDocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nový InMemoryLuceneIndex (nový RAMDirectory (), nový StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganga", "Rieka v Indii"); inMemoryLuceneIndex.indexDocument („Mekong“, „Táto rieka tečie v južnej Ázii“); Termín termínu = nový termín („titul“, „gangy“); inMemoryLuceneIndex.deleteDocument (výraz); Dotaz na dopyt = nový TermQuery (termín); Zoznam dokumentov = inMemoryLuceneIndex.searchIndex (dopyt); assertEquals (0, documents.size ()); }

9. Záver

Tento článok bol krátkym úvodom do začiatkov používania Apache Lucene. Taktiež sme vykonali rôzne dotazy a vytriedili nájdené dokumenty.

Ako vždy nájdete kód príkladov na Githube.