Mokabyte

Dal 1996, architetture, metodologie, sviluppo software

  • Argomenti
    • Programmazione & Linguaggi
      • Java
      • DataBase & elaborazione dei dati
      • Frameworks & Tools
      • Processi di sviluppo
    • Architetture dei sistemi
      • Sicurezza informatica
      • DevOps
    • Project Management
      • Organizzazione aziendale
      • HR
      • Soft skills
    • Lean/Agile
      • Scrum
      • Teoria della complessità
      • Apprendimento & Serious Gaming
    • Internet & Digital
      • Cultura & Società
      • Conferenze & Reportage
      • Marketing & eCommerce
    • Hardware & Tecnologia
      • Intelligenza artificiale
      • UX design & Grafica
  • Ultimo numero
  • Archivio
    • Archivio dal 2006 ad oggi
    • Il primo sito web – 1996-2005
  • Chi siamo
  • Ventennale
  • Libri
  • Contatti
  • Argomenti
    • Programmazione & Linguaggi
      • Java
      • DataBase & elaborazione dei dati
      • Frameworks & Tools
      • Processi di sviluppo
    • Architetture dei sistemi
      • Sicurezza informatica
      • DevOps
    • Project Management
      • Organizzazione aziendale
      • HR
      • Soft skills
    • Lean/Agile
      • Scrum
      • Teoria della complessità
      • Apprendimento & Serious Gaming
    • Internet & Digital
      • Cultura & Società
      • Conferenze & Reportage
      • Marketing & eCommerce
    • Hardware & Tecnologia
      • Intelligenza artificiale
      • UX design & Grafica
  • Ultimo numero
  • Archivio
    • Archivio dal 2006 ad oggi
    • Il primo sito web – 1996-2005
  • Chi siamo
  • Ventennale
  • Libri
  • Contatti

Nel numero:

100 ottobre
, anno 2005

Applicazioni Full-Text con Apache Lucene

Una libreria per la ricerca testuale

Avatar
Lorenzo Viscanti

Lorenzo Viscanti è un ingegnere informatico e si occupa soprattutto di problematiche relative al trattamento di testi, quali realizzazioni di motori di ricerca, text clustering e text mining.

MokaByte

Applicazioni Full-Text con Apache Lucene

Una libreria per la ricerca testuale

Picture of Lorenzo Viscanti

Lorenzo Viscanti

  • Questo articolo parla di: Frameworks & Tools, Programmazione & Linguaggi

Lucene è una libreria open source per creare un potente motore di ricerca per siti e applicazioni. Vediamone aspetti fondamentali e un confronto con le caratteristiche dei più comuni DBMS.

Le funzioni di trattamento di dati testuali sono una parte importante in un numero di applicazioni sempre più alto; dal CMS al più semplice software gestionale è infatti necessario permettere all’utente di interagire con delle risorse testuali. Da alcuni anni esiste un componente open-source che permette di realizzare soluzioni estremamente avanzate, Apache Lucene.

DBMS vs Full-Text

