DNM+ Online
dotnetmania 2.0
Recuperación de información con Lucene.NET
Recientemente se ha publicado una nueva versión de Lucene.NET, una librería de código abierto que nos permite realizar diversas tareas de Recuperación de información. Puede ser una solución ideal para incorporar a nuestras aplicaciones .NET características de indexación y búsqueda sobre bases de datos o colecciones de documentos. En este artículo presentaremos los fundamentos de Lucene.NET y veremos lo sencillo que es generar una aplicación básica que realice búsquedas sobre documentos indexados de manera similar a como lo haría un buscador web.

Estamos de cumpleaños, Lucene acaba de cumplir su décimo aniversario el pasado mes de septiembre. Han pasado 10 años desde que Doug Cutting creó una de las herramientas más útiles en el campo de la Recuperación de información. Son numerosas las webs y productos comerciales que utilizan esta fantástica librería: Wikipedia, Twitter, MySpace, LinkedIn... Pero, ¿qué es Lucene exactamente?

¿Qué es Lucene.NET?

Lucene es una librería que nos permite incorporar en nuestras aplicaciones diversas tareas de recuperación de información. En concreto, nos permite indexar diversos tipos de información y realizar posteriormente búsquedas sobre el índice generado. Escrita originalmente en Java y publicada bajo licencia open source, existen versiones de Lucene para otros muchos lenguajes, entre ellos C#, por lo que Lucene .NET nos permitirá añadir características de búsqueda en nuestras aplicaciones .NET de una forma rápida y sencilla.

Lucene.NET no es una aplicación completa; tampoco es un buscador, ni un crawler. Lucene es una biblioteca de software, una API que permite implementar fácilmente procesos de indexación y búsqueda.

Conceptos básicos de IR

La Recuperación de información o Information Retrieval (IR) es el área de estudio dedicada a la búsqueda de documentos, de información en documentos o metadatos que describan documentos, y también a la búsqueda en bases de datos relacionales y la web. Es un campo muy amplio, donde confluyen varias disciplinas: informática, matemáticas, lingüística, biblioteconomía, arquitectura de la información, estadística, psicología cognitiva y otros.

Un proceso de recuperación de información se inicia cuando un usuario introduce en el sistema una consulta que representa una necesidad de información. El sistema, que previamente ha indexado una colección de documentos, responde a dicha consulta con un conjunto de resultados, cada uno de los cuales tiene asignada una cierta relevancia para la consulta dada.

El diseño de un sistema de IR es una tarea altamente compleja que incluye técnicas muy sofisticadas. La popularidad de Lucene se basa precisamente en que nos abstrae de esta complejidad, y como veremos a continuación, la indexación y búsqueda de documentos en nuestras aplicaciones será una tarea muy sencilla.

Instalación

Comenzar a utilizar Lucene.NET es muy fácil, ya que no requiere de ningún tipo de instalación. Simplemente deberemos descargarnos la última versión disponible desde la web oficial [1], que incluye la propia librería Lucene.Net.dll, así como algunos ficheros de ejemplo. Bastará con referenciar dicho ensamblado en nuestro proyecto de Visual Studio, y podremos comenzar a trabajar.

Indexación

Supongamos que necesitamos buscar un texto en un conjunto de documentos. Una forma obvia de hacer esto es recorrer los documentos en busca de dicho texto. Pero a medida que la colección de documentos crezca, ese planteamiento provocará graves problemas de rendimiento. Es por ello que para la búsqueda sobre grandes colecciones de datos se debe construir un índice que registre las palabras que aparecen en cada documento. Además, el índice debe ser una estructura de datos optimizada para realizar lecturas muy rápidas, por lo que los índices tienen forma de índice inverso: un listado de palabras donde por cada palabra se registra los documentos en los que aparece.

Lucene.NET nos permitirá indexar fácilmente diferentes tipos de ficheros, e-mails o incluso registros de una base de datos. Para ello, Lucene proporciona un esquema flexible de datos basado en los conceptos de documento (Document) y campo (Field). Un documento para Lucene es la unidad mínima de indexación y búsqueda; es una especie de contenedor que incluye varios campos. A modo de ejemplo, indexaremos varios números antiguos de la revista dNM en formato texto. A partir de cada documento de texto, se generará un documento Lucene que incluirá dos campos: “nombre del fichero" y “contenido". Antes de realizarse la indexación, se deben obtener los diferentes tokens (que a grandes rasgos se corresponden con palabras) que componen el texto. Esta labor corresponde al analizador (Analyzer), que extrae las palabras del texto y basándose en la gramática del lenguaje realiza tareas adicionales: extraer la raíz léxica de las palabras (lematización), reducir las palabras en plural a su forma singular, convertir el texto en minúsculas, descartar signos de puntuación, o eliminar palabras poco relevantes para la búsqueda (preposiciones, artículos, etc.), entre otras.

Para realizar la indexación (listado 1) instanciaremos un objeto de tipo IndexWriter, que se encargará de generar los índices y recibirá, entre otros parámetros, la ruta donde almacenar los ficheros del índice y el analizador a utilizar (en este caso StandardAnalyzer). El siguiente paso será recorrer los ficheros existentes y enviarlos a nuestro IndexWriter.

// Inicializamos el escritor de índices
Lucene.Net.Index.IndexWriter writer = new IndexWriter(
  FSDirectory.Open(rutaIndices), 
  new StandardAnalyzer(Version.LUCENE_CURRENT), 
  true,
  IndexWriter.MaxFieldLength.LIMITED);

// Recorremos los ficheros a indexar, pasándoselos al writer
String[] ficheros = Directory.GetFiles(rutaIndexacion);
foreach (string nombreFichero in ficheros )
{
  Console.WriteLine("Indexando: " + nombreFichero);
  writer.AddDocument(getDocument(new FileInfo(nombreFichero)));
}
writer.Close();
Listado 1. Indexando varios ficheros con Lucene.NET

