Uvod u Apache Lucene

1. Pregled

Apache Lucene je pretraživač punog teksta koji se može koristiti iz različitih programskih jezika.

U ovom ćemo članku pokušati razumjeti osnovne koncepte knjižnice i stvoriti jednostavnu aplikaciju.

2. Postavljanje Mavena

Da započnemo, najprije dodamo potrebne ovisnosti:

 org.apache.lucen lucen-jezgra 7.1.0 

Najnoviju verziju možete pronaći ovdje.

Također, za raščlanjivanje upita za pretraživanje trebat će nam:

 org.apache.lucen lucen-queryparser 7.1.0 

Ovdje potražite najnoviju verziju.

3. Temeljni koncepti

3.1. Indeksiranje

Jednostavno rečeno, Lucene koristi "obrnuto indeksiranje" podataka - umjesto preslikavanja stranica na ključne riječi, preslikava ključne riječi na stranice baš kao i pojmovnik na kraju svake knjige.

To omogućuje brže odgovore pretraživanja jer pretražuje indeks, umjesto da izravno pretražuje tekst.

3.2. Dokumenti

Ovdje je dokument zbirka polja i svako polje ima vrijednost povezanu s njim.

Indeksi se obično sastoje od jednog ili više dokumenata, a rezultati pretraživanja su skupovi dokumenata koji se najbolje podudaraju.

To nije uvijek običan tekstualni dokument, to može biti i tablica baze podataka ili zbirka.

3.3. Polja

Dokumenti mogu imati podatke polja, gdje je polje obično ključ koji sadrži vrijednost podataka:

naslov: Dobrota čajnog tijela: Rasprava o dobroti ispijanja biljnog čaja ...

Primijetite to ovdje titula i tijelo su polja i mogu se tražiti zajedno ili pojedinačno.

3.4. Analiza

Analiza pretvara zadani tekst u manje i precizne jedinice radi lakšeg pretraživanja.

Tekst prolazi kroz razne operacije izdvajanja ključnih riječi, uklanjanja uobičajenih riječi i interpunkcije, mijenjanja riječi u mala slova itd.

U tu svrhu postoji više ugrađenih analizatora:

  1. StandardAnalyzer - analizira na temelju osnovne gramatike, uklanja zaustavne riječi poput "a", "an" itd. Također pretvara u mala slova
  2. SimpleAnalyzer - razbija tekst na temelju slova bez slova i pretvara u mala slova
  3. WhiteSpaceAnalyzer - razbija tekst na temelju praznih prostora

Dostupno nam je više analizatora koje možemo koristiti i prilagoditi.

3.5. Traženje

Jednom kada je indeks izgrađen, taj indeks možemo pretraživati ​​pomoću a Upit i an IndexSearcher. Rezultat pretraživanja obično je skup rezultata koji sadrži preuzete podatke.

Imajte na umu da an IndexWritter odgovoran je za stvaranje indeksa i IndexSearcher za pretraživanje indeksa.

3.6. Sintaksa upita

Lucene pruža vrlo dinamičnu i jednostavnu sintaksu upita za pisanje.

Za pretragu slobodnog teksta samo bismo koristili tekst Niz kao upit.

Za pretraživanje teksta u određenom polju koristili bismo:

fieldName: tekst npr.: naslov: čaj

Pretrage raspona:

vremenska oznaka: [1509909322,1572981321] 

Također možemo pretraživati ​​pomoću zamjenskih znakova:

piće

bi tražio jedan znak umjesto zamjenskog znaka "?"

d * k

traži riječi koje počinju s "d" i završavaju s "k", a između njih je više znakova.

uni *

naći će riječi koje počinju s "uni".

Također možemo kombinirati ove upite i stvoriti složenije upite. I uključite logički operator poput AND, NOT, OR:

naslov: "Čaj u doručku" I "kava"

Više o sintaksi upita ovdje.

4. Jednostavna aplikacija

Stvorimo jednostavnu aplikaciju i indeksirajmo neke dokumente.

Prvo ćemo stvoriti indeks u memoriji i dodati ćemo mu neke dokumente:

... Imenik memoryIndex = novi RAMDirectory (); Analizator StandardAnalyzer = novi StandardAnalyzer (); IndexWriterConfig indexWriterConfig = novi IndexWriterConfig (analizator); IndexWriter pisač = novi IndexWriter (memoryIndex, indexWriterConfig); Dokument dokumenta = novi dokument (); document.add (novi TextField ("naslov", naslov, Field.Store.YES)); document.add (novo TextField ("body", body, Field.Store.YES)); writeter.addDocument (dokument); Writter.close (); 

Ovdje kreiramo dokument pomoću Polje za tekst i dodajte ih u indeks pomoću IndexWriter. Treći argument u Polje za tekst konstruktor pokazuje treba li vrijednost polja također pohraniti ili ne.

Analizatori se koriste za dijeljenje podataka ili teksta na dijelove, a zatim filtriranje zaustavnih riječi iz njih. Riječi zaustavljanja su riječi poput „a“, „am“, „is“ itd. One u potpunosti ovise o danom jeziku.

Dalje, kreirajmo upit za pretraživanje i pretražimo indeks za dodani dokument:

javni popis searchIndex (String inField, String queryString) {Query query = novi QueryParser (inField, analizator) .parse (queryString); IndexReader indexReader = DirectoryReader.open (memoryIndex); IndexSearcher pretraživač = novi IndexSearcher (indexReader); TopDocs topDocs = searcher.search (upit, 10); Popis dokumenata = novi ArrayList (); za (ScoreDoc scoreDoc: topDocs.scoreDocs) {documents.add (searcher.doc (scoreDoc.doc)); } vratiti dokumente; }

U traži() metoda drugi cijeli brojni argument ukazuje na to koliko bi top rezultata pretraživanja trebao vratiti.

Ajmo sada testirati:

@Test javna praznina givenSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (novi RAMDirectory (), novi StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Hello world", "Some hello world"); Popis dokumenata = inMemoryLuceneIndex.searchIndex ("tijelo", "svijet"); assertEquals ("Pozdrav svijetu", documents.get (0) .get ("naslov")); }

Ovdje u indeks dodajemo jednostavan dokument s dva polja "naslov" i "tijelo", a zatim pokušavamo pretražiti isti pomoću upita za pretraživanje.

6. Upiti o lucenu

Kako smo sada zadovoljni osnovama indeksiranja i pretraživanja, krenimo malo dublje.

U ranijim smo odjeljcima vidjeli osnovnu sintaksu upita i kako je pretvoriti u Upit primjer pomoću QueryParser.

Lucene također nudi razne konkretne implementacije:

6.1. TermQuery

A Termin je osnovna jedinica za pretraživanje koja sadrži naziv polja zajedno s tekstom koji se traži.

TermQuery je najjednostavniji od svih upita koji se sastoje od jednog pojma:

@Test javna praznina givenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = novi InMemoryLuceneIndex (novi RAMDirectory (), novi StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("aktivnost", "pokretanje u stazi"); inMemoryLuceneIndex.indexDocument ("aktivnost", "Automobili voze cestom"); Pojam izraza = novi pojam ("tijelo", "trčanje"); Upit upita = novi TermQuery (pojam); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit); assertEquals (2, documents.size ()); }

6.2. PrefixQuery

Da biste pretražili dokument s riječju "započinje s":

@Test javna praznina givenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (novi RAMDirectory (), novi StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("članak", "Uvod u lucen"); inMemoryLuceneIndex.indexDocument ("članak", "Uvod u lucen"); Pojam pojam = novi pojam ("tijelo", "uvod"); Upit upita = novi PrefixQuery (pojam); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit); assertEquals (2, documents.size ()); }

6.3. Zamjenski upit

Kao što samo ime govori, možemo koristiti zamjenske znakove "*" ili "?" za pretraživanje:

// ... Termin pojam = novi pojam ("body", "intro *"); Upit upita = novi WildcardQuery (pojam); // ...

6.4. PhraseQuery

Koristi se za pretraživanje niza tekstova u dokumentu:

// ... inMemoryLuceneIndex.indexDocument ("navodnici", "Ruža pod bilo kojim drugim imenom mirisala bi slatko."); Upit upita = novi PhraseQuery (1, "tijelo", novi BytesRef ("miris"), novi BytesRef ("slatko")); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit); // ...

Primijetite da je prvi argument PhraseQuery naziva se konstruktor bljuzgavica, što je udaljenost u broju riječi između pojmova koji se podudaraju.

6.5. FuzzyQuery

To možemo koristiti kada tražimo nešto slično, ali ne nužno identično:

// ... inMemoryLuceneIndex.indexDocument ("članak", "Halloween festival"); inMemoryLuceneIndex.indexDocument ("ukras", "Dekoracije za Noć vještica"); Pojam pojam = novi pojam ("tijelo", "sveti"); Upit upita = novi FuzzyQuery (pojam); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit); // ...

Pokušali smo potražiti tekst "Halloween", ali s pogrešno napisanim "hallowen".

6.6. BooleanQuery

Ponekad ćemo možda trebati izvršiti složena pretraživanja, kombinirajući dvije ili više različitih vrsta upita:

// ... inMemoryLuceneIndex.indexDocument ("Odredište", "Las Vegas singapore automobil"); inMemoryLuceneIndex.indexDocument ("Putuje u posao u Singapuru", "Autobusi za autobuse"); Pojam pojam1 = novi pojam ("tijelo", "singapur"); Pojam pojam2 = novi pojam ("tijelo", "automobil"); TermQuery query1 = novi TermQuery (pojam1); TermQuery query2 = novi TermQuery (pojam2); BooleanQuery booleanQuery = novo BooleanQuery.Builder () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...

7. Sortiranje rezultata pretraživanja

Također možemo sortirati dokumente rezultata pretraživanja na temelju određenih polja:

@Test javna praznina givenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = novi InMemoryLuceneIndex (novi RAMDirectory (), novi StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "Rijeka u Indiji"); inMemoryLuceneIndex.indexDocument ("Mekong", "Ova rijeka teče u južnoj Aziji"); inMemoryLuceneIndex.indexDocument ("Amazon", "Rijeka kišnih šuma"); inMemoryLuceneIndex.indexDocument ("Rajna", "Pripada Europi"); inMemoryLuceneIndex.indexDocument ("Nil", "Najduža rijeka"); Pojam pojam = novi pojam ("tijelo", "rijeka"); Upit upita = novi WildcardQuery (pojam); SortField sortField = novo SortField ("title", SortField.Type.STRING_VAL, false); Sort sortByTitle = novo Sort (sortField); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit, sortByTitle); assertEquals (4, documents.size ()); assertEquals ("Amazon", documents.get (0) .getField ("title"). stringValue ()); }

Pokušali smo sortirati preuzete dokumente prema poljima naslova, koja su imena rijeka. Logički argument za SortField konstruktor je za poništavanje redoslijeda sortiranja.

8. Uklonite dokumente iz indeksa

Pokušajmo ukloniti neke dokumente iz indeksa na temelju zadanog Termin:

// ... IndexWriterConfig indexWriterConfig = novi IndexWriterConfig (analizator); IndexWriter pisac = novi IndexWriter (memoryIndex, indexWriterConfig); writer.deleteDocuments (pojam); // ...

Testirat ćemo ovo:

@Test public void whenDocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = novi InMemoryLuceneIndex (novi RAMDirectory (), novi StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "Rijeka u Indiji"); inMemoryLuceneIndex.indexDocument ("Mekong", "Ova rijeka teče u južnoj Aziji"); Pojam pojam = novi pojam ("naslov", "ganges"); inMemoryLuceneIndex.deleteDocument (pojam); Upit upita = novi TermQuery (pojam); Popis dokumenata = inMemoryLuceneIndex.searchIndex (upit); assertEquals (0, documents.size ()); }

9. Zaključak

Ovaj je članak kratki uvod u početak rada s Apache Luceneom. Također, izvršili smo razne upite i sortirali preuzete dokumente.

Kao i uvijek kod za primjere možete pronaći na Githubu.