Possiamo evidenziare con grande efficacia le possibilità offerte da una soluzione full-text partendo da un confronto con le soluzioni presenti nei sistemi maggiormente utilizzati dagli sviluppatori, le basi di dati relazionali.
Di solito siamo abituati a lavorare con i DBMS basati sul modello relazionale che, come è ben noto, prevede una rappresentazione dei dati in tabelle, le cui righe rappresentano gli elementi inseriti e le cui colonne sono dei campi cui viene assegnato un valore; all’interno di queste tabelle possiamo effettuare operazioni di selezione ed ordinamento, basandoci sui valori di alcune colonne, che contengono valori numerici (o pseudonumerici).
Pensiamo, ad esempio, ad un sito Web di carattere giornalistico: il sistema di presentazione molto spesso si avvarrà di una base di dati, partendo dalla quale selezionerà gli articoli da presentare ai visitatori sulla base dell’identificativo con cui sono salvati nel database, per selezionare gli ultimi elementi pubblicati, oppure sulla base di colonne che specificano la categoria della notizia (per fornire più pagine, ad esempio una dedicata all’economia ed una allo sport).
Come è possibile notare dalle considerazioni appena fatte il database relazionale serve perlopiù a garantire la persistenza dei dati testuali, mentre poco può fare per quanto riguarda la loro elaborazione.
Volendo eseguire invece una ricerca all’interno dei testi, per trovare quelli che contengono un certo termini, possiamo utilizzare l’operatore SQL ‘LIKÈ; questa soluzione presenta però notevoli limiti, in termini di efficienza e di prestazioni.
Per fornire all’utente un sistema più avanzato di ricerca quasi tutti i DBMS forniscono dei moduli o delle estensioni full-text: attivando queste funzionalità è possibile accedere a servizi avanzati di ricerca testuale. Ad esempio in MySql, una volta definita l’indicizzazione full-text è possibile eseguire interrogazioni mediante le parole chiave MATCH() e AGAINST().
Esistono però altre soluzioni che consentono di estendere le proprie applicazioni per fornire servizi avanzati di analisi testuale: tra questi prodotti software Apache Lucene è uno dei più apprezzati e potenti.
Ma quali sono i motivi concreti per cui può essere opportuno appoggiarsi a prodotti specifici per il trattamento di testi?
Le ragioni sono molteplici, ma preferisco soffermarmi sulle due che ritengo più importanti. In primo luogo molto spesso ci si può trovare a confrontarsi con banche dati testuali che contengono fino a diverse decine di migliaia di documenti: in questa situazione le prestazioni ottenute tramite un motore ad hoc sono nettamente superiori rispetto a quelle raggiungibili con le espansioni full-text dei DBMS commerciali. Come esperienza personale posso dire di aver visto situazioni in cui uno dei più diffusi DBMS enterprise ha mostrato evidenti limiti a livello di prestazioni, soprattutto se la base documentale si caratterizza per frequenti aggiunte e cancellazioni di documenti (comportando problemi di organizzazione ed ottimizzazione alla base dati).
L’altro motivo, come è prevedibile, risiede nelle possibilità offerte da una soluzione full-text pura come Lucene: ricerche complesse con operatori booleani, ricerche all’interno di una specifica frase (i termini della query devono apparire vicini o ad una distanza massima tra loro), ricerche con wildcard e così via.
Da aggiungere a questi vantaggi è sicuramente anche la licenza open source: non solo un risparmio economico, ma soprattutto la possibilità di accedere ad una comunità di utenti cui appartengono anche gli stessi sviluppatori del progetto. In questa maniera è possibile ottenere, semplicemente e senza dispendio, un aiuto (quasi una consulenza) sulle problematiche che si manifestano durante lo sviluppo di applicazione.

Una panoramica su Lucene

Una base di dati testuale Lucene viene chiamata, con un termine che può essere fuorviante, indice; riprendendo il confronto con i database relazionali possiamo dire che ogni indice contiene una sola tabella le cui righe altro non sono che i documenti da noi inseriti.
L’indice è normalmente contenuto in una cartella del filesystem, ma esistono altre possibilità, quali la creazione di un indice in RAM, utilizzato soprattutto per aumentare le prestazioni (attenzione però al limite imposto dalle dimensioni della memoria!).
All’interno dell’indice vengono inseriti documenti (istanze di org.apache.lucene.document.Document), a loro volta divisi in campi o colonne (Fields); come nei database esistono vari tipi di campi, anche se in questo caso il tipo di dati è lo stesso per ogni colonna (o quasi, in realtà esistono alcune piccole differenze).
I campi testuali veri e propri (come il titolo ed il corpo di un documento) verranno indicizzati , mentre altri campi, ad esempio l’identificatore del documento all’interno di un database, saranno solo salvati, per essere utilizzati come riferimento.
In pratica non sarà possibile eseguire ricerche in questi campi, ma solo accedere al loro valore, nei documenti ritornati da una query eseguita su un altro campo. Lo scenario di utilizzo prevede infatti nella quasi totalità dei casi l’affiancamento della base di dati full-text ad una relazionale.
Così facendo utilizzeremo Lucene per fornire un sistema di ricerca (e per presentarne i risultati), mentre quando l’utente desiderà visualizzare un documento (ad esempio cliccando sul titolo) accederemo ad una pagina popolata dal database relazionale; sfrutteremo Lucene per l’efficienza delle ricerche ed il DBMS per la maggiore velocità nella selezione di un singolo elemento (aiutato in questo magari da un opportuno meccanismo di caching, attivo e passivo).

Inserire un documento nell’indice

L’inserimento di un documento all’interno dell’indice comporta una scansione del testo, per individuare le parole presenti; tale procedimento prende il nome di analisi ed ovviamente è dipendente dalla lingua del testo. Lucene fornisce un analizzatore standard, mirato sulle necessità delle lingue occidentali più diffuse; in questo articolo verrà utilizzato, ma per noi italiani è necessaria una piccola precisazione: in questa maniera le parole apostrofate non verranno riconosciute correttamente (ad esempio un’ancora non verrà diviso in un ed ancora, ma resterà un unico termine).
Una volta che il documento (o meglio, i suoi campi) sono stati indicizzati questi sono disponibili per l’esecuzione di ricerche.

