Páginas

lunes, 4 de febrero de 2013

Primeros pasos con Apache Lucene

Introducción

Lucene es un motor de búsqueda de Apache. Está implementado en Java (http://lucene.apache.org) y es usado en multitud de gestores de contenido.

Lucene indexa texto, no trabaja directamente sobre ningún formato de fichero concreto. Por tanto, para indexar "documentos",  previamente deberá extraerse su contenido con las librerías adecuadas.

Dado cualquier texto, Luceneindexa por defecto las primeras 1000 palabras.

Algunas librerías típicas para menejar los documentos más comunes son:

    PDF. JPedal (http://www.jpedal.org/).
    Microsoft Word, Excel, Visio y Power Point. Apache POI (http://poi.apache.org/)
    OpenOffice. LIUS (http://www.bibl.ulaval.ca/lius/).
    RTF. Swing RTFEditorKit class.

Una vez indexado el contenido de un "documento", se podrán efectuar búsquedas sobre los índices generados. Al efectuarse las búsquedas sobre los índices estas son siempre más rápidas que búsquedas sobre los "documentos".

Componentes de Lucene

En Lucene trabaja con tres conceptos esenciales:
  • Documentos. Un documento, para Lucene, es una tupla de N campos. Cada campo tiene un nombre y contiene cierto tipo de información.
  • Campos. Es cada una de las categorías de información que componen el documento.
  • Querys. Para realizar las búsquedas en el índice, Lucene ofrece un lenguaje de consultas, que permite especificar en qué campos buscar, admite comodines, admite AND, OR y NOT, admite búsquedas por proximidad, etc.
Veamos por ejemplo un par de "documentos":
  • Agenda. En una agenda de contactos, un documento sería cada una de las fichas de la agenda. Los campos serían, por ejemplo, el nombre, el primer apellido, el segundo apellido, el teléfono móvil, el teléfono fijo, etc
  • Biblioteca. En una biblioteca, un documento sería cada uno de los libros. Los campos serían, por ejemplo: título, autor, estantería, contenido, ISBN, etc.
Veamos por ejemplo algunas querys en el índice de la biblioteca:
  • Lista de libros donde el autor sea "Phillip"
        autor:Phillip
  • Lista de libros donde el autor sea "Philip K Dick"
        autor:"Philip K Dick"
  • Lista de libros donde el autor sea "Philip K Dick", en el contenido del libro aparezca "ovejas"
        autor:"Philip K Dick" AND contenido:ovejas

Apliación Java de ejemplo

Hemos montado un proyecto Maven2 con las siguientes dependencias:

  <dependencies>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-core</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-queryparser</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-analyzers-common</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-io</artifactId>
          <version>1.3.2</version>
      </dependency>
  </dependencies>


Adjuntamos un ejemplo en Java donde se montará el índice de una biblioteca, añadiendo varios documentos y efectuando algunas búsquedas.

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

public class AppTest {

    public static void main(String[] args) throws IOException, ParseException, InvalidTokenOffsetsException {

        //Fijamos el analizador para tokenizar, indezar y buscar en textos
        final StandardAnalyzer analizador = new StandardAnalyzer(Version.LUCENE_40);

        //Creamos el indice en memoria RAM 

        final Directory indice = new RAMDirectory();
        final IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_40, analizador);

        //Montamos el generador del índice
        final IndexWriter iw = new IndexWriter(indice, config);

        //Nuestros documentos serán libros con tres campos: titulo, autor y contenido.
        //Añadimos un par de libros, los ficheros txt con el contenido de los mismos están en el classpath
        addLibro(iw, "El ingenioso hidalgo Don Quijote de la Mancha", "Miguel de Cervantes y Saavedra", new File("Quijote.txt"));
        addLibro(iw, "El Buscón", "Francisco Gómez de Quevedo y Santibáñez Villegas", new File("Buscon.txt"));
 

        //Cerramos el generador de índice
        iw.close();

        //Lanzamos algunas querys
        lanzaConsulta(analizador, indice, "autor:Cervantes");
        lanzaConsulta(analizador, indice, "autor:\"de Cervantes y\"");
        lanzaConsulta(analizador, indice, "autor:Cervan*");
        lanzaConsulta(analizador, indice, "autor:Cervantes AND titulo:hidalgo");
        lanzaConsulta(analizador, indice, "autor:Cervantes OR autor:Quevedo");
        lanzaConsulta(analizador, indice, "(autor:Cervantes OR autor:Quevedo) AND NOT titulo:Quijote");


        //Algunas querys de búsqueda por proximidad, esto es palabras parecidas a la nuestra 
        //pudiendo diferenciase en N caracteres
        lanzaConsulta(analizador, indice, "contenido:Tocoso~1"); //Buscamos a Dulcinea del "Tocoso" sabiendo que es algo parecido
    }

    private static void addLibro(final IndexWriter iw, 

                                                  final String titulo, 
                                                  final String autor, 
                                                  final File f) throws Exception {

        //Montamos el documento
        final Document libro = new Document();

        //Le añadimos el atributo titulo
        libro.add(new TextField("titulo", titulo, Field.Store.YES));

        //Le añadimos el atributo autor
        libro.add(new TextField("autor", autor, Field.Store.YES));

        //Le añadimos el atributo contenido
        libro.add(new TextField("contenido", new FileReader(f)));

        //Le añadimos la ubicacion del fichero
        libro.add(new TextField("fichero", f.getCanonicalPath(), Field.Store.YES));

        //Damos de alta el documento
        iw.addDocument(libro);
    }


    private static void lanzaConsulta(final StandardAnalyzer analizador, 

                                                           final Directory indice, 
                                                           final String query) throws ParseException, IOException {

        //Montamos el parseador de Querys. El segundo parámetro es el campo por defecto donde
        //se realizarán las búsquedas si una query no indica ningún campo concreto
        final QueryParser queryParser = new QueryParser(Version.LUCENE_40, "titulo", analizador);
        final Query q = queryParser.parse(query);

        //Montamos el buscador
        final IndexReader reader = DirectoryReader.open(indice);
        final IndexSearcher searcher = new IndexSearcher(reader);

        //Lanzamos la query
        final int numMaxResults = 10;
        final TopDocs docsEncontrados = searcher.search(q,numMaxResults);   
        System.out.println("Total hits "+docsEncontrados.totalHits);

        //Recuperamos la lista de coincidencias
        final ScoreDoc[] docs = docsEncontrados.scoreDocs;

        //Mostramos los resultados
        System.out.println("-------------------------------------------------------------------");
        System.out.println("Encontradas " + docsEncontrados.totalHits + " coincidencias para '"+query);
        int idDoc;
        int index;
        float score;
        Document doc;
        for(int i=0;i<docs.length;++i) {
            idDoc = docs[i].doc;
            score = docs[i].score;
            index = docs[i].shardIndex;
            doc = searcher.doc(idDoc);
            System.out.println(String.format("%d. [%f] \t %s \t %s",index,score,doc.get("titulo"),doc.get("autor")));
        }
        System.out.println("-------------------------------------------------------------------");
    }
}


Enlaces de interés

http://wiki.apache.org/lucene-java/LuceneFAQ
http://www.lucenetutorial.com/

No hay comentarios:

Publicar un comentario