Framework para indexación de documentos en Lucene: LIUS

1
20906

Framework para indexación
de documentos en Lucene: LIUS

Introducción

En este
tutorial, complementario del de
Extracción
de texto de documentos Office desde Java
,
vamos a ver como usar el framework LIUS (Lucene Index Update and
Search) para indexar documentos en el motor de busqueda textual
Lucene, de la Fundación Apache. El framework LIUS ha sido
desarrollado por autores franceses y está disponible en
Sourceforge, bajo licencia LGPL.

La
URL del proyecto es http://sourceforge.net/projects/lius/.

Este
tutorial asume que el lector conoce Lucene y lo ha usado alguna vez.
Si no es así, consultar el tutorial Primeros
pasos con Lucene
.

Opinión
del autor sobre el framework

La
idea del framework en si es algo lógico, puesto que cualquiera
que haya usado Lucene alguna vez se habrá encontrado con la
misma carencia: Lucene es solo un motor de busqueda, pero nos falta
algo más para indexar y para manipular el motor de forma más
sencilla. En ese sentido, esta librería agrega una fina capa
por encima de Lucene para organizar el trabajo de extraer datos de
distintos tipos de documentos y mapear esos datos a campos de Lucene.
Además, el framework permite manipular mediante configuracion
XML que campos se crean, de que tipo, que analizadores se deben usar,
etc. En otras palabras: LIUS ofrece más comodidad a la hora de
configurar Lucene y decidir qué, cómo y dónde
indexamos.

Aunque
la idea es muy buena, se notan algunos fallos en el diseño y
la programación del framework que disminuyen su calidad y
pueden afectar a su mantenibilidad futura. No obstante, estos fallos
no nos deben preocupar como usuarios del framework porque la interfaz
entre el usuario y el framework está bien definida.

Entre
estos problemas detectados se dan:

  • Uso
    pobre de interfaces y clases abstractas

  • Uso
    de constantes a pelo en el código

  • Pésima
    documentación (y además parte de ella sólo en
    francés)

  • Poco
    uso de tipos de datos (muchas cosas se manejan directamente como
    java.lang.Object)

Descripción
del framework

LIUS
se compone de una librería propia y de otras de terceros que
se usan para extraer texto de distintos tipos de documentos. En el
momento de escribir este tutorial, Lius es capaz de indexar los
siguientes tipos de documentos:

Además,
LIUS se basa en ficheros XML de configuración que permiten
describir como se debe configurar Lucene para indexar y/o buscar en
los documentos. Lo normal es tener un solo fichero XML, pero se
pueden usar más para, por ejemplo, definir distintas formas de
indexar o buscar. En el fichero de configuración se puede
especificar:

  • El
    analizador que debe usar Lucene.

  • Los
    parametros de creación del indice.

  • Qué
    datos se deben extraer de cada tipo de documento y a que campos de
    Lucene se deben mapear. Por ejemplo, podemos decir que el contenido
    de los PDFs lo meta en un campo de Lucene llamado
    content,
    el
    titulo en otro llamado
    title,
    y el autor en el campo
    writer.

  • Cómo
    se deben configurar los campos de Lucene en los que se introducen
    los datos. Por ejemplo, tenemos los siguientes tipos de campo:

    • Text:
      campo de texto almacenado y tokenizado.

    • TextReader:
      campo de tipo stream.

    • Keyword:
      campo de texto almacenado y no tokenizado.

    • concatDate:
      campo tipo fecha almacenado y no tokenizado.

    • UnIndexed:
      campo de texto almacenado y no indexado.

    • UnStored:
      campo de texto no almacenado y tokenizado.

  • Qué
    boost
    (peso)
    se
    debe aplicar a cada campo y al documento entero.

  • Qué
    tipo de búsqueda se quiere emplear. De momento hay
    disponibles:

    • queryTerm: búsqueda por un
      solo campo.

    • rangeQuery: búsqueda de
      rango por un solo campo.

    • queryParser: usar el parser de
      consultas de Lucene sobre un solo campo.

    • multiFieldQueryParser: usar el
      parser de consultas de Lucene sobre varios campos.

  • Qué
    campos debe devolver la búsqueda y como formatearlos (si se
    deben fragmentar o no y si se deben resaltar los términos
    buscados).

Un
ejemplo

Para
el ejemplo vamos a hacer un programa que indexe todos los ficheros
contenidos en un directorio. Para ello primero debemos descargar el
proyecto Lius de http://sourceforge.net/projects/lius/.
Una vez descargado el fichero
Lius-1.0.zip
lo
descomprimimos, con lo que obtendremos la siguiente estructura de
directorios:

  • classes

  • Config

  • doc

  • ExempleFiles

  • legal

  • lib

  • src