Para poder indexar un fichero, antes hay que obtener el contenido del mismo. Esto es inmediato para ficheros de texto, pero puede ser más complejo con otros formatos. Lucene es independiente del formato de fichero y deja en nuestras manos el desarrollo de estas tareas, aunque existen proyectos asociados a Lucene, como Tika [2], que proporcionan filtros para los formatos de archivos más habituales. En nuestro ejemplo utilizaremos ficheros de texto, aunque de igual forma se podrían indexar documentos PDF, Microsoft Word, XML, etc.

En el listado 2 podemos observar cómo por cada documento de texto existente creamos un documento Lucene (Document) con dos campos (contenido y nombreFichero). La granularidad de indexación influirá en la calidad de nuestras búsquedas posteriores. Si indexamos, por ejemplo, con un campo independiente, los autores de los artículos de la revista posteriormente podríamos realizar búsquedas centradas en dichos autores.

// Devuelve un documento Lucene a partir de un fichero de texto
public static Document getDocument(FileInfo fichero)
{
    // Generamos el documento Lucene
    Lucene.Net.Documents.Document doc = new Document();

    // Leemos el contenido del fichero
    TextReader readerFichero = new StreamReader(fichero.FullName);

    // Añadimos los campos al documento
    doc.Add(new Field("contenido", readerFichero));
    doc.Add(new Field("nombreFichero", 
        fichero.Name,
        Field.Store.YES,
        Field.Index.NOT_ANALYZED));
    return doc;
}
Listado 2. Generación de documentos para Lucene.NET

En la figura 1 podemos ver los ficheros indexados. El índice generado consiste en una serie de ficheros binarios, aunque es posible visualizar el contenido de dicho índice con herramientas como Luke [3] (figura 2), lo cual es muy útil en la fase de desarrollo para afinar el proceso de indexación.

Figura 1. Ficheros indexados

Figura 2. Visualización del contenido del índice con Luke

Búsqueda

Una vez indexada nuestra colección, construiremos un sencillo buscador que nos permitirá realizar consultas sobre el contenido de las revistas. Lucene.NET no proporciona ningún tipo de interfaz de usuario por defecto; podremos crearnos desde una simple aplicación de consola a una elaborada interfaz web. En nuestro caso, por simplicidad, optaremos por la primera opción, que se presenta en el listado 3.

public static void Main(System.String[] args)
{
    String indexPath = @"C:\demoLucene\index";
    String campo = "contenido";
    String consulta = "MVC OR Silverlight";

    // Inicializamos el buscador
    Directory indexDirectory =
        FSDirectory.Open(new DirectoryInfo(indexPath));
    Searcher buscador = new IndexSearcher(indexDirectory, true);

    // Preparamos la consulta a realizar
    Analyzer analizador = new StandardAnalyzer(Version.LUCENE_CURRENT);
    QueryParser parser = new QueryParser(campo, analizador);
    Query query = parser.Parse(consulta);

    // Ejecutamos la consulta y obtenemos los resultados
    Lucene.Net.Search.TopDocs resultados = 
        buscador.Search(query, null, 10);
			
    // Mostramos los resultados obtenidos
    foreach (ScoreDoc scoreDoc in resultados.scoreDocs) 
    {
        Document d = buscador.Doc(scoreDoc.doc);
        Console.WriteLine(d.Get("nombreFichero") + scoreDoc.score );
    }
    buscador.Close();
}
Listado 3. Búsqueda sobre un índice

Para realizar una consulta sobre nuestro índice, deberemos instanciar un objeto de tipo IndexSearcher, que recibirá como parámetro el directorio donde se encuentran los índices. También necesitaremos generar un QueryParser, que transformará la consulta introducida por el usuario (en el ejemplo, “MVC OR Silverlight") en un objeto Query que Lucene sea capaz de procesar. Deberemos especificar además el analizador a utilizar sobre la consulta y el campo sobre el cual consultar. Lucene.NET proporciona una sintaxis de consultas de búsqueda con las operaciones más habituales (ver tabla 2), aunque si nuestras necesidades son mayores, éste es un aspecto ampliamente personalizable.

Tabla 1. Clases más habituales en Lucene.NET

Tabla 2. Ejemplos de expresiones de búsqueda soportadas por Lucene.NET

Una vez realizada la búsqueda, obtendremos el conjunto de resultados en forma de una instancia de la clase TopDocs. Además, mediante la propiedad score de los resultados de búsqueda es posible consultar el grado de relevancia que ha obtenido cada documento devuelto con respecto a la consulta realizada. En la figura 3 podemos ver el resultado de nuestra búsqueda.

Figura 3. Búsqueda sobre el índice

Conclusiones

El ejemplo aquí presentado es muy sencillo, pero Lucene.NET nos permite un alto grado de personalización y afinación en indexación y búsquedas. En una solución profesional, será habitual indexar no sólo el contenido del documento, sino varios otros campos (fecha de creación, resumen, título, autor, etc.), lo que nos permitiría filtrar después las búsquedas por un determinado campo. Se podrían indexar además documentos en varios idiomas diferentes (utilizando para ello diferentes analizadores), podríamos hacer un boosting de ciertos documentos relevantes para asignarles de forma manual un mayor ranking, es posible personalizar el procesamiento de la consulta introducida por el usuario para generar consultas complejas, etc. A medida que se profundice en el conocimiento de la librería y en las diversas técnicas de recuperación de información  que ofrece, se descubrirá el gran número de opciones disponibles.

blog comments powered by Disqus
autor
referencias