Il Listato 1 mostra come sia possibile aggiungere un documento all’indice, prendendo come scenario un ipotetico sistema CMS Web.


/* Listato1 */
import org.apache.lucene.index;

protected void addDocument(String aPath, String theSubject, String theBody, int anId){
    //Apro l'oggetto attraverso il quale accedo all'indice
    IndexWriter writer = new IndexWriter(aPath, new StandardAnalyzer(), true);
    //Creo un nuovo documento
    org.apache.lucene.document.Document aDoc = new org.apache.lucene.document.Document ();
    //Aggiungo i campi opportuni
    aDoc.add(Field.Text("subject", theSubject));
    aDoc.add(Field.Text("body", theBody));
    aDoc.add(Field.UnIndexed("id", anId));
    //Salvo il documento nell'indice
    writer.addDocument(aDoc);
    //eseguo un'ottimizzazione dell'indice
    writer.optimize();
    //Infine chiudo l'accesso in scrittura all'indice
    writer.close();
}

Le operazioni avvengono attraverso un’istanza di org.apache.lucene.index.IndexWriter. Il documento contiene tre campi, il titolo, il corpo del testo e l’identificativo numerico del documento all’interno della base di dati relazionale.
Come abbiamo già detto, i campi possono essere trattati in maniera diversa, a seconda della funzione che rivestono. I campi testuali sono normalmente analizzati (scomposti in una lista di termini) e quindi indicizzati, mentre il campo identificatore (che contiene solamente un riferimento al corrispondente elemento della base di dati relazionale) deve essere solamente salvato all’interno dell’indice, per essere usato come riferimento tra l’indice full-text e una base di dati relazionale.
Nel listato1, come possiamo vedere utilizziamo i metodi statici della classe Field per creare i campi adatti alle nostre esigenze (Field.Text(), Field.UnIndexed()).

Il listato 2, mostra uno scenario differente, in cui l’indicizzazione avviene in una directory del filesystem.


/** Listato 2 **/
import java.io;
import org.apache.lucene.index;

...

public static void index(String aPath, String indexPath) throws IOException {
    File file=new File(aPath);
    if (!file.exists() || !file.isDirectory()) {
        throw new IOException("Verificare l'esistenza della directory " + file + "!");
    }

    IndexWriter writer = new IndexWriter(indexPath, new StandardAnalyzer(), true);
    indexDirectory(writer, file);
    writer.close();
}

private static void indexDirectory(IndexWriter writer, File dir) throws IOException {
    File[] files = dir.listFiles();

    for (int i=0; i < files.length; i++) {
        File f = files[i];
        if (f.isDirectory()) {
            //Esplora la sottodirectory
            indexDirectory(writer, f);
        }
        else if (f.getName().endsWith(".txt")) {
            indexFile(writer, f);
        }
    }
}

Un’interessante osservazione, relativa sia al primo che al secondo listato, ci consente di notare che in entrambi è presente la seguente istruzione:


writer.optimize();

L’indicizzazione di documenti è ottimizzata da Lucene, in maniera tale da aumentare le prestazioni senza però interferire con eventuali operazioni contemporanee di ricerca. Il metodo IndexWriter.optimize() serve appunto a riorganizzare l’indice, per aumentare le prestazioni delle future ricerche (e scritture). Nel primo caso, avendo ipotizzato di trovarci in un CMS, possiamo supporre che gli inserimenti di nuovi documenti siano relativamente sporadici, mentre nel secondo ci troviamo di fronte ad una sorta di operazione batch, pertanto effettuiamo l’ottimizzazione solamente al termine di tutti gli inserimenti (o di una tranche di questi).
La gestione dell’accesso concorrente (in scrittura e lettura, quindi indicizzazione e ricerca) ad un indice Lucene è piuttosto articolata, anche se di facile comprensione: è possibile un solo accesso in scrittura, ma che può essere condiviso da vari processi logici (o thread) di indicizzazione, mentre non sono poste limitazioni alle ricerche contemporanee.

La ricerca

È possibile eseguire ricerche sull’indice in due modalità distinte: utilizzando direttamente le API (http://lucene.apache.org/java/docs/api/index.html) di ricerca oppure utilizzando una sorta di linguaggio di interrogazione. Il primo metodo prevede l’utilizzo delle classi che derivano da Query e che si trovano nel package org.apache.lucene.search, per brevità aggiungerò solo alcune note su queste classi più avanti, per informazioni più approfondite si può leggere la documentazione.
Nel listato 3 possiamo trovare un esempio di come sia possibile utilizzare la classe QueryParser per eseguire una semplice ricerca. La sintassi della stringa di ricerca da passare al metodo QueryParser.parse() è disponibile in http://lucene.apache.org/java/docs/queryparsersyntax.html.


/** Listato 3 **/
public void search(String aDirectory, String q) throws Exception{
    IndexSearcher searcher = new IndexSearcher(aDirectory);

    Query query = QueryParser.parse(q, "body", new StandardAnalyzer());
    //questo metodo esegue la ricerca nell'indice
    Hits hits = searcher.search(query);
    System.out.println("La ricerca ha restituito "+ hits.length() +" elementi.");
    int i, c;
    c= hits.length();
    //ciclo sui risultati della ricerca
    for (i = 0; i < c;i++) {
    Document doc = hits.doc(i);
    System.out.println(doc.get("subject") + " ? Score: " + hits.score(i));
    }
    //chiudo l'accesso in lettura all'indice
    searcher.close();
}

L’accesso in lettura all’indice avviene attraverso la classe IndexSearcher. Il metodo QueryParser.parse() ci permette di ottenere direttamente un oggetto Query; è necessario passare a questo metodo la stringa di ricerca (che naturalmente può essere composta da più parole), un campo in cui eseguire la ricerca ed un analizzatore (usato per trattare la query string, che sostanzialmente è elaborata come se fosse un documento da inserire nell’indice).
Il metodo IndexSearcher.search() restituisce un oggetto di tipo Hits, che contiene i risultati. Il codice mostra come accedere al titolo dei documenti restituiti ed anche come stampare un punteggio che rappresenta in forma numerica l’attinenza del documento alla query. Questo punteggio è calcolato sulla base di un’analisi semantica della query, cercando cioè di interpretare l’importanza che hanno i termini nel fornire il significato del testo.
Per evitare di incappare in errori abbastanza fastidiosi e che potrebbero rivelarsi di difficile interpretazione è bene avere presente che i documenti all’interno della collezione Hits non sono più accessibili quando si è chiamato il metodo IndexSearcher.close(), pertanto è necessario estrarre i dati necessari alla presentazione dei risultati prima di procedere a questa chiamata.
Naturalmente utilizzando le API è possibile eseguire ricerche più complesse, ad esempio per termini vicini tra loro(PhraseQuery), oppure utilizzando wildcards. Per un’introduzione a queste funzionalità rimando ai documenti presenti nella bibliografia in fondo all’articolo.

Conclusioni

La necessità di trattare in maniera opportuna dati testuali appartiene ormai alla maggior parte dei progetti software, soprattutto grazie all’enorme sviluppo di Internet. In questo articolo è stato presentato un componente estremamente avanzato, che permette di realizzare con facilità soluzioni anche complesse. Per testimoniare la potenza di Lucene posso aggiungere che su questa base è stato costruito un progetto, Nutch (http://lucene.apache.org/nutch), che ha creato un’infrastruttura tecnologica assolutamente in grado di competere con quelle dei più diffusi motori di ricerca commerciali.
Per rimanere nel campo delle soluzioni che lo sviluppatore si trova a dover realizzare più frequentemente segnalo, ad esempio, la possibilità di estrarre da un indice documenti in base alla similitudine: in questa maniera un sistema di commercio elettronico potrà mostrare prodotti simili a quello correntemente visualizzato dall’utente (sulla base delle descrizione), oppure consultando una banca dati sarà possibile estrarre articoli simili per significato ad un testo scelto dall’utente.
Inoltre è possibile realizzare sistemi di correzione ortografica, simili a quelli presenti negli elaboratori di testi.
Naturalmente lo scopo di questo articolo non è quello di mostrare le soluzioni più avanzate, ma avvicinare all’utilizzo di tale tecnologia. Credo che a questo punto per il lettore non sarà difficile realizzare le prime applicazioni basate su Lucene, magari avvalendosi dei documenti riportati nella bibliografia. Tra questi mi sento di raccomandare la lettura e la frequentazione della mailing list general@lucene.apache.org, attraverso la quale è possibile accedere ad esempi o best practices.

Riferimenti bibliografici

Lucene
http://lucene.apache.org

Lucene Wiki
http://wiki.apache.org/jakarta-lucene

E. Hatcher, O. Gospodnetic, Lucene in Action, Manning, 2004

Mailing List general@lucene.apache.org
(archivi su http://mail-archives.apache.org/mod_mbox/lucene-general/)

Avatar
Lorenzo Viscanti

Lorenzo Viscanti è un ingegnere informatico e si occupa soprattutto di problematiche relative al trattamento di testi, quali realizzazioni di motori di ricerca, text clustering e text mining.

Facebook
Twitter
LinkedIn
Picture of Lorenzo Viscanti

Lorenzo Viscanti

Lorenzo Viscanti è un ingegnere informatico e si occupa soprattutto di problematiche relative al trattamento di testi, quali realizzazioni di motori di ricerca, text clustering e text mining.
Tutti gli articoli
Nello stesso numero
Loading...

MJPF: micro Java Plugin Framework

Un framework per realizzare applicazioni estensibili

Java in pista!

Un sistema di telemetria per la Formula 1

Service Oriented Architecture: dalla teoria alla pratica

I parte: Introduzione

JSP (o WebWork?), riposa in pace…

Architetture e tecniche di progettazione EJB

II parte: la comunicazione con client-session

Applicazioni Desktop

Parte I: toolbar in stile aqua

Integration Patterns

I parte

Il tunneling HTTP

Comunicare con un‘applicazione server

Soluzioni Oracle per la scalabilità e l‘affidabilità

III parte

Il networking in Java

III parte: UDP e Datagrammi

Process Oriented Development: il processo alla base dello sviluppo

I parte: un approccio diverso ai requisiti di business

Java Business Integration

I parte

Nella stessa serie
Loading...

Il dilemma del prigioniero

Un “gioco serio” per comprendere la cooperazione

Accessibilità in team di prodotto: sfide, normative e best practice

II parte: Analisi di un caso reale

Adattare l’agilità ai contesti: una chiave di lettura

I parte: Un caso di studio con le sue peculiarità

Accessibilità in team di prodotto: sfide, normative e best practice

I parte: Cosa è l’accessibilità e perché implementarla

Il web al tempo della GEO (Generative Engine Optimization)

II parte: Strategie per strutturare i contenuti

Un backlog non tanto buono

II parte: Caratteristiche e ruolo del backlog.

FIWARE: Open APIs for Open Minds

V parte: Implementazione del sistema di ricarica

Il web al tempo della GEO (Generative Engine Optimization)

I parte: Struttura e ricerca delle informazioni

Un backlog non tanto buono

I parte: Un progetto con qualche difficoltà

DDD, microservizi e architetture evolutive: uno sguardo d’insieme

X parte: Il ruolo del Software Architect

FIWARE: Open APIs for Open Minds

IV parte: Sistema di ricarica intelligente per veicoli elettrici

Tra Play14 e serious gaming

Un ponte tra gioco e apprendimento

DDD, microservizi e architetture evolutive: uno sguardo d’insieme

IX parte: Event Sourcing is not Event Streaming

FIWARE: Open APIs for Open Minds

III parte: Tecnologie e implementazione

Agilità organizzativa

II parte: Qualche caso d’esempio

Agilità organizzativa

I parte: Individui e interazioni nelle aziende moderne

FIWARE: Open APIs for Open Minds

II parte: Generic Enablers per costruire ecosistemi smart

Intelligenza artificiale e industria

Riflessioni sull’uomo e sulla macchina

Effetto Forrester e dinamiche dei sistemi di produzione

La storiella di una birra per comprendere il Lean

DDD, microservizi e architetture evolutive: uno sguardo d’insieme

VIII parte: La filosofia dell’architettura del software

Digital revolution: trasformare le aziende in ecosistemi digitali

XVIII parte: Una piattaforma comune a tutti gli eventi

Scene dalla “neolingua”

Panoramica semiseria dell’incomunicabilità aziendale

Autenticazione schede elettorali… lean!

Simulazione lean nella gestione di un seggio

FIWARE: Open APIs for Open Minds

I parte: Fondamenti e architettura

Mokabyte

MokaByte è una rivista online nata nel 1996, dedicata alla comunità degli sviluppatori java.
La rivista tratta di vari argomenti, tra cui architetture enterprise e integrazione, metodologie di sviluppo lean/agile e aspetti sociali e culturali del web.

Imola Informatica

MokaByte è un marchio registrato da:
Imola Informatica S.P.A.
Via Selice 66/a 40026 Imola (BO)
C.F. e Iscriz. Registro imprese BO 03351570373
P.I. 00614381200
Cap. Soc. euro 100.000,00 i.v.

Privacy | Cookie Policy

Contatti

Contattaci tramite la nostra pagina contatti, oppure scrivendo a redazione@mokabyte.it

Seguici sui social

Facebook Linkedin Rss
Imola Informatica
Mokabyte