Ahora
crearemos un proyecto Java con nuestro editor favorito y le
añadiremos como librerías todos los JARs contenidos en
el directorio
lib
de
Lius, así como el
Lius-1.0.jar,
en el directorio principal. A continuación copiaremos los
ficheros
log4j.properties
y
liusConfig.xml
del
directorio
Config
de
Lius al directorio
config
de
nuestro proyecto. Por último, copiamos los documentos del
directorio
ExempleFiles/testFiles
a
un directorio llamado
docs
dentro
de nuestro proyecto.

Ahora
creamos una clase Java que contenga el
main()
con
el siguiente código:

package com.autentia.tutorial.lius;

import java.io.File;
import java.util.Collection;
import java.util.List;
import lius.LiusLogger;
import lius.config.LiusConfig;
import lius.config.LiusConfigBuilder;
import lius.config.LiusDocumentProperty;
import lius.config.LiusField;
import lius.index.Indexer;
import lius.index.IndexerFactory;
import lius.search.LiusHit;
import lius.search.LiusHitList;
import lius.search.SearchIndex;

/**
 *
 * @author ivan
 */
public class Lius
{

  // Directorio donde se creara el índice de Lucene
  private final String indexDir = "index";
  // Localización del fichero de configuración de LOG4J
  private final String log4jFile = "config/log4j.properties";
  // Localización del fichero de configuración de Lius
  private final String configFile = "config/liusConfig.xml";

  private LiusConfig config;

  /**
   * @param args the command line arguments
   */
  public static void main( String[] args )
  {
    try
    {
      new Lius(  ).run(  );
    }
    catch( Throwable t )
    {
      t.printStackTrace(  );
    }
  }

  /**
   *
   */
  private void run( ) throws Exception
  {
    // Inicializamos LOG4J
    LiusLogger.setLoggerConfigFile( log4jFile );
    // Leemos el fichero de configuración de Lius
    config = LiusConfigBuilder.getSingletonInstance(  ).
                getLiusConfig( configFile );

    // Recorremos los ficheros del directorio docs
    File filesDir = new File( "docs" );
    File[] files = filesDir.listFiles();
    for( File file : files )
    {
      // Indexamos el fichero
      index(file);
    }
  }

  // Método para indexar ficheros
  private void index( File file )
  {
    log( "-------------------------------------------------" );
    log( "FILE:    " + file.getName(  ) );
    Indexer indexer = IndexerFactory.getIndexer( file, config );
    debug( indexer );
    indexer = IndexerFactory.getIndexer( file, config );
    indexer.index( indexDir );
  }

  // Metodo para volcar información de depuración por pantalla
  private void log( String msg )
  {
    System.out.println( msg );
  }

  // Metodo para volcar la información obtenida de un indexador
  private void debug( Indexer indexer )
  {
    log( "INDEXER: " + indexer.getClass(  ).getName(  ) );
    log( "FIELDS:  " );
    Collection c = indexer.getPopulatedLiusFields(  );
    for( Object o : c )
    {
      if( o instanceof LiusField )
      {
        // Campo Lucene
        LiusField o2 = (LiusField) o;
        log( "  FIELD: " + o2.getGet(  ) + "." + 
               o2.getGetMethod(  ) + " <" + o2.getName(  ) + 
               "> = " + o2.getValue(  ) );
      }
      else if( o instanceof LiusDocumentProperty )
      {
        // Valor boost para el documento Lucene
        LiusDocumentProperty o2 = (LiusDocumentProperty) o;
        log( "  BOOST: " + o2.getBoost(  ) );
      }
      else
      {
        // Otros datos
        log( "  " + o );
      }
    }
  }
}

Y la ejecutamos, con lo que nos creará un índice de
Lucene en el directorio index.
Ahora podemos ver los contenidos de ese ínidice con la
herramienta Luke, que podemos descargar de
http://www.getopt.org/luke/.
Esta aplicación visual permite manipular el índice y
ver los contenidos internos. Al arrancarla y abrir el indice creado
debemos ver lo siguiente:

Si pulsamos en la pestaña
podremos ver los documentos Lucene que ha creado Lius (debería
haber un documento Lucene por cada documento binario indexado del
directorio
docs). En
la siguiente imagen se muestra el resultado de la indexación
del documento
testPDF.pdf:

Como es fácil
adivinar, los campos Lucene creados para este PDF han sido definidos
en el fichero
liusConfig.xml y
su contenido extraido del fichero PDF en si. La parte que configura
esta extracción es:

<pdf setBoost="1.6">
  <indexer class="lius.index.pdf.PdfIndexer">
    <mime>application/pdf</mime>
  </indexer>
  <fields>
    <luceneField name="fullText" get="content" type="Text" />
    <luceneField name="title" get="title" type="Text" />
    <luceneField name="author" get="author" type="Text" />
    <luceneField name="creator" get="creator" type="Text" />
    <luceneField name="summary" get="summary" type="Text" />
    <luceneField name="keywords" get="keywords" type="Text" />
    <luceneField name="producer" get="producer" type="Text" />
    <luceneField name="subject" get="subject" type="Text" />
    <luceneField name="trapped" get="trapped" type="Text" />
    <luceneField name="creationDate" get="creationDate" type="DateToString" />
    <luceneField name="modificationDate" get="modificationDate" type="DateToString" />
  </fields>
</pdf>

Donde
podemos ver como se mapean los campos que extrae la clase
lius.index.pdf.PdfIndexer
de
los ficheros PDF (atributo
get)
a los campos de Lucene (atributo
name)
y, ademas, como se deben almacenar (atributo
type).

Referencía
de extractores y campos que proveen

En
este capítulo se listan los extractores disponibles y los
campos que proveen para ser mapeados a campos de Lucene.

Extractor

Campos

Observaciones

lius.index.application.TexIndexer

documentclass

title

author

content

abstract

lius.index.application.VCardIndexer

name

title

nickname

birthday

notes

email

phone

homephone

workphone

cellphone

categories

address

homeaddress

workaddress

url

organization

Hay que cambiar la clase y el
mimetype del fichero
liusConfig.xml de ejemplo, porque vienen mal.

Hay que modificar las siguientes
partes en rojo:

<indexer class="lius.index.application.VCardIndexer">
  <mime>text/x-vcard</mime>
  <mime>text/directory</mime>
  ...
</indexer>

lius.index.audio.MP3Indexer

channels

channelsmode

version

samplingrate

layer

emphasis

nominalbitrate

duration

location

size

copyright

crc

original

vbr

track

year

genre

title

artist

album

comments

lius.index.excel.ExcelIndexer

content

lius.index.html.JTidyHtmlIndexer

content

[nodo por nombre de etiqueta]

Ejemplos de nombre de etiqueta:
“body”, “p”, “span”.

Estos nodos tienen que ser de tipo HTML que contengan texto
(i.e.: tienen que tener como primer hijo un nodo de tipo texto de
acuerdo al estandar DOM).

lius.index.html.NekoHtmlIndexer

[nodos denominados por una expresion XPath]

Ejemplo: “//*” recupera todo el documento

lius.index.javaobject.BeanIndexer

Indexador propio que mapea getters a campos de Lucene.

Ejemplo de uso:

  BeanIndexer idx = new BeanIndexer();
  idx.setUp(configurationObject);
  idx.setObjectToIndex(unObjetoJavaBean);
  idx.index(“directorioDelIndiceLucene”);

lius.index.mixedindexing.MixedIndexer

Este indexador analiza todos los ficheros contenidos en un
directorio y los devuelve como si fuese un solo documento Lucene.

Ejemplo de uso:

  MixedIndexer idx = new MixedIndexer();
  idx.setUp(configurationObject);
  idx.setMixedContentsObj(new File("directorioAIndexar"));
  idx.index(“directorioDelIndiceLucene”);

lius.index.msword.WordIndexer

content

lius.index.openoffice.OOIndexer2
y lius.index.openoffice.OOIndexer

[nodos denominados por una expresion XPath]

Hay que saberse la especificacion
OASIS OpenDocument para saber que formato tiene el XML.

En el fichero de configuración liusConfig.xml se
pueden ver algunos ejemplos de nodos como el titulo, el autor, el
resumen, etc.

lius.index.pdf.PdfIndexer

content

title

author

creator

summary

keywords

producer

subject

trapped

creationDate

modificationDate

lius.index.powerpoint.PPTIndexer

content

lius.index.rtf.RTFIndexer

content

lius.index.txt.TXTIndexer

content

lius.index.xml.XMLFileIndexer

[nodos denominados por una expresion XPath]

A la hora de configurar las reglas de mapeo XML-Lucene en el
fichero liusConfig.xml se
pueden discriminar distintos tipos de campo XML en funcion del
namespace.

lius.index.zip.ZIPIndexer

Este indexador analiza todos los ficheros contenidos en un
archivo ZIP y los devuelve como si fuese un solo documento Lucene.

Cómo buscar en el índice

Finalmente, veremos como está configurada la búsqueda
en el fichero liusConfig.xml por defecto y como se usan las
clases de búsqueda de Lius. Ni que decir tiene que no es
obligatorio usar estas clases, pero algo de ayuda si que aportan,
haciendo más fácil buscar y presentar los resultados.
Además, se pueden configurar los resultados de las busquedas
en el fichero de configuración sin necesidad de tocar el
código.

Primeramente veremos como está configurada la búsqueda
en el liusConfig.xml de
ejemplo:

  <search>
    <multiFieldQueryParser>
      <searchFields sep=",">title,subject,creator,description,publisher,contributor,fullText</searchFields>
    </multiFieldQueryParser>
  </search>
  <searchResult>
    <fieldsToDisplay setHighlighter="true">                             
      <luceneField name="title" label="title"/>
      <luceneField name="subject" label="subject"/>
      <luceneField name="creator" label="creator"/>     
      <luceneField name="fullText" label="full text" setFragmenter="15"/>       
    </fieldsToDisplay>
  </searchResult>

Se distinguen dos apartados:

  • search: define que
    parser hay que usar para procesar la sentencia de búsqueda de
    Lucene (
    multiFieldQueryParser)
    y sobre que campos buscar (
    searchFields).

  • searchResult: define
    que campos se devuelven en la búsqueda (
    fieldsToDisplay)
    y si se usan “highlighters” o “fragmenters”. El
    “highlighter” rodea los términos buscados por
    <span
    class=»liusHit»></span>
    de
    forma que se pueda mostrar directamente en una página HTML,
    redefiniendo el estilo
    liusHist para
    que muestre el texto resaltado en el color que queramos. El
    “fragmenter” permite no mostrar el texto entero del campo, sino
    sólo unas pocas palabras alrededor de los términos
    buscados (para campos muy largos). Los trozos omitidos por el
    “fragmenter” se sustituyen por puntos suspensivos.

Para realizar una búsqueda
usamos el siguiente código (se puede sustituir el metodo
run()
de la clase de arriba por éste):

  private void run( ) throws Exception
  {
    LiusLogger.setLoggerConfigFile( log4jFile );
    config = LiusConfigBuilder.getSingletonInstance(  ).
                getLiusConfig( configFile );

    // Buscamos la cadena "institutionnel" en los campos configurados en liusConfig.xml
    LiusHitList ls = SearchIndex.search( "institutionnel", 
                                           indexDir, configFile );

    System.out.println( "Numero de documentos hallados = " + ls.size(  ) );
    for( int i = 0; i < ls.size(  ); i++ )
    {
      LiusHit lh = (LiusHit) ls.get(i);
      System.out.println( "--- Documento "+i+" ---" );
      System.out.println( "Puntuacion    = " + lh.getScore(  ) );
      System.out.println( "Identificador = " + lh.getDocId(  ) );
      List liusHitFields = lh.getLiusFields(  );
      for( int j = 0; j < liusHitFields.size(  ); j++ )
      {
        LiusField lf = (LiusField) liusHitFields.get(j);
        String name = lf.getLabel(  );
        String[] values = lf.getValues(  );
        System.out.print( name + " : " );
        for( int k = 0; k < values.length; k++ )
        {
          System.out.println( "\t" + values[k] );
        }
      }
      System.out.println( "---------------------" );
    }
  }

Como vemos, el fichero
liusConfig.xml sólo
permite una búsqueda, sin embargo, dado que el responsable de
cargar la configuración es el usuario de Lius, se pueden crear
varios ficheros con configuraciones para distintas búsquedas y
cargar uno u otro al buscar.

Conclusiones

Como hemos dicho al principio, Lucene es un excelente motor de
búsqueda textual, pero sólo eso. Cualquiera que haya
usado Lucene sabrá que, además de Lucene, se necesita
código que transforme nuestros datos a campos de Lucene. Por
hacer una analogia con los buscadores web Lucene sería como el
motor de busqueda y Lius como los bots que navegan la red procesando
sus contenidos para introducirlos en la base de datos del motor de
búsqueda.

Así pues, desde Autentia recomendamos (y de hecho usamos en
nuestros proyectos) este framework para proyectos basados en Lucene.
Por un lado organiza bastante el código y, por otro, es
fácilmente extensible y modificable al ser de código
abierto.

Por último, remarcar que, aunque este framework está
orientado a la indexación en Lucene, no debemos perder de
vista que también puede ser util para otros proyectos en los
que se necesite extraer texto de documentos binarios.

1 COMENTARIO

  1. Excelente Informacion lamentable que sea la unica informacion que encontre en la red sobre este Framework si conocen algun sitio donde encontrar tutoriales ya que soy nuevo en java se los agradeceria mucho. De antemano Gracias! …

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad