Kitabı oku: «Desarrollo de motores de búsqueda utilizando herramientas open source»

Yazı tipi:

DESARROLLO DE MOTORES DE BÚSQUEDA

UTILIZANDO HERRAMIENTAS OPEN SOURCE

Análisis y desarrollo con Apache Lucene, Solr y ElasticSearch

José Manuel Ortega Candel


DESARROLLO DE MOTORES DE BÚSQUEDA

UTILIZANDO HERRAMIENTAS OPEN SOURCE

Análisis y desarrollo con Apache Lucene, Solr y ElasticSearch

José Manuel Ortega Candel


Desarrollo de motores de búsqueda utilizando herramientas open source

© 2021 José Manuel Ortega Candel

Primera edición, 2021

© 2021 MARCOMBO, S. L.

www.marcombo.com

Diseño de cubierta: ENEDENÚ DISEÑO GRÁFICO

Maquetación: D. Márquez

Corrección: Nuria Barroso y Anna Alberola

Directora de producción: M.a Rosa Castillo Producción del ePub: booqlab

«Cualquier forma de reproducción, distribución, comunicación pública o transformación de esta obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley. Diríjase a CEDRO (Centro Español de Derechos Reprográficos, www.cedro.org) si necesita fotocopiar o escanear algún fragmento de esta obra».

ISBN: 978-84-267-3336-8

Este libro va dedicado a aquellos que me han seguido, me siguen y me seguirán, no importa cuál sea el camino escogido, en algún lugar nos encontraremos.

ÍNDICE

INTRODUCCIÓN

CAPÍTULO 1

INTRODUCCIÓN A LOS MOTORES DE BÚSQUEDA

1.1 Definición de motores de búsqueda

1.2 ¿Por qué necesito un motor de búsqueda?

1.2.1 Extracción de texto en documentos

1.2.2 Operaciones de lectura

1.2.3 Esquema flexible

1.3 Funcionamiento de un motor de búsqueda

1.3.1 Buscadores de directorios

1.3.2 Buscadores jerárquicos

1.3.3 Spiders (arañas o crawlers)

1.4 Proceso de indexación

1.4.1 Rendimiento en la indexación de documentos

1.4.2 Stopwords

1.4.3 Steamming

1.5 Recuperación de la información

1.5.1 Extracción de información

1.5.2 Sistema de búsqueda/respuesta

1.6 Motores de búsqueda e indexadores

1.6.1 Lucene

1.6.2 Índice invertido

1.6.3 Apache Solr

1.6.4 ElasticSearch

1.6.5 Otros motores de búsqueda

1.7 Herramientas de procesamiento de lenguaje natural (PLN)

CAPÍTULO 2

APACHE LUCENE

2.1 Introducción a Apache Lucene

2.2 Módulos y funcionalidades de Apache Lucene

2.3 Arquitectura de Apache Lucene

2.3.1 Proceso de tokenización y búsqueda en Apache Lucene

2.4 Trabajar con Apache Lucene

2.4.1 Configuración del entorno

2.4.2 Crear un índice

2.4.3 Crear y escribir documentos en un índice

2.5 Realizar búsquedas en Apache Lucene

2.5.1 Obtención de un IndexSearcher

2.5.2 Proceso de búsqueda con IndexSearcher

2.5.3 Crear consultas con Lucene QueryParser

2.5.4 Sintaxis de las consultas en Apache Lucene

2.6 Búsqueda de información con Apache Lucene

2.7 Búsqueda en múltiples índices de Lucene

2.8 Herramientas de administración de Lucene

2.9 Herramientas de búsqueda que usan Apache Lucene

2.9.1 Krugle

2.9.2 Google Dataset Search

2.9.3 Otros repositorios y portales

CAPÍTULO 3

APACHE SOLR

3.1 Introducción a Apache Solr

3.2 Arquitectura de Apache Solr

3.3 Indexación e índice invertido en Apache Solr

3.3.1 Relevancia y filtro en las búsquedas

3.4 Instalación de Solr

3.5 Configurar Apache Solr

3.5.1 Ficheros de configuración y esquema

3.5.2 Proceso de indexación

3.5.3 Indexación de datos con SimplePostTool

3.5.4 Esquema administrado en Apache Solr

3.5.5 Añadir información al índice

3.6 Búsqueda de documentos

3.6.1 Usar la API REST

3.6.2 Consultas básicas de Solr

3.6.3 Componente stats (estadísticas)

3.7 Consultas avanzadas de Solr

3.7.1 Paginación y clasificación

3.7.2 Navegación facetada en Solr

3.7.3 Búsqueda de facetas en Solr

3.8 Componentes de una petición en Solr

3.8.1 Controladores Request Handler

3.9 Analizadores en Apache Solr

3.9.1 Definir campos personalizados

3.9.2 Configuración de los tipos de campos

3.9.3 Propiedad score

3.10 Monitorización del rendimiento de Apache Solr

3.10.1 Página de plugins/estadísticas de Apache Solr

3.11 Ejecución en modo Cloud

3.11.1 Terminología usada en SolrCloud

3.11.2 SolrCloud

3.11.3 Ejecutar SolrCloud

3.11.4 Indexación distribuida

3.11.5 Ventajas de SolrCloud

3.12 Arrancar Apache Solr desde Docker

3.13 Interactuar con Solr desde Java

3.13.1 Añadir Solrj a un proyecto Maven

3.13.2 Conexión con el servidor Solr desde Java

3.14 Crawling con Apache Nutch y conexión con Apache Solr

3.15 Conclusiones de Apache Solr

CAPÍTULO 4

ELASTICSEARCH

4.1 Introducción a ElasticSearch

4.1.1 Ventajas de ElasticSearch sobre Apache Solr

4.2 Uso de ElasticSearch para proyectos de Big Data

4.3 Arquitectura de ElasticSearch

4.3.1 Nodos en ElasticSearch

4.4 ElasticSearch como solución distribuida

4.5 Fragmentos y réplicas en ElasticSearch

4.6 Instalación de ElasticSearch

4.7 Crear un índice en ElasticSearch

4.8 API REST de ElasticSearch

4.8.1 Mapping o estructura de los datos del índice

4.8.2 Recuperar parte de un documento

4.8.3 Asignar un mapeo a un índice

4.8.4 Query DSL (solicitudes DSL)

4.8.5 Búsquedas en ElasticSearch

4.8.6 La consulta multi_match

4.8.7 Navegación facetada en ElasticSearch

4.8.8 Agregaciones en documentos

4.8.9 Autocompletado en ElasticSearch

4.8.10 Analizadores en ElasticSearch

4.9 Relevancia de documentos en ElasticSearch

4.9.1 Puntuación y relevancia de los documentos

4.9.2 Mejorar la relevancia de los resultados de búsqueda

4.10 Estadísticas e información sobre el clúster, índices y nodos

4.10.1 Estado del clúster

4.10.2 Métricas del clúster

4.10.3 Estadísticas de los índices

4.11 Herramientas de monitorización en ElasticSearch

4.11.1 ElasticSearch Head

4.11.2 ElasticSearch query client

4.11.3 Cerebro

CAPÍTULO 5

CLIENTES ELASTICSEARCH

5.1 Clientes de conexión en Java

5.2 Conexión al clúster de ElasticSearch

5.3 Gestionar índices

5.4 Indexación de documentos en el índice

5.5 Búsqueda de documentos en el índice

5.6 Integración con Spring Data

5.7 Seguridad en ElasticSearch

5.8 Conclusiones de ElasticSearch

CAPÍTULO 6

VISUALIZACIÓN DE DATOS CON KIBANA

6.1 Introducción a Kibana

6.2 Stack ELK

6.2.1 Principales usos de ELK

6.2.2 Stack ELK en contenedores Docker

6.3 Instalación de Kibana

6.4 LogStash

6.4.1 Instalación de LogStash

6.4.2 Configuración de LogStash

6.4.3 Otros servicios de Elastic

6.5 Monitorización de LogStash desde Kibana

6.6 Seguridad en Kibana

CAPÍTULO 7

MOTORES DE BÚSQUEDA CON PYTHON

7.1 Conectando con Solr desde Python

7.2 Integración de ElasticSearch con Python

7.2.1 Crear un índice e insertar documentos usando Python

7.2.2 Realizar búsquedas usando Python

7.3 Consultar ElasticSearch a través de la API REST en Python

7.4 Optimizar un índice en ElasticSearch

7.5 Indexar documentación de Django

7.6 Whoosh como motor de búsqueda en Python

7.6.1 Whoosh schema

7.6.2 Realizar búsquedas en Whoosh y scoring

7.7 Django-haystack

7.7.1 Integrar haystack en una aplicación Django

CAPÍTULO 8

GLOSARIO DE TÉRMINOS

INTRODUCCIÓN

Este libro está especialmente pensado para perfiles con conocimientos previos en programación orientada a objetos, principalmente con los lenguajes de programación Java y Python. Va dirigido a aquellos perfiles con habilidades de programación que necesiten trabajar con motores de búsqueda open source y a quienes precisen llevar a cabo tareas orientadas a la extracción, procesamiento y manipulación de grandes volúmenes de datos no estructurados.

Analizaremos los principales motores de búsqueda de código abierto, como Apache Lucene, Solr y ElasticSearch. Este último es uno de los motores de búsqueda open source para Big Data con mayor crecimiento en la industria. Se caracteriza por sus grandes capacidades de flexibilidad, velocidad, escalado y herramientas de soporte asociadas, y recorre todo el ciclo de trabajo desde la construcción de los procesos de extracción e incorporación de datos hasta el trabajo de análisis y monitorización mediante herramientas de búsqueda y visualización en tiempo real con herramientas como Kibana.

A lo largo del libro, el lector encontrará ejemplos de código fuente en los lenguajes de programación Java y Python. Los scripts y proyectos implementados se pueden encontrar en el repositorio de GitHub:

https://github.com/Marcombo/motores_busqueda_open_source

A partir del capítulo 2, se recomienda al lector seguir los ejemplos prácticos del libro junto con el código fuente que se puede encontrar en el repositorio de GitHub, con el objetivo de practicar los conceptos que se analizan de forma teórica. En dicho repositorio también hallará instrucciones de instalación del entorno y de las principales herramientas a nivel de desarrollo que se utilizan a lo largo del libro.

Capítulo 1
INTRODUCCIÓN A LOS MOTORES DE BÚSQUEDA

En la actualidad, el empleo de motores de búsqueda se ha extendido de forma generalizada. Esto se debe principalmente a la proliferación de sitios web, a la evolución de los sistemas y a las necesidades de compartir documentos y realizar búsquedas en un mundo globalizado y digitalmente conectado. Cabe destacar que los motores de búsqueda se encuentran muy de moda en la actualidad, no solamente por su gran éxito en la red y por su capacidad para indexar documentos, sino también por su utilización en el mundo de las redes sociales y en las modernas técnicas y herramientas de Big Data.

Los motores de búsqueda solucionan la mayoría de los problemas que se presentan a la hora de realizar una búsqueda con una cantidad elevada de datos, y permiten obtener resultados relevantes para el usuario mediante algoritmos de ordenación.

En la actualidad, cuando se habla de motor de búsqueda, se tiende a pensar en la consulta web. En general, el impacto que ha supuesto la creación de Internet, junto con la necesidad de los usuarios de compartir información, ha llevado a la inevitable búsqueda de herramientas que faciliten el proceso de clasificación de documentos conforme a las necesidades del usuario. Además, la creciente cantidad de datos que encontramos en Internet necesita ser estructurada y clasificada de algún modo que permita su interpretación. En esencia, esta es la misión de un motor de búsqueda: permitir a los usuarios acceder, a través de un conjunto de palabras clave, a la información que desean, intentando presentar en primer término los datos más relevantes para el usuario.

Sin embargo, los motores de búsqueda no se emplean únicamente en Internet. Aunque ese fuera el origen de su nacimiento, en la actualidad se han convertido en poderosas herramientas para aportar soluciones de negocio (por ejemplo, para búsquedas internas en la documentación de las empresas, o para situaciones que requieran localizar información dentro de un gran volumen de datos).

De esta manera, los motores de búsqueda se han convertido en algo intrínseco en nuestro día a día. Es por ello que realizar un estudio analizando las semejanzas y diferencias entre diferentes motores de búsqueda puede resultar muy interesante.

Entre los principales objetivos de este capítulo podemos destacar:

• Comprender la estructura y naturaleza de un motor de búsqueda y la importancia de los sistemas de búsqueda y recuperación de la información para su implementación.

• Conocer las necesidades típicas que se resuelven mediante motores de búsqueda.

• Conocer los principales motores de búsqueda que podemos encontrar en el mercado.

1.1 DEFINICIÓN DE MOTORES DE BÚSQUEDA

En la actualidad, para agilizar el tiempo de respuesta en el proceso de búsqueda, se hace uso de motores de búsqueda que suelen utilizar palabras clave y metadatos que provienen de las mismas páginas para, de esta forma, obtener un vocabulario y mejorar la búsqueda en el sitio.

Los motores de búsqueda han recorrido un largo camino desde sus primeros prototipos, y su desarrollo ha llevado a la investigación de múltiples tecnologías de búsqueda, que se desarrollaron a partir de diferentes motores. Alta Vista fue el primer motor de búsqueda para procesar las consultas en lenguaje natural; y Lycos fue uno de los primeros motores de búsqueda en implementar un sistema de categorización a partir de la relevancia de las palabras, teniendo en cuenta los prefijos en palabras clave y la proximidad de las palabras encontradas.

Un motor de búsqueda es un software o una secuencia de comandos que busca en documentos o en bases de datos las palabras clave y devuelve los resultados de todos los archivos que contengan esas palabras. Hoy en día, existen muchos motores de búsqueda diferentes disponibles en Internet, cada uno con sus propias habilidades y características. El primer motor de búsqueda desarrollado fue Archie (https://es.wikipedia.org/wiki/Archie_(Buscador_FTP)), que fue utilizado para buscar archivos de forma similar al protocolo FTP. Hoy en día, el motor de búsqueda más popular es Google.

Al final, un motor de búsqueda como Google es un buscador jerárquico que va recorriendo páginas y recopilando información sobre su contenido. En el momento de realizar una consulta mediante frases o palabras clave, el motor de búsqueda reúne información de su base de datos y la muestra al usuario según la relevancia que cada motor pueda definir. Como resultado final, devuelve al usuario los resultados en un listado de páginas que contienen las palabras de la consulta realizada.

1.2 ¿POR QUÉ NECESITO UN MOTOR DE BÚSQUEDA?

En primer lugar, sería interesante analizar las preguntas que deberíamos responder sobre sus datos y los casos de uso para decidir si un motor de búsqueda sería lo más adecuado para el problema que trata de resolver. Al final, se trata de entender los datos y los usuarios y elegir una tecnología que funcione para ambos.

Una característica distintiva de las arquitecturas de aplicaciones modernas es hacer coincidir el motor de almacenamiento y procesamiento con los datos. Si es un programador, debe seleccionar la mejor estructura de datos en función de cómo se utilizarán los datos en un algoritmo. El mismo principio se aplica con los motores de búsqueda.

Los motores de búsqueda están optimizados para manejar datos que tienen las siguientes características:

• Extracción de texto en documentos

• Operaciones de lectura

• Orientación a documentos

• Esquema de documentos flexible a los cambios

Una posible quinta característica es la de tener un gran volumen de datos para tratar, es decir, Big Data. Pero sería interesante estudiar lo que hace que un motor de búsqueda sea especial para manejar grandes volúmenes de datos. Por ahora, nos centraremos en los conceptos de alto nivel.

1.2.1 Extracción de texto en documentos

Esta característica hace referencia a que un motor de búsqueda está específicamente diseñado para extraer la estructura implícita del texto a partir de la información que almacena en los índices. Los datos centrados en el texto implican que el texto de un documento contiene información que los usuarios están interesados en encontrar. Por supuesto, un motor de búsqueda también admite datos sin texto, como fechas y números, pero su principal ventaja reside en su capacidad de manejar textos basados en lenguaje natural.

1.2.2 Operaciones de lectura

Otro aspecto clave es que los motores de búsqueda manejan de manera efectiva la información en modo lectura y, por lo tanto, están optimizados para acceder con eficiencia a la información de los índices. Esto significa que las operaciones de lectura son más óptimas si las comparamos con operaciones de creación o actualización. Esto no quiere decir que no se puedan escribir grandes cantidades de datos o que existan límites sobre la frecuencia con la que se pueden escribir otros nuevos. De hecho, una de las características clave de los motores de búsqueda es la capacidad que tienen, casi en tiempo real, de indexar miles de documentos por segundo y hacer que se puedan buscar casi de inmediato.

El punto más importante es que cuando escribe datos en los índices de un motor de búsqueda, estos datos están destinados a ser leídos en algún momento y releídos muchas veces a lo largo de su vida útil. Piense en un motor de búsqueda como en una herramienta optimizada para ejecutar consultas, en lugar de almacenar datos. Además, si las operaciones de actualización van a ser más frecuentes que las operaciones de lectura, esto podría ser un indicador de que un motor de búsqueda podría no ser la mejor solución para sus necesidades. Existen otras tecnologías NoSQL, como MongoDB o Cassandra, que podrían ser mejores opciones si necesitara escrituras aleatorias rápidas de los datos existentes.

1.2.3 Esquema flexible

La última característica destacada de los motores de búsqueda es que tienen un esquema flexible. Esto significa que los documentos, en un índice de búsqueda, no necesitan una estructura uniforme. En una base de datos relacional, cada fila de una tabla tiene la misma estructura.

La mayoría de los motores de búsqueda se basan en que los documentos pueden tener diferentes campos y no hay una estructura fija. Piense, por ejemplo, en bases de datos NoSQL orientadas a documentos como Mongo-DB o CouchDB.

El beneficio de disponer de una variedad de opciones para almacenar y procesar datos es que no tiene que encontrar una tecnología única para todos. En la mayoría de los casos, podrá complementar el uso de motores de búsqueda tanto con bases de datos relacionales como con bases de datos NoSQL.

1.3 FUNCIONAMIENTO DE UN MOTOR DE BÚSQUEDA

Un motor de búsqueda es un programa que permite localizar en un conjunto de documentos aquellos relacionados con una palabra o palabras clave seleccionadas. Para ofrecer esta funcionalidad, se exploran los ficheros existentes en el sistema de forma eficiente, registrando información relevante sobre ellos en una base de datos. Esta base de datos es empleada por el motor para permitir a los usuarios realizar consultas y localizar esos documentos de forma más sencilla.

Así pues, para conseguir esta funcionalidad, existen varios componentes principales que permiten al motor de búsqueda realizar estas operaciones:

• Crawler o Spider: así se denomina el proceso que recorre el conjunto de documentos almacenados, ya sea en una máquina o en una red, que recoge los metadatos y la ubicación de los mismos para, posteriormente, guardar un registro de todos ellos y permitir su rápida localización y presentación al usuario. En colecciones de documentos reducidas, esta operación no le supone una gran cargabilidad al proceso. Sin embargo, en entornos como Internet, se precisa de un proceso que esté de forma continuada verificando los enlaces que forman una web, dado que la situación de los elementos es más dinámica y varía con el tiempo.

• Índice: el índice se puede describir como el conjunto de información recopilada por el crawler y que se almacena en un registro; se denomina índice de búsqueda. Estos datos son los que emplea posteriormente el motor para escoger los elementos que cumplen con la petición del usuario a través de las palabras clave que conforman la consulta.

• Algoritmo de ordenación: si el índice es el corazón del motor de búsqueda, el algoritmo de ordenación es el cerebro, ya que es el responsable de recoger los datos del índice de la forma más adecuada posible, devolviendo al usuario los resultados por orden de relevancia. Es interesante que los datos más relevantes sean siempre los primeros en aparecer, según diferentes criterios de ordenación. Se permite que estos criterios, en ocasiones, sean personalizables bajo ciertos requisitos, lo cual dota al algoritmo de cierta flexibilidad y adaptación. Esto permite al motor adaptarse a las necesidades reales del entorno empresarial o del usuario.

De los elementos comentados, el índice se puede considerar como el corazón del motor de búsqueda. El índice y el algoritmo de ordenación empleado son los elementos diferenciadores que actualmente permiten la existencia de una gran diversidad de motores de búsqueda.

1.3.1 Buscadores de directorios

Los buscadores de directorio buscan información sobre contenidos de la página, y los resultados se presentan haciendo referencia a los contenidos y temática del sitio. Los algoritmos para este tipo de motor de búsqueda son más sencillos, y los sitios se presentan como enlaces, los cuales representan los sitios registrados. Estos tipos de buscadores no recorren los sitios web ni almacenan sus contenidos; lo que hacen es registrar algunos de los datos de la página web, como el título y la descripción (que normalmente se introducen en el momento de registrar el sitio en el directorio).

1.3.2 Buscadores jerárquicos

Este tipo de motor de búsqueda recorre las páginas coleccionando información sobre sus contenidos. Cuando se inicia una búsqueda de información concreta en los buscadores, ellos consultan su base de datos y presentan resultados clasificados por su distinción para esa búsqueda concreta. Los buscadores pueden almacenar en sus bases de datos desde la página de entrada de cada web hasta todas las páginas que residan en el servidor, una vez que el buscador la haya reconocido e indexado.

Debido a que los motores de búsqueda contienen millones —y a veces miles de millones— de páginas, muchos motores de búsqueda no solo se centran en el proceso de búsqueda, sino que también muestran los resultados en función de su importancia. Esta importancia de los resultados se determina normalmente mediante el uso de diversos algoritmos.

1.3.3 Spiders (arañas o crawlers)

Spider o araña es el nombre que se le ha dado al componente que se encarga de rastrear la web siguiendo su estructura hipertextual, para almacenar los enlaces en un lugar para su posterior análisis. En muchas ocasiones es llamado también crawler o webcrawler. Cada cierto tiempo, los spiders recorren la web para actualizar los contenidos recopilados con anterioridad; por ejemplo, los sitios de noticias y los blogs que están en constante actualización son analizados frecuentemente por estos programas. Además, los spiders pueden trabajar de forma conjunta, funcionando como una red de spiders, para obtener más documentos y almacenarlos en un repositorio o base de datos.

Figura 1.1 Funcionalidad de una araña o web crawler.

Como se puede observar en la figura 1.1, la fuente de todos los datos del motor de búsqueda es un crawler, que visita automáticamente las páginas y los índices de sus contenidos. Una vez que una página ha sido rastreada, los datos que contiene se procesan.

Los motores de búsqueda pueden realizar distintos tipos de búsquedas, ya sea por fecha, por un campo específico o por temas; en los siguientes puntos se explican algunos tipos :

• Restringido campo de búsqueda. Permite a los usuarios realizar su búsqueda sobre un determinado campo dentro de un registro almacenado de datos, por ejemplo “Título” o “Autor”.

• Consultas booleanas. Se hace uso de operadores booleanos para aumentar la precisión de una búsqueda.

• Búsqueda de concordancia. Produce una lista alfabética de todas las palabras principales que se encuentran en un texto con su contexto inmediato.

• Búsqueda de proximidad. Incluye solo los documentos que contienen dos o más palabras separadas por un número determinado de palabras.

• Expresión regular. Emplea una sintaxis para realizar consultas más complejas.

• Búsqueda facetada. Consiste en encontrar elementos o contenidos restringiendo el conjunto global de resultados a través de múltiples criterios o facetas, lo cual permitirá realizar la búsqueda mediante cualquier metadato del grafo semántico de una determinada entidad.

1.4 PROCESO DE INDEXACIÓN

El proceso de localizar y recuperar cada contenido de un archivo se conoce como indexación. Una palabra clave, asociada a un identificador de un archivo específico, se incluirá en un índice para, posteriormente, conocer la posición exacta de cada archivo y posibilitar el análisis de frecuencias de cada palabra. La mayoría de las herramientas de acceso a la web están basadas en indexación automática, que no es más que la indexación que se realiza a través de procedimientos algorítmicos.

Además, el proceso de indexación puede mejorar el rendimiento de las consultas, ya que los datos necesarios para satisfacer las necesidades de la consulta existen en el propio índice y se reduce al máximo el tamaño de los archivos; por lo tanto, se reducen también las operaciones de lectura y escritura sobre el disco.

En el proceso de indexación, para agilizar la búsqueda de grandes cantidades de datos es necesario hacer uso de índices, ya que estos mejoran la velocidad de las operaciones, de modo que el acceso a la información es más rápido.

Cuando el número de archivos a buscar es potencialmente de gran tamaño, o la cantidad de consultas de búsqueda por realizar es considerable, el problema de búsqueda a menudo se divide en dos tareas: la indexación y la búsqueda. La etapa de indexación analizará el contenido de todos los archivos y creará una lista de los términos de búsqueda, a menudo llamada índice. En la etapa de búsqueda, al realizar una consulta específica, esta se realiza utilizando el índice en lugar de utilizar el contenido de los documentos originales.

1.4.1 Rendimiento en la indexación de documentos

A la hora de almacenar documentos hay que tener en cuenta que uno de los factores más importantes es el rendimiento. Los factores que afectan al rendimiento son: el número de campos a indexar o almacenar, el número de registros que se introducen, el tamaño de los documentos a indexar y el tipo de documento que se indexa (pdf, txt, xml, etc.).

En este punto, el proceso de indexación trata de reducir al máximo el tamaño de los archivos o tablas de la base de datos, para conseguir la mejor relación entre tiempo de ejecución de las consultas y exhaustividad del fichero inverso. Para ello, vamos a introducir los siguientes conceptos:

• stopwords: se trata de una lista de palabras de uso frecuente que no se tienen en consideración ni en el proceso de indexación ni en el de búsqueda.

• stemming: es un método para obtener la raíz semántica de una palabra. Las palabras se reducen a su raíz o stem (tema).

1.4.2 Stopwords

Las stopwords son palabras que no contienen un significado importante, por lo que no serán utilizadas en las consultas de búsqueda.

Se trata de una técnica de indexación que genera una lista de palabras de uso frecuente que no se tendrán en consideración y que se omitirán tanto en el momento del proceso de indexación como en el proceso de búsqueda. El hecho de que haya palabras que no aparezcan en el índice en una base de datos se debe a que son insignificantes (como los artículos y las preposiciones). Estas palabras son excluidas de las búsquedas para agilizar el proceso de indexar y analizar las páginas web. Algunos ejemplos de estas palabras son: “un”, “y”, “pero”, “cómo”, “o” y “qué”. Los buscadores, en Internet, no pueden impedir el uso de estas palabras; por lo tanto, las ignoran.

1.4.3 Stemming

Stemming es un método que permite reducir una palabra a su raíz o stem. Hay algunos algoritmos de stemming que ayudan en sistemas de recuperación de información. El stemming aumenta el recall, que es una medida sobre el número de documentos que se pueden encontrar con una consulta. Por ejemplo, una consulta sobre “bibliotecas” también encontrará documentos en los que solo aparezca “bibliotecario”, porque el stem de las dos palabras es el mismo (“biblioteca”).

Esta técnica se suele utilizar cuando queremos que un término pueda reducirse a su común denominador, y permitirá la recuperación de todos los documentos cuyas palabras tengan la misma raíz común (por ejemplo: catálogo, catálogos, catalogación, catalogador, catalogar, catalogando, catalogado).

1.5 RECUPERACIÓN DE LA INFORMACIÓN

La recuperación de información, o information retrieval, es una disciplina que se encarga de estudiar las técnicas para buscar información dentro de documentos que no se encuentran organizados o cuando, debido a la gran cantidad de documentos, resulta difícil buscar de forma manual.

Los sistemas para la recuperación de información están formados por diferentes mecanismos, que son los que permiten realizar las búsquedas, y un spider o crawler, que es el que se encarga de recorrer la web siguiendo los enlaces que va encontrando en las páginas o documentos. Este recorrido puede realizarse tanto en profundidad como a lo ancho y, generalmente, este tipo de programas suelen estar alojados en ordenadores con gran capacidad de memoria y CPU.

Los documentos encontrados en la web son analizados por el crawler, que les da un formato común. Después, estos documentos se almacenan en alguna estructura de datos, que puede ser un sistema relacional en forma de índices para su rápido acceso. En este proceso se realiza un análisis de cada una de las páginas o documentos encontrados y, por cada palabra encontrada, se guarda la referencia del documento donde se encuentra.

Cuando el usuario escribe el criterio a buscar, los resultados se muestran de forma ordenada según una relevancia que se calcula teniendo en cuenta el modelo de recuperación utilizado.

Este último componente también se comunica con el mecanismo de formulación de consulta, que es el que utiliza los términos introducidos por el usuario y los convierte en los términos que están almacenados en el índice. De esta forma, realiza una recuperación de todos los documentos que presentan estos términos, ordenándolos por la relevancia asociada a cada documento.

A partir de una colección de documentos, se seleccionan aquellos relacionados con una pregunta de un usuario a través de un conjunto de palabras claves. La recuperación de información tiene las siguientes características:

• La información es una pieza fundamental en todos los procesos de nuestra sociedad.

• El desarrollo de las tecnologías de la información (TI) ha permitido crear sistemas y servicios de información cada vez más ágiles.

• El desarrollo de las comunicaciones ha permitido el acceso a información desde cualquier sitio de forma remota.

• Hoy en día disponemos de gran cantidad de repositorios y documentación en Internet.

A continuación, se muestran los principales pasos para la recuperación de la información (figura 1.2):

1. Obtener representación de los documentos. Generalmente, los documentos se presentan utilizando un conjunto más o menos grande de términos índice. La elección de dichos términos es el proceso más complicado.

2. Identificar la necesidad informativa del usuario. Se trata de obtener la representación de esa necesidad y plasmarla formalmente en una consulta acorde con el sistema de recuperación.

3. Búsqueda de documentos que satisfagan la consulta. Consiste en comparar las representaciones de documentos y la representación de la necesidad informativa para seleccionar los documentos pertinentes.

4. Presentación de los resultados al usuario. Puede ser desde una breve identificación del documento hasta el texto completo.

5. Evaluación de los resultados. Para determinar si son acordes con la necesidad informativa.

Figura 1.2 Pasos para un proceso de recuperación de información.

Los sistemas de recuperación basados en términos índice se apoyan en la idea fundamental de que tanto el contenido de los documentos como la necesidad informativa del usuario pueden representarse con términos índice, lo cual permite agrupar diferentes documentos para representar un concepto. Los documentos se pueden clasificar, a grandes rasgos, en dos categorías:

• Documentos primarios: informes, artículos, páginas web, etc.

• Documentos secundarios: título, autor, resumen, etc.

La necesidad informativa se expresa formalmente mediante una consulta:

• Puede emplear diferentes términos y operadores booleanos.

• Puede realizarse en lenguaje natural.

La recuperación de información se utiliza en muchas de las aplicaciones que encontramos hoy en día, por ejemplo:

• Bibliotecas digitales

• Buscadores de Internet

• Herramientas de búsqueda personal (correos electrónicos, documentos personales, etc.

1.5.1 Extracción de información

La extracción de información es un área de investigación que comprende la intersección entre lingüística computacional, machine learning, data mining, bases de datos y recuperación de información. Se trata de un proceso que consiste en obtener información relevante a partir de un conjunto de documentos. Por tanto, el problema estriba en extraer la información y convertirla en información estructurada para poder buscar, manejar y explotar la información que se desee de forma eficiente.

El objetivo final es elaborar sistemas que permitan encontrar y relacionar información relevante mientras ignoran otras informaciones no relevantes. La relevancia se determina a partir de una serie de guías que permiten especificar con la mayor exactitud posible el tipo de información a extraer.

Desde la perspectiva del procesamiento de lenguaje natural, los sistemas de extracción de información deben trabajar a distintos niveles: desde el reconocimiento de palabras hasta el análisis de frases, y desde el entendimiento a nivel de frase hasta el texto completo. A grandes rasgos, entre las principales tareas relacionadas con la extracción de la información podemos destacar:

• Extracción de entidades y relaciones:

− Entidades: con nombre y genéricas.

− Relaciones: entidades relacionadas de una forma predefinida.

− Eventos: pueden estar compuestos de múltiples relaciones.

• Subtareas comunes en la extracción:

− Preproceso: segmentación de frases, análisis morfológico y sintáctico.

− Creación de reglas y/o patrones de extracción: de forma manual, automática o mixta.

− Aplicación de reglas o patrones de extracción: para extraer nueva información.

− Post-proceso: integración de la información, resolución y desambiguación de términos.

1.5.2 Sistema de búsqueda/respuesta

Un sistema basado en búsqueda/respuesta parte de una consulta expresada en lenguaje natural y debe devolver no un documento que sea relevante (es decir, un documento que contenga la respuesta) sino la propia respuesta (normalmente, un hecho). Proporciona el fragmento de texto en el que se encuentra la respuesta a una pregunta del usuario.

A diferencia de los sistemas de recuperación de la información convencionales que utilizan técnicas estadísticas, los sistemas de búsqueda/respuesta emplean técnicas de PLN (procesamiento de lenguaje natural). Uno de los sistemas de búsqueda/respuesta más conocidos es el correspondiente al START (natural language question answering system) (figura 1.3).

http://www.ai.mit.edu/projects/infolab/globe.html

Figura 1.3 Sistema START de búsqueda/respuesta.

1.6 MOTORES DE BÚSQUEDA E INDEXADORES

Los sistemas de recuperación de información con índice invertido son sistemas caracterizados por tener una estructura de datos capaz de manejar grandes volúmenes de información orientados a texto. Esta funcionalidad está presente en los motores de búsqueda que se van a analizar en profundidad:

• Apache Lucene (https://lucene.apache.org) es una potente librería de recuperación de información que se basa en la elaboración de un índice invertido como principal estructura especializada en emparejar documentos de texto con términos de consulta. Lucene, al igual que otros motores de búsqueda, se distingue por la escalabilidad, el rápido despliegue, el manejo de grandes volúmenes de datos y la optimización orientada a búsqueda de textos en documentos.

• Apache Solr (https://lucene.apache.org/solr) es una librería open source construida sobre la librería de Lucene, que incluye una interfaz web de administración, API en distintos lenguajes para la realización de consultas y una serie de mejoras funcionales sobre las características base que trae Lucene de serie.

• ElasticSearch (https://www.elastic.co/es/), construido sobre Apache Lucene, ha basado su modelo en una API REST y ha ganado popularidad en los últimos años. Se trata de una plataforma distribuida en tiempo real de búsqueda y análisis:

− ElasticSearch distribuye y organiza la información en clúster de nodos, por lo que lo podemos ejecutar en varios servidores si nuestra aplicación necesita escalar.

− ElasticSearch es un motor de búsqueda que funciona en tiempo real, ya que los datos están indexados. De esta forma, obtenemos respuestas a nuestras consultas de forma mucho más rápida si lo comparamos con otras soluciones.

En la actualidad, es común que las aplicaciones estén respaldadas por una base de datos sobre la que se implementan las búsquedas. Sin embargo, con este enfoque, la implementación de funcionalidades de búsqueda con cierta complejidad puede llegar a ser complicada debido a las limitaciones de las bases de datos. Por ejemplo, las búsquedas pueden no llegar a ser suficientemente eficientes si el volumen de información es muy grande o si la forma en que está estructurada la información no es la adecuada. En escenarios como estos, una plataforma especialmente dedicada a la optimización de las búsquedas puede resultar una alternativa más práctica y eficiente.

Estas plataformas utilizan un modelo de datos orientado a documentos que pueden pensarse como una base de datos de una sola tabla. Un documento es simplemente un conjunto de campos, como una tupla en una tabla de una base de datos, con la diferencia de que cada columna puede ser multivaluada.

Además, permiten la implementación de funcionalidades de búsqueda sobre documentos que facilitan al usuario la localización de recursos de una manera más rápida y organizada. Tomando como referencia la web DB-Engines, podemos ver que la solución más popular hoy en día es ElasticSearch. En el siguiente URL podemos ver los motores de búsqueda más populares:

https://db-engines.com/en/ranking/search+engine

Figura 1.4 Listado de motores de búsqueda.

De los motores mostrados en la figura 1.4, analizaremos en profundidad tanto ElasticSearch como Apache Solr (ambos basados en Lucene, que nos permite realizar búsquedas por una gran cantidad de datos de un texto específico). Gracias al motor Lucene sobre el que están implementados, estos motores nos ofrecen capacidades de búsquedas de texto, autocompletado y soporte de geolocalización. Podríamos definir a estos motores de búsqueda como bases de datos NoSQL orientadas a documentos JSON, que pueden ser consultados, creados, actualizados o borrados mediante una API REST.

1.6.1 Lucene

Lucene es el corazón de la mayoría de los motores de búsqueda de hoy en día. Básicamente acepta el texto que se va a buscar, lo almacena en una estructura de datos (índice invertido) y, luego, acepta varios tipos de consultas de búsqueda y devuelve un conjunto de resultados que coinciden con los criterios de búsqueda.

Muchas soluciones de búsqueda basadas en servidor, como Apache Solr o ElasticSearch, están construidas sobre Lucene y marcan una evolución en el desarrollo de motores de búsqueda. El objetivo del diseño de estas herramientas es ofrecer un tipo de soluciones que proporcionen escalabilidad y alta disponibilidad a la base que trae Lucene como motor de búsqueda.

En los últimos años han surgido diferentes proyectos de la fundación Apache que también se consideran parte del universo de Lucene. Aunque estos proyectos son independientes, y más conocidos en otros contextos, se pueden integrar de forma sencilla con Lucene. Entre estos proyectos podemos destacar Hadoop, Mahout (un framework para aprendizaje automático), Nutch (una aplicación de búsqueda basada en Lucene) y Tika (que permite analizar documentos en diferentes formatos, como PDF, y se puede utilizar para alimentar con datos un índice de Lucene).

• Hadoop: http://hadoop.apache.org

• Mahout: http://mahout.apache.org

• Nutch: http://nutch.apache.org

• Tika: http://tika.apache.org

Apache Lucene guarda toda la información en una estructura llamada índice invertido. Se trata de una estructura de datos donde para cada término que encontramos en los diferentes documentos, obtiene los índices donde aparecen. Puede pensar en el índice invertido como en una estructura de datos donde estos están orientados a términos en lugar de a documentos.

Entre los conceptos clave que podemos encontrar en Lucene destacan:

• Index: colección de documentos con similares características.

• Type: partición lógica que contiene aquellos documentos que comparten información a través de un atributo.

• Document: unidad básica de información.

• Mapping: incluye datos sobre cómo la información se almacena en el índice en forma de atributos y propiedades, y sobre cómo se debe tratar un campo a la hora de hacer las búsquedas, a nivel de extraer tokens de una determinada cadena de búsqueda.

• Relevancia: son los algoritmos utilizados para clasificar los resultados basados en la consulta.

• Corpus: colección de todos los documentos del índice.

• Segmentos: datos fragmentados que almacenan el índice invertido y permiten buscar en el índice de una manera eficiente.

1.6.2 Índice invertido

El índice invertido es el corazón del motor de búsqueda. Es como una estructura en formato diccionario en un formato clave-valor, donde las claves son los términos que hemos generado con los n-gramas de todos los documentos. Cada índice invertido almacena la posición y el identificador del documento. Para cada término nos dice en qué documentos (indica la posición) aparece cada uno.

El índice invertido se crea por temas de optimización y son las estructuras de datos las que permiten realizar búsquedas sobre los documentos. Por ejemplo, Lucene, debido a su índice invertido, es uno de los motores más rápidos la hora de buscar un término en un campo. Cada campo que se indexa en Lucene se convierte en una estructura de búsqueda rápida para su tipo particular donde, por ejemplo, los campos de texto se dividen en tokens y los campos numéricos se convierten en sus representaciones binarias.

1.6.3 Apache Solr

Apache Solr es un motor de búsqueda de código abierto basado en la librería de búsqueda, escrita en Java, Lucene. Al haber sido desarrollado durante varios años y con las colaboraciones de la comunidad de desarrolladores, ha conseguido mejorar sus funcionalidades con el paso del tiempo. Apache Solr, además, provee diversas API para realizar desarrollos que precisen del uso del motor de búsqueda para recuperar información, y es muy sencillo trabajar con él gracias a su funcionalidad transparente al usuario. Para su ejecución, requiere de una máquina o servidor local y tener instalada la JVM (Java virtual machine) en el sistema.

1.6.4 ElasticSearch

ElasticSearch es un motor de búsqueda open source y, al igual que Apache Solr, está basado en la librería de búsqueda de Java Lucene. Sin embargo, ElasticSearch ha centrado su desarrollo en optimizar su funcionamiento y ha intentado ofrecer siempre funcionalidades que operan como motor de búsqueda de forma óptima. Esto quiere decir que, aunque provee numerosas API de desarrollo, intenta dejar la funcionalidad ajena al proceso de búsqueda a selección del desarrollador. Aun así, ElasticSearch es hoy en día uno de los mejores motores de búsqueda debido a lo bien construido que está y a una gran comunidad de usuarios que respalda el proyecto.

El hecho de que ElasticSearch utilice Lucene para resolver las búsquedas supone una ventaja en comparación con otros sistemas de consulta de bases de datos SQL. Una llamada a una API nos permite realizar búsquedas utilizando objetos JSON, lo cual resulta mucho más flexible y da a cada parámetro de búsqueda dentro del objeto un peso, importancia y/o prioridad diferentes.

Por ejemplo, insertar un documento en ElasticSearch es tan fácil como llamar al API REST que proporciona y enviar un objeto JSON mediante el método POST; si va a actualizar un documento, se utiliza el método PUT. Entre las ventajas que ofrece ElasticSearch destacamos:

• Podemos añadir un documento sin crear un índice.

• Si el índice no ha sido creado, ElasticSearch creará automáticamente el tipo de asignación y los campos al añadir un documento.

• ElasticSearch lo que hace internamente es deducir los tipos de datos basados en los del documento.

• En ElasticSearch, todas las conversiones de tipos se gestionan automáticamente. La búsqueda de un término es archivada por ElasticSearch utilizando el formato correcto para el campo.

1.6.5 Otros motores de búsqueda

Dentro del mercado IT podemos encontrar otros motores de búsqueda, entre los que podemos destacar:

• Splunk (https://www.splunk.com) es una plataforma y un motor de búsqueda orientado a las anaĺíticas para el Big Data.

• Algolia (https://www.algolia.com). Algolia’s Search es un producto que se ofrece como un SaaS (software as a service). Se trata de un servicio al que se accede a través de una plataforma web y que permite al usuario subir colecciones de documentos y realizar consultas sobre ellos. Para interactuar con la plataforma, aparte del propio servicio web, el servicio ofrece API (interfaz de programación de aplicaciones) de acceso para poder acceder desde otras aplicaciones a los recursos de Algolia.

• Amazon CloudSearch (http://aws.amazon.com) es un motor de búsqueda para soluciones software con servicio cloud que se oferta desde Amazon Web Services. Es el producto más conocido de computación de servicios en la nube de la compañía Amazon. Este motor de búsqueda permite, tras registrarse en la plataforma, subir documentos y realizar búsquedas en ellos. Para construir aplicaciones que empleen los recursos de Amazon CloudSearch, la plataforma provee una API que permite el desarrollo de aplicaciones diversas, aunque principalmente de tipo web.

• SearchBlox (https://www.searchblox.com) es un producto de pago, que sus distribuidores definen como el mejor sustituto para ElasticSearch o Google Search Appliance. El servicio se ofrece como búsqueda en la nube, al igual que entornos como Microsoft Azure Search o Algolia. Sin embargo, ofrecen también la instalación del servicio en local en cierto tipo de servidores. El servicio provee también un potente sistema de búsqueda accesible a través de una API para desarrolladores, que permite a los usuarios que lo deseen emplear este servicio en caso de necesitarlo.

• Sphinx Search Server (http://sphinxsearch.com) es otro motor de búsqueda de código abierto desarrollado en C++. A pesar de ser muy potente, es relativamente complejo de manejar, debido a que se ha diseñado para ser altamente configurable y porque la mayor parte de la documentación y recursos que aporta son muy escasos. Sin embargo, es un motor de búsqueda muy completo y versátil, que puede actuar en ocasiones de gestor de bases de datos, y con una fuerte comunidad detrás que lo mantiene actualizado. Sphinx es una de las alternativas recomendadas para aquellos que busquen personalizar su motor de búsqueda ya que, a través de diferentes opciones, ofrece la posibilidad de afinar el algoritmo de relevancia, así como de indicar índices o determinar parámetros que deberán ser tenidos en cuenta en cada búsqueda.

1.7 HERRAMIENTAS DE PROCESAMIENTO DE LENGUAJE NATURAL (PLN)

Las tecnologías de procesamiento de lenguaje natural son un conjunto de herramientas lingüísticas que permiten extraer información relativa a características léxicas, morfosintácticas y semánticas de un determinado texto.

Existen muchas propuestas en este ámbito, y entre ellas destacan las siguientes:

• OpenNLP (https://opennlp.apache.org) es un proyecto de la fundación Apache que consiste en una librería de aprendizaje automático cuyo objetivo es el procesamiento del lenguaje humano en texto. Posee soporte para las tareas de procesamiento de lenguaje natural (PLN) más comunes. No tiene soporte para el uso de distintos idiomas, pero posee una serie de modelos entrenados en diferentes idiomas para propósitos específicos. En el caso del español, encontramos cuatro modelos entrenados en reconocimiento de entidades: personas, organizaciones, localizaciones y misceláneo. Cualquier extractor de interés que no esté en los modelos tendría que ser entrenado para su objetivo específico.

• CoreNLP (https://stanfordnlp.github.io/CoreNLP) es un proyecto de la Universidad de Stanford que provee una serie de herramientas de análisis de lenguaje humano (figura 1.5). Proporciona instrumentos para un amplio análisis gramático, además de diferentes elementos de extracción de la información. Posee soporte oficial para seis idiomas, aunque no todos tienen disponibles todas las funciones. La integración con otros proyectos se realiza a través de una serie de API en distintos lenguajes. A nivel práctico, esta herramienta es utilizada por la Biblioteca Virtual Miguel de Cervantes, concretamente en el componente correspondiente al analizador sintáctico: http://data.cervantesvirtual.com/analizador-sintactico-automatico.

• FreeLing (http://nlp.lsi.upc.edu/freeling/node/1) es un proyecto de la Universitat Politècnica de Catalunya (figura 1.6). Se trata de una librería con una serie de herramientas open source para el análisis de lenguaje humano. Está escrita en C++ y tiene soporte para muchos idiomas y dialectos españoles, pero carece de una comunidad sólida que utilice la herramienta de manera asidua; tampoco cuenta con una documentación tan extensa como las propuestas anteriores.

Tanto CoreNLP como FreeLing proporcionan una web para demostraciones en la que puede insertar texto y extraer características.

http://nlp.stanford.edu:8080/corenlp/process

Figura 1.5 Prueba de texto con CoreNLP.

http://nlp.lsi.upc.edu/freeling/demo/demo.php

Figura 1.6 Prueba de texto con FreeLing.

Capítulo 2
APACHE LUCENE

Apache Lucene (http://lucene.apache.org) es una librería para la recuperación de información que permite añadir funcionalidades de búsqueda a cualquier aplicación realizada en Java. En los últimos años, se ha convertido en la librería de referencia para implementar herramientas de recuperación de información, llegando a ser casi un estándar. Es importante destacar que Lucene no es una herramienta de búsqueda en sí misma, ya que no proporciona una interfaz de usuario, sino una librería más a bajo nivel que proporciona funcionalidades de recuperación de información que se pueden añadir a cualquier aplicación.

Apache Lucene ha sido ampliamente usada por su utilidad en la implementación de motores de búsqueda y es útil para cualquier aplicación que requiera indexado y búsqueda de textos. A pesar de que Lucene está desarollada en Java, podemos encontrar implementaciones en otros lenguajes, que podemos ver en http://cwiki.apache.org/lucene-java/LuceneImplementations.

Entre los principales objetivos de este capítulo podemos destacar:

• Comprender la arquitectura a nivel de clases e interfaces que proporciona Lucene.

• Conocer y comprender la funcionalidad de los componentes básicos de un motor de búsqueda basado en Apache Lucene.

• Comprender el concepto de índice invertido de Apache Lucene.

• Conocer el proceso de tokenización y búsqueda en Apache Lucene.

2.1 INTRODUCCIÓN A APACHE LUCENE

Apache Lucene permite a los desarrolladores integrar funciones de indexación y búsqueda de información textual dentro de sus proyectos. También permite la búsqueda de texto completo, es decir, permite buscar en un conjunto de documentos de texto uno o más términos definidos por el usuario. Para poder utilizar Lucene, es necesario seguir una serie de pasos o fases para conseguir realizar búsquedas efectivas: indexación y búsqueda.

• Indexación: el proceso de indexación consiste en analizar y extraer de entre toda la información disponible la verdaderamente relevante. Posteriormente, con esa información se crea el índice a partir del cual se realizarán las búsquedas. El índice es una estructura de datos que permite acceso rápido a la información (algo similar a lo que podría ser el índice de un libro).

• Búsqueda: el proceso de búsqueda consiste en consultar el índice para obtener los documentos donde aparecen unas determinadas palabras o una determinada expresión de consulta.

En la base de la arquitectura de Lucene se encuentra el concepto de documento (Document) que contiene campos (Fields) de texto. Esta flexibilidad permite a Lucene ser independiente del formato del fichero. Lucene soporta la indexación de textos que se encuentran en muchos tipos de documentos, como documentos PDF, páginas HTML y documentos de Microsoft Word.

El índice es el elemento más importante en el proceso de búsqueda con Lucene y se considera el corazón de un motor de búsqueda, ya que en él se almacenan todos los términos de todos los documentos. Se trata de un tipo de índice invertido, que consiste principalmente en una tabla donde se guarda la posición de cada término. No obstante, para poder crear un índice, primero es necesaria la extracción de todos los términos de todos los documentos —proceso que cada usuario o desarrollador puede configurar individualmente—. En la configuración, los desarrolladores establecen qué campos quieren incluir en el índice.

Los objetos con los que Lucene trabaja son documentos de cualquier tipo. Estos documentos contienen una serie de campos e incluyen información relativa a la información que queremos almacenar (como el nombre del autor, el título del documento o el nombre del archivo). Cada campo tiene una designación y un valor único, de forma que, al crear el índice, el usuario puede decidir qué metadatos quiere incluir.

En la indexación de los documentos, tiene lugar también un proceso que se conoce como tokenización. Para una máquina, un documento es ante todo un conjunto de información. Incluso cuando se pasa del nivel de los bits al de contenido legible para los humanos, un documento sigue estando compuesto por una serie de signos: letras, puntuación, espacios, etc.

A partir de estos datos, se crean los segmentos de tokenización y, posteriormente, los términos (en su mayoría palabras) que finalmente van a poder buscarse. La forma más sencilla de ejecutar este tipo de tokenización es ir separando en tokens cada vez que se encuentre un espacio en blanco. Sin embargo, este método pierde su utilidad cuando un término está compuesto por varias palabras separadas, como puede ser el caso de “sin embargo”. Para solucionarlo, se recurre a diccionarios adicionales que se pueden implementar también en el código Lucene.

La principal ventaja que aporta este motor de búsqueda es que devuelve los resultados de la búsqueda ordenados en función de la similitud con los términos de búsqueda introducidos por el usuario.

Lucene, primero, crea un índice invertido para los documentos en una colección. Como vimos, un índice invertido consiste en una lista alfabética de todas las palabras que aparecen en los documentos de texto. Cada entrada en esta lista contiene punteros que hacen referencia a todos los documentos que contienen cada palabra. De esta forma, cuando un usuario busca una palabra, el algoritmo se coloca en la posición apropiada en el índice y devuelve los documentos a los que se hace referencia.

La indexación invertida es una técnica rápida y fiable para generar índices de búsqueda, incluso para grandes cantidades de datos. Por ejemplo, una colección de textos que contiene 1 000 documentos con un promedio de 200 palabras cada uno, tendría un total de aproximadamente 200 000 palabras. Si la búsqueda de una palabra tuviera que pasar por toda la colección, el algoritmo tendría que hacer 200 000 comparaciones de palabras.

Por el contrario, el índice invertido contiene una sola entrada para cada palabra. El número de entradas dependerá de los documentos a indexar pero, por regla general, el tamaño de un índice invertido casi siempre estará por debajo del número total de palabras. Por ejemplo, si tenemos una colección de documentos de 200 000 palabras, esto podría generar un índice invertido de 10 000 palabras.

Otra de las ventajas de usar un índice invertido es que es más fácilmente escalable, ya que el número de palabras diferentes (el vocabulario) crece de forma más lenta que el número total de palabras. En algún momento, los nuevos documentos añadidos a la colección generarán muy pocas o ninguna entrada nueva en el índice, ya que las entradas existentes en el índice recibirán más referencias (punteros) para las palabras correspondientes a los nuevos documentos.

De esta forma, la frecuencia real de las palabras en un documento o una colección de documentos se acercará a la distribución descrita por la Ley de Zipf a medida que la colección de documentos se hace más grande.

La Ley de Zipf (https://es.wikipedia.org/wiki/Ley_de_Zipf) describe una distribución estadística de frecuencias de palabras presentes en todos los idiomas donde la probabilidad de que una palabra aparezca en un documento es inversamente proporcional a la frecuencia de aparición de cada una de las palabras. Dicha ley establece que la palabra más frecuente de un texto aparece el doble de veces que la siguiente más frecuente, tres veces más que la tercera más frecuente, cuatro veces más que la cuarta más frecuente, y así sucesivamente.

De esta forma, en términos matemáticos, la ley afirma que si se ordenan todas las palabras por frecuencia de uso, la segunda más frecuente aparece 1/2 veces el número de veces que aparece la más frecuente; la tercera, 1/3 veces; y, en general, la que ocupa la posición n aparece 1/n veces que la más frecuente.

2.2 MÓDULOS Y FUNCIONALIDADES DE APACHE LUCENE

Lucene permite indexar y realizar búsquedas sobre cualquier conjunto de datos que se puedan extraer de un texto, independientemente de cuál sea la fuente, el formato o el idioma. Esto permite que se puedan indexar y buscar datos almacenados en ficheros contenidos en páginas web o en servidores remotos, documentos almacenados en sistemas de archivos locales, ficheros de texto, documentos de Word, XML, HTML, archivos PDF o cualquier otro formato del que se pueda extraer información textual.

La búsqueda de texto es un proceso que consiste en encontrar términos en un índice para acceder a los documentos en los que aparecen. Tanto la recolección de datos como la generación de documentos son tareas específicas de cada aplicación y que, por lo tanto, Lucene no implementa. Además, tampoco proporciona ningún tipo de interfaz, por lo que cada aplicación deberá generar las suyas propias. Entre los módulos que aporta Lucene podemos destacar:

• Análisis de documentos (Analyze Document): Lucene proporciona diferentes analizadores que permiten controlar este proceso y ofrece la posibilidad de personalizar los analizadores que encontramos por defecto o crear uno nuevo combinando sus funcionalidades para tokenizar y filtrar los tokens, de manera que el proceso de creación de tokens puede ser personalizado.

• Indexado de documentos (Index Document): módulo que permite añadir los documentos al índice. Lucene almacena los documentos en una estructura de índice invertido, lo que hace más eficiente el almacenamiento del índice en el disco y agiliza las búsquedas.

• Construcción de la consulta (Build Query): cuando un usuario utiliza una aplicación de búsqueda, generalmente lo hace a través de un formulario web en el que introduce una consulta que el navegador envía al servidor web que contiene el motor de búsqueda. Una vez recibida, se debe transformar en una Query del motor de búsqueda; este proceso es el que engloba el módulo de “Construcción de la consulta”.

• Ejecución de la consulta (Run Query): es el proceso de consultar el índice de búsqueda y recuperar los documentos que coinciden con la Query solicitada. Lucene encapsula este paso, proporcionando una API para realizarlo de forma sencilla; aunque también permite personalizarlo, de forma que se pueda realizar el filtrado y ordenamiento de los resultados.

• Interpretación de resultados (Render Results): consiste en generar una lista de resultados que el usuario pueda entender y utilizar de forma sencilla. Esta lista se genera a partir de la lista interna de identificadores de documentos ordenados que concuerdan con una consulta.

El proceso de análisis comienza con la tokenización del texto, es decir, con la extracción de cadenas o palabras que lo componen, y que se conocen como tokens. Para generar los tokens, se aplican al texto diferentes procesos, como la extracción de palabras, la eliminación de los signos de puntuación, la normalización (paso a minúsculas), el stemming (reducción de las palabras a su forma raíz) o la lematizacion (la transformación de las palabras a su forma básica). Los tokens, combinados con el nombre de su campo asociado, forman los términos.

La aproximación de Lucene se basa en combinar diferentes modelos de espacio vectorial y booleano, y ofrece la posibilidad de decidir cuál de ellos usar a la hora de realizar una consulta. La fase de ejecución de la consulta termina cuando Lucene devuelve una lista de documentos que se debe transformar en una salida que el usuario pueda comprender y analizar.

Para la indexación y recuperación del contenido textual de los documentos que gestiona bastaría con utilizar alguno de los analizadores que proporciona por defecto Lucene. Pero si se quieren potenciar las búsquedas de modo que no se produzca demasiado ruido en los resultados y se encuentren documentos similares, hay que conseguir que los documentos estén lo más normalizados posible.

El proceso de normalización de los documentos se puede realizar utilizando alguna de las librerías que facilitan los siguientes procesos:

• Eliminación de stopwords: son palabras de uso frecuente que no se tienen en cuenta ni en el proceso de indexación ni en el de búsqueda.

• Stemming: es un método para obtener la cadena raíz —no necesariamente léxica— de una palabra, mediante el que las palabras se reducen a su raíz lingüística o a su stem. El proceso de stemming es muy útil para el inglés, aunque para el castellano no siempre se puede aplicar porque el stem de una palabra no tiene por qué coincidir con su raíz léxica.

Al analizar los datos de los que forma parte el proceso de tokenización, Lucene también ejecuta un proceso de normalización. Esto significa que los términos se convierten a una forma estandarizada, de modo que, por ejemplo, todas las mayúsculas se escriben en minúscula. Además, Apache Lucene introduce una clasificación mediante una serie de algoritmos (por ejemplo, a través de la medida TD-IDF), con la que es posible obtener los resultados más relevantes.

Lucene se encarga de construir y gestionar lo que se denomina índice invertido, una estructura de datos especializada que permite emparejar documentos de texto con términos de consulta del usuario. Las características principales de estos motores son:

• Escalables: capaces de distribuir el trabajo (indexación y procesado de consultas) entre múltiples servidores en un clúster.

• Listo para ser desplegado: ambas soluciones vienen con ejemplos prácticos para levantar un servicio con el mínimo esfuerzo.

• Optimizados para búsquedas: son rápidos, muy rápidos, capaces de ejecutar consultas complejas en decenas de milisegundos.

• Grandes volúmenes de datos: están diseñados para almacenar índices de millones de documentos.

• Centrados en texto: la principal ventaja que aporta es la posibilidad de manejar textos, extrayendo su estructura implícita al índice del motor para mejorar la búsqueda.

• Resultados ordenados por relevancia: dependiendo de la consulta del usuario, se le devuelven documentos clasificados en base a dicha consulta por orden de relevancia.

Dado que Lucene subdivide los documentos en campos de texto y los clasifica de forma lógica, la búsqueda de texto completo con Lucene funciona de forma muy precisa y responde bien al encontrar resultados relevantes para textos y documentos similares. Mientras reconozca un texto, el formato (texto plano, PDF, HTML u otros) es lo de menos porque, en lugar de indexar archivos, Lucene trabaja con texto y metadatos, aunque los archivos se han de extraer de la biblioteca de todas las formas posibles.

Para ello, el equipo de Lucene ha desarrollado el proyecto Apache Tika (https://tika.apache.org/index.html), que es una herramienta para el análisis, traducción e indexación de textos. La herramienta lee texto y metadatos de diferentes tipos de archivos y, posteriormente, extrae el texto y lo pone a disposición para su tratamiento posterior. Esta herramienta consta de un analizador (parser) y un detector. El analizador examina los textos y estructura el contenido en una jerarquía ordenada, mientras que el detector tipifica el contenido (por ejemplo, reconociendo el tipo de archivo y el tipo de contenido a partir de los metadatos).

Entre las funciones y características más importantes de Lucene podemos destacar:

• Indexación rápida, tanto en fases como por lotes (hasta 150 GB por hora, según sus desarrolladores).

• Uso económico de la memoria RAM.

• Escrito por completo en Java y, por lo tanto, multiplataforma (las variantes en lenguajes de programación alternativos son Apache Lucy y Lucene.NET).

• Interfaz para plugins.

• Búsqueda por campos de texto (categorías como contenido, título, autor, palabra clave), también para varios al mismo tiempo.

• Clasificación por campos de texto.

• Listado de los resultados de la búsqueda según similitud o relevancia.

2.3 ARQUITECTURA DE APACHE LUCENE

El proceso de indexación que utiliza Apache Lucene requiere analizar los objetos implicados en dicho proceso (figura 2.1):

Figura 2.1 Arquitectura de Apache Lucene.

• Directory. El directorio representa la ubicación de un índice de Lucene. Es una clase abstracta que permite a sus subclases almacenar el índice como mejor consideren. Podríamos obtener una implementación concreta para almacenar archivos en un directorio dentro del sistema de archivos, y pasar dicho objeto a su vez al constructor de IndexWriter.

• IndexWriter. IndexWriter es el componente central del proceso de indexación. Esta clase crea un nuevo índice o abre uno existente, y añade, elimina o actualiza documentos en el índice. Puede pensar en IndexWriter como en un objeto que le da acceso de escritura al índice pero que no le permite realizar búsquedas. IndexWriter necesita un lugar para almacenar su índice, y para eso sirve la clase Directory.

• Analyzer. Antes de indexar el texto, este se pasa por un analizador. El analizador, especificado en el constructor IndexWriter, se encarga de extraer esos tokens del texto y eliminar aquellas palabras que no sean relevantes. Analyzer es una clase abstracta y Lucene viene con varias implementaciones de la misma. Algunas de estas implementaciones se ocupan de omitir las stopwords y otras se encargan de la conversión de tokens a letras minúsculas. Para un desarrollador que integra Lucene en una aplicación, en ocasiones, la elección de los analizadores es un elemento crítico del diseño de la aplicación.

• Document. Un documento representa una colección de campos. Un documento es, simplemente, un contenedor para múltiples campos donde el campo es la clase que contiene el contenido textual que posteriormente se indexará en el índice. Los campos de un documento representan el documento o metadatos asociados con ese documento. Los metadatos (autor, título, tema y fecha de modificación) se indexan y se almacenan por separado como campos de un documento.

• Field. Cada documento en un índice contiene uno o más campos con un determinado nombre, incorporados en una clase llamada Field (campo). Cada campo tiene un nombre y un valor correspondiente, y un conjunto de opciones que controlan cómo Lucene indexará el valor del campo. Un documento puede tener más de un campo con el mismo nombre. En este caso, los valores de los campos se añaden, durante el proceso de indexación, en el orden en que se añadieron al documento.

Lucene, en principio, solo trata con texto y números. De esta forma, el núcleo de Lucene maneja los tipos java.lang.String para cadenas texto y java. io.Reader para lectura de ficheros y los tipos numéricos nativos (como int o float). Aunque varios tipos de documentos se pueden indexar y hacer búsquedas, procesarlos no es tan sencillo como procesar contenido puramente textual o numérico.

En nuestro indexador, para cada archivo de texto que encontramos creamos una nueva instancia de la clase Documento, la rellenamos con campos y metadatos y, finalmente, añadimos ese documento al índice, indexando finalmente el archivo.

La biblioteca Lucene ofrece posibilidades prácticamente ilimitadas para desarrollar sus propias aplicaciones de búsqueda. El proceso de desarrollo se centra en los dos pasos clave para buscar por índice invertido: primero indexar documentos y posteriormente realizar búsquedas sobre los índices creados.

La clase Java org.apache.lucene.index.IndexWriter es responsable de crear y modificar índices. El constructor de la clase org.apache.lucene.index.IndexWriter acepta dos argumentos: un objeto de la clase org.apache. lucene.store.Directory, que representa un directorio donde almacenar los documentos, y otro objeto tipo configuración de la clase org.apache.lucene.index.IndexWriterConfig.

Posteriormente, con el método IndexWriter.addDocument() podemos añadir un documento al índice. El método close() guarda los cambios en el directorio indicado y cierra los archivos temporales que pudieran quedar abiertos.

Un documento consta de un número de campos que Lucene define con la clase org.apache.lucene.document.Field. Estos campos no siguen un esquema definido, ya que cada documento dentro de un índice puede contener campos de diferentes tipos y contenidos. En la práctica, los campos sirven para clasificar el contenido y hacer que se pueda buscar por separado. Los desarrolladores usan el método add() para añadir este contenido a un objeto del tipo Document. Para los casos más comunes, Lucene proporciona una serie de subclases apropiadas para diferentes tipos de datos, como TextField, IntField y DoubleField.

El siguiente ejemplo crea un documento y lo almacena en el índice utilizando el método addDocument() del objeto IndexWriter:

/* Creando un objeto IndexWriter * /

Analyzer analyzer = new EnglishAnalyzer(VERSION. Lucene_40);

IndexWriterConfig config = new IndexWriterConfig(analyzer);

Directory dir = FSDirectory.open(new File(“/home/user/lucene/index”));

IndexWriter writer = new IndexWriter(dir, config);

/* Nuestro documento a almacenar*/

Field textField = new TextField(“text”, “My document”, Field.Store.YES);

Document documento = new Document();

documento.add(textField);

/* Añadir el documento al índice*/

writer.addDocument(documento);

writer.close();

En el código anterior vemos cómo se configura el objeto IndexWriterConfig, donde se ha utilizado un objeto de la clase org.apache.lucene.analysis. Analyzer, que se encarga del análisis de los textos que se van añadiendo al índice. En el ejemplo anterior se ha empleado el analizador para textos en inglés con la clase org.apache.lucene.analysis.en.EnglishAnalyzer.

2.3.1 Proceso de tokenización y búsqueda en Apache Lucene

El analizador comentado desempeña un papel importante para la estructura del índice porque, entre otras cosas, se encarga del proceso de “tokenización”, es decir, de la división del texto en los componentes representados por sus propios tokens de entrada en el índice invertido.

Este proceso es una tarea bastante compleja porque debe diferenciar correctamente entre signos de puntuación y caracteres especiales como partes de un token (como en una dirección web o de correo electrónico) y caracteres independientes (como un punto al final de una oración).

Además, existen numerosas formas de optimizar el índice en el análisis; por ejemplo, se pueden eliminar palabras como “y” —que son bastante frecuentes pero de poca importancia— con la ayuda de los filtros que se encuentran en org.apache.lucene.analysis.TokenFilter.

Los analizadores y tokenizadores son componentes importantes del proceso de indexación porque determinan qué puede buscar un usuario. Para ayudar a evitar cualquier confusión de los diferentes algoritmos de análisis y tokenización, cuando se realiza una consulta sobre alguno de los índices normalmente se lleva acabo utilizando el mismo analizador que el empleado para indexar los documentos.

String queryString = new String(“texto_buscar”);

int maxHits = 100;

/* Crear los objetos IndexReader y IndexSearcher */

Analyzer analyzer = new EnglishAnalyzer(VERSION. Lucene_40);

Directory dir = FSDirectory.open(new File(“/home/user/lucene/index”));

IndexReader reader = DirectoryReader.open(dir);

IndexSearcher searcher = new IndexSearcher(reader);

/* Crear y ejecutar una query */

StandardQueryParser parser = new StandardQueryParser(analyzer);

Query query = parser.parse(queryString);

TopDocs topdocs = searcher.search(query, maxHits);

/* Leer los documentos que se han encontrado */

ScoreDoc[] docs = topdocs.scoreDocs;

for (ScoreDoc documento : docs){

System.out.println(documento.doc+»\t»+documento.score);

}

reader.close();

En el ejemplo anterior, en primera instancia creamos los objetos IndexReader e IndexSearchers. La clase org.apache.lucene.index.IndexReader es responsable de leer un índice a partir de un objeto de la clase org.apache.lucene.index. DirectoryReader, que contiene el directorio donde se encuentra almacenado nuestro índice. El constructor de la clase org.apache.lucene.search.Index-Searcher recibe dicho objeto IndexReader como parámetro en su constructor.

Respecto al proceso de búsqueda, la clase IndexSearcher será la encargada de ejecutar las consultas de búsqueda utilizando un objeto IndexReader. Un objeto de la clase org.apache.lucene.queryparser.flexible.standard. StandardQueryParser genera dichos objetos de consulta a partir de una cadena de consulta y un analizador. Alternativamente, los desarrolladores pueden implementar su propio analizador de consultas para construir los diferentes tipos de consultas y objetos de búsqueda de forma personalizada.

Las búsquedas pasan por el método search() del objeto IndexSearcher. El resultado se almacena en un objeto de la clase org.apache.lucene.search. TopDocs, que contiene los documentos encontrados, así como información sobre el número de documentos y la relevancia de cada uno de ellos.

2.4 TRABAJAR CON APACHE LUCENE

Antes de crear un índice y empezar a trabajar con Lucene, es necesario descargar el framework y crear un proyecto para empezar a trabajar. Lucene está disponible en la página web http://lucene.apache.org, en concreto dentro de la sección Apache Lucene Core (http://lucene.apache.org/core). En la sección Download encontrará disponible para descargar la última versión disponible. Una vez descargado el archivo, lo descomprimimos en una carpeta y extraemos los ficheros que vamos a necesitar para incorporarlos a nuestro proyecto.

2.4.1 Configuración del entorno

Lucene está construido en Java, por lo que es necesario disponer de un sistema operativo compatible con Java con el JDK instalado. Se recomienda usar una versión del JDK compatible con la última versión de Lucene (figura 2.2). Una forma de incluir Lucene en un proyecto de Java es a través de un repositorio Maven. Aquí hay una descripción de dependencia de Maven de muestra para incluir en la biblioteca lucene-core. Puede encontrar el repositorio oficial de Lucene en http://mvnrepository.com/artifact/org.apache.lucene.

Figura 2.2 Librería de Apache Lucene en el repositorio de Java.

Para configurar Lucene en Maven, es necesario añadir la siguiente configuración en el fichero de dependencias pom.xml del proyecto. La versión con la que vamos a trabajar es la publicada en el repositorio de Maven, la 8.6.3.

https://mvnrepository.com/artifact/org.apache.lucene/lucene-core/8.6.3

<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->

<dependency>

<groupId>org.apache.lucene</groupId>

<artifactId>lucene-core</artifactId>

<version>8.6.3</version>

</dependency>

Los pasos para crear un proyecto desde Eclipse son:

• Creamos un nuevo proyecto haciendo clic en File -> New-> Maven Project (figura 2.3).

Figura 2.3 Crear nuevo proyecto con Maven desde Eclipse.

• Introducimos un nombre para el artefecto y definimos la versión (figura 2.4).

Figura 2.4 Nombre y versión del artefacto Maven.

El siguiente paso consiste en añadir en el fichero pom.xml las dependencias correspondientes a la API de Lucene.



También podríamos configurar el proyecto añadiendo las librerías al Build Path y haciendo clic en Add External Archives. Podríamos añadir las siguientes librerías (figura 2.5):

Figura 2.5 Librerías para el Java Build Path.

2.4.2 Crear un índice

El núcleo de un motor de búsqueda basado en Lucene es el índice. Por eso, el primer paso es crear una clase en Java para la indexación. La creación de un índice constituye el punto de partida para el trabajo con Lucene. Una vez generado, se le irá añadiendo la información relevante de los distintos documentos seleccionados para el análisis.

Antes de empezar con el ejemplo, vamos a crear una clase en un proyecto de Eclipse. Para ello, haga clic con el botón derecho sobre el nombre del proyecto y, en el menú desplegable, haga clic en New>Class. En la ventana emergente, en este ejemplo escribiremos “LuceneCrearIndice”, seleccionando también las casillas “Public static void main” e “Inherited abstract method”.

El programa que se muestra a continuación, LuceneCrearIndice.java, construye un índice, para lo cual simplemente crea un objeto de la clase IndexWriter que busca e indexa los documentos que se encuentran en una carpeta específica.

En esta clase creamos nuestro índice, junto con las funciones para generar un documento en el índice. En este caso, empleamos la clase IndexWriter del paquete org.apache.lucene.index, que se configura con un objeto de la clase ClasicAnalyzer. Lucene ofrece diferentes clases para análisis; todas ellas se encuentran dentro del paquete org.apache.lucene.analysis.

El siguiente código lo podemos encontrar en el fichero LuceneCrearIndice. java dentro del repositorio:





En el código anterior tendríamos que modificar la rutas por otras que apunten a nuestro equipo. Para cada archivo encontrado en dicha carpeta creamos un objeto tipo Document; estos serán los objetos que realmente se indexarán en el índice. A esta variable le añadimos objetos de tipo Field, que son los campos que queremos indexar y a través de los cuales podremos realizar búsquedas.

Lucene proporciona la clase FSDirectory para indicarle la ruta donde crear el índice en el sistema de archivos. El parámetro indexPath representa la ubicación del directorio; si no existe el directorio, Lucene lo creará de forma automática.

Directory directory = FSDirectory.open(Paths. get(indexPath));

Lucene indexa objetos tipo Document propios de la API de Lucene. Recorre el directorio buscando archivos con extensión txt y con permisos de lectura. En este caso, a cada variable Document le añadimos dos variables Field: una con el nombre del archivo y otra con su contenido. Una vez añadidos los campos a la variable Document, el documento se añade al índice con la instrucción indice.add(doc).

En el ejemplo anterior, lo que hemos hecho ha sido indexar tantos documentos como teníamos en la ruta definida en la variable directorioDocumentos. Esta podría ser la salida:

Indexing /home/linux/documentos/java.txt

Indexing /home/linux/documentos/python.txt

Indexing /home/linux/documentos/php.txt

Indexing /home/linux/documentos/lucene.txt

Indexing /home/linux/documentos/golang.txt

El número de documentos indexados es 5

Al crear el índice, Lucene genera los segmentos para ir añadiendo documentos; cada segmento es un índice completamente independiente a partir del cual realizar búsquedas por separado.

2.4.3 Crear y escribir documentos en un índice

El siguiente código muestra un ejemplo de cómo añadir un documento a un índice. Para ello, primero se inicializan una serie de objetos de las clases Analyzer, Directory, IndexWriterConfig e IndexWriter. Una vez que se obtiene un objeto IndexWriter, se crea un nuevo documento de la clase Document con un TextField personalizado. Por último, el documento se añade al IndexWriter a través del método addDocument(doc).

El ejemplo lo podemos encontrar en el fichero LuceneCrearIndiceDocumentos.java:







Hemos visto cómo se obtiene un IndexWriter inicializado con los objetos Analyzer e IndexWriterConfig. El comportamiento de inicialización predeterminado funciona bien la mayoría de las veces. Sin embargo, puede haber situaciones en las que necesite un control más preciso durante la secuencia de inicialización. Por ejemplo, cuando el comportamiento predeterminado crea un nuevo índice y no existe un índice previo. Esto puede no ser ideal en un entorno de producción donde siempre debe existir un índice.

La clase IndexWriter expone el método addDocument(doc) que le permite añadir un documento al índice. IndexWriter añadirá dicho documento al índice especificado por el directorio. Además, es importante realizar la llamada al método indexWriter.close() al final para que se confirmen todos los cambios, y cerrar el índice para que ningún otro proceso pueda escribir sobre el mismo.

Para que los documentos queden registrados en el índice, además, hay que aplicar el método commit() del objeto IndexWriter; así, los cambios quedarán reflejados en el índice. Al final, el objetivo es añadir documentos al índice a través del objeto IndexWriter, cuya documentación podemos encontrar en:

https://lucene.apache.org/core/8_6_3/core/org/apache/lucene/index/IndexWriter.html

En el código anterior hemos utilizado el método addDocument() para añadir documentos a un índice. Este método proporciona dos constructores (uno donde se pasa por un parámetro un objeto Document y otro donde, además, se pasa por el parámetro un objeto Analyzer):

• addDocument(Document): este método permite añadir el documento utilizando el analizador predeterminado para la tokenización que se indicó al crear el IndexWriter.

• addDocument(Document, Analyzer): permite añadir el documento utilizando el analizador proporcionado para la tokenización. Para que las búsquedas funcionen correctamente, necesita el analizador empleado en el momento de la búsqueda para “hacer coincidir” los tokens producidos por los analizadores en el momento de la indexación.

2.5 REALIZAR BÚSQUEDAS EN APACHE LUCENE

Tras revisar el proceso de indexación de documentos en Lucene, continuamos con la implementación del proceso de búsqueda. Hay que tener en cuenta que la indexación es un proceso necesario para que los textos puedan buscarse.

Si consideramos un escenario de búsqueda donde ya tenemos un índice construido y funcionando en nuestro clúster, el objetivo será buscar todos aquellos documentos relacionados con el índice de Lucene que coincidan con un término de búsqueda. Lucene tiene la capacidad de obtener documentos con solo introducir el término Lucene en el índice, y devolver todos los documentos relacionados. Un término en Lucene contiene dos elementos: el valor y el campo en el que aparece.

2.5.1 Obtención de un IndexSearcher

Para realizar una búsqueda concreta necesitamos apoyarnos en la clase IndexSearcher, que proporciona la base para realizar búsquedas sobre el índice. La clase IndexSearcher es la puerta de entrada para buscar en un índice, en lo que respecta a Lucene. Un IndexSearcher toma un objeto IndexReader y realiza una búsqueda a través de este objeto.

IndexReader se comunica con el índice físicamente y devuelve los resultados. A continuación, se describe un fragmento de código que muestra cómo obtener un objeto de la clase IndexSearcher:

Directory directory = getDirectory();

IndexReader indexReader = DirectoryReader. open(directory);

IndexSearcher indexSearcher = new IndexSearcher(indexReader);

Para que los usuarios puedan realizar búsquedas y obtener lo que buscan, deben introducir un término o términos de búsqueda como parámetro de entrada. Dicho término recibe el nombre de Query (consulta) en el contexto de Apache Lucene. Esta entrada no solo debe estar formada por una palabra o conjunto de ellas, sino que también puede contener comodines y operadores booleanos como AND, OR o + y -, entre otros.

Esta consulta la vamos a construir con un objeto de la clase QueryParser, una clase dentro de Lucene que permite traducir la entrada en una solicitud de búsqueda concreta para el motor de búsqueda. Los desarrolladores también pueden configurar QueryParser de modo que se adapte exactamente a las necesidades del usuario. A continuación, pasamos a analizar cómo realizar una búsqueda. Para ello, necesitamos crear un objeto Query con un QueryParser.

2.5.2 Proceso de búsqueda con IndexSearcher

Antes de comenzar una búsqueda, necesitamos obtener un objeto de la clase IndexSearcher para facilitar la consulta de un índice determinado. IndexSearcher proporciona varios métodos de búsqueda para consultar datos y devolver objetos del tipo TopDocs como resultado.

TopDocs representa los resultados de una búsqueda y contiene una matriz de ScoreDoc que contiene el identificador de cada documento (DocId) y la puntuación (score) de cada documento que coincida con los criterios de búsqueda.

Hay que tener en cuenta que TopDocs contiene identificadores de documentos (DocId), pero no contiene ningún contenido del documento. La recuperación del contenido del documento deberá basarse en las clases IndexReader o FieldCache. Un IndexSearcher requiere un IndexReader como entrada a su constructor para inicializar. Veamos un fragmento de código simple para ver cómo se configura IndexSearcher:

Path path = Paths.get(“/home/linux/documentos”);

Directory directorio = FSDirectory.open(path);

IndexReader indexReader = DirectoryReader.open(directorio);

IndexSearcher indexSearcher = new IndexSearcher(indexReader);

Primero, configuramos un objeto de clase Directory para poder pasarlo a un DirectoryReader, que es básicamente un IndexReader. Luego, pasamos el DirectoryReader a IndexSearcher para su inicialización. Hay que tener en cuenta que IndexReader realiza una instantánea del contenido del índice en un instante de tiempo. Por lo tanto, si se actualiza el índice, debemos asegurarnos de volver a abrir el IndexReader y el IndexSearcher para que los últimos cambios aparezcan en el índice para la búsqueda.

Ahora que tenemos un IndexSearcher, estamos listos para continuar con las consultas. Para comenzar, necesitaremos construir un objeto Query para pasar al método de búsqueda de IndexSearch. Hay un par de formas de construir una consulta. QueryParser proporciona la utilidad para interpretar texto y convertirlo en una consulta.

Si fuera necesario utilizar modificadores de búsqueda, hay una sintaxis a seguir con el fin de formar una cadena de consulta. Por defecto, una frase de búsqueda devolverá cualquier documento que tenga coincidencias en cualquiera de los términos de la consulta. Mostraremos el funcionamiento de QueryParser en la próxima sección.

2.5.3 Crear consultas con Lucene QueryParser

Lucene admite un potente motor de consultas que permite una amplia gama de tipos de consulta. El siguiente paso para realizar búsquedas es crear objetos de consulta con la clase QueryParser. QueryParser es una clase que analiza una cadena de consulta y proporciona la utilidad para convertir la entrada de texto en objetos.

El método principal de la clase QueryParser es parse(String) (figura 2.6). En la siguiente imagen vemos un extracto de la documentación en formato JavaDoc de Lucene para esta clase: https://lucene.apache.org/core/8_6_3/queryparser/index.html

Figura 2.6 Extracto del JavaDoc de la clase QueryParser.

Ahora que tenemos un objeto de consulta, estamos listos para ejecutar una búsqueda. Hay que tener en cuenta que, por defecto, Lucene ordena los resultados según su relevancia y dispone de un mecanismo de puntuación que asigna una puntuación a cada documento que coincida con los criterios de búsqueda. Esta puntuación es responsable del orden de clasificación en los resultados de búsqueda.

El siguiente código lo podemos encontrar en el fichero LuceneBuscarDocumentos.java:

import java.nio.file.Path;

import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.core.WhitespaceAnalyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexReader;

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.store.Directory;

import org.apache.lucene.store.FSDirectory;

Path path = Paths.get(“/home/linux/documentos”);

Directory directorio = FSDirectory.open(path);

Analyzer analyzer = new WhitespaceAnalyzer();

IndexReader indexReader = DirectoryReader.open(directorio);

IndexSearcher indexSearcher = new IndexSearcher(indexReader);

QueryParser parser = new QueryParser(“texto”,analyzer);

Query query = parser.parse(“Python”);

int hitsPerPage = 10;

TopDocs docs = indexSearcher.search(query, hitsPerPage);

ScoreDoc[] hits = docs.scoreDocs;

int end = (int) Math.min(docs.totalHits.value, hitsPerPage);

System.out.print(“Total de documentos: “ + docs.totalHits);

System.out.print(“\nResultados: “);

for (int i = 0; i < end; i++) {

Document d = indexSearcher.doc(hits[i].doc);

System.out.println(“\tDocumento: “ + d.get(“nombre”)+” Texto: “ + d.get(“texto”));

}

En el fragmento de código anterior, utilizamos la clase QueryParser para generar un objeto del tipo Query. El objeto QueryParser puede aceptar cualquier texto como primer parámetro y convertirlo en una consulta. Hay que tener en cuenta que también tenemos que proporcionar un analizador (Analyzer) a QueryParser como segundo parámetro.

Esto se hace para poder aplicar el mismo tratamiento de análisis de texto que en el proceso de indexación y garantizar de esta forma la precisión de la búsqueda.

Una vez definido el objeto de búsqueda, podríamos obtener los documentos con el método search y recorrerlos con el siguiente bucle:

TopDocs topDocs = indexSearcher.search(query, hitsPerPage);

for (ScoreDoc scoreDoc : topDocs.scoreDocs) {

Document documento = new Document();

documento = indexReader.document(scoreDoc.doc);

System.out.println(scoreDoc.score + “: “ + documento.getField(“nombre”).stringValue()+ “ “+ documento.getField(“texto”).stringValue());

}

Puede pensar en IndexSearcher como en una clase que abre un índice en modo de solo lectura. Requiere una instancia de la clase Directory que contenga el índice creado previamente y, luego, ofrece una serie de métodos de búsqueda. El método search acepta como parámetros un objeto Query y un número entero —que indica el número de documentos a devolver— y devuelve un objeto TopDocs.

En la ejecución del programa LuceneBuscarDocumentos.java vemos el número de documentos obtenidos para la cadena de búsqueda Python, junto con la puntuación de cada documento (figura 2.7):

Figura 2.7 Ejecución del programa LuceneBuscarDocumentos.java.

2.5.4 Sintaxis de las consultas en Apache Lucene

La mayoría de los motores de búsqueda no solo se limitan a la búsqueda de un solo término. Con determinados métodos es posible encadenar términos, buscar frases o excluir palabras concretas. Apache Lucene también ofrece estas posibilidades a través de Lucene Query Syntax, que permite buscar expresiones complejas, incluso en diferentes campos.

• Un solo término: Lucene asume que el término introducido está escrito correctamente. Si el usuario introduce un error ortográfico, obtendrá un resultado negativo al buscar por este término.

• Frase: las frases son secuencias de palabras establecidas. En este caso, no solo son importantes los términos individuales introducidos, sino también el orden en que aparecen.

• Consulta con comodines: en la consulta puede reemplazar uno o más caracteres con diferentes marcadores de posición. Los caracteres comodines pueden usarse tanto al final como en la mitad de un término, pero nunca al principio. Por ejemplo, el asterisco sustituye a un número infinito de caracteres.

• Consulta de expresiones regulares: con las expresiones regulares puede buscar al mismo tiempo varios términos que comparten una serie de similitudes. A diferencia de los comodines, con estos marcadores se definen exactamente aquellas expresiones con las cuales quiere coincidir.

• Consulta Fuzzy Searching: este tipo de consulta se utiliza cuando se quiere tener tolerancia a errores. Se emplea la distancia de Damerau-Levenshtein, fórmula que evalúa las similitudes para establecer cuán grande puede ser la desviación. Use la virgulilla para indicarlo. Se admiten distancias de 0 a 2. Ejemplo: Coche~1.

• Búsquedas de proximidad: cuando quiera permitir una aproximación en las frases, utilice también la virgulilla. Por ejemplo, puede especificar que desea buscar dos términos incluso si hay 5 palabras entre ellos. Ejemplo: “Coche rojo”~5.

• Búsquedas por rango: en este tipo de consulta se busca entre dos términos en un área concreta. La clasificación funciona de acuerdo con un orden lexicográfico. Se recurre a los corchetes para indicar un área de inclusión y a las llaves para indicar un área de exclusión (en ambos casos determinadas por los dos términos indicados en la búsqueda). Estos términos están delimitados con “TO” (a, hacia).

• Boosting: Lucene ofrece la oportunidad de dar más relevancia en la búsqueda a determinados términos o frases, lo que influye en la clasificación de los resultados. Para indicar este parámetro de búsqueda se utiliza el acento circunflejo seguido del valor al que se le quiera dar más relevancia. Ejemplo: Coche^2 rojo.

• Operadores booleanos: los operadores lógicos sirven para establecer conexiones entre términos en una consulta. Los operadores han de estar siempre escritos en mayúsculas, para que Lucene no los considere términos de búsqueda normales. Entre los principales operadores podemos destacar:

− AND: con el operador AND, ambos términos deben estar presentes en el documento para que este se muestre como resultado. En lugar del término AND se pueden usar también los símbolos &&. Ejemplo: Coche && rojo.

− OR: con el operador OR, situado entre dos términos de búsqueda, se indica que para que se muestre un documento como resultado al menos uno de los términos indicados ha de aparecer en él. Además de OR, se puede usar el símbolo ||. Ejemplo: Coche || rojo.

− +: el signo más se utiliza para establecer un caso específico del operador OR. Si se coloca el símbolo directamente delante de una palabra, se establece que dicho término debe aparecer, mientras que el otro es opcional. Ejemplo: +Coche rojo.

− NOT: el operador NOT excluye ciertos términos o frases de la búsqueda. También podemos utilizar un signo de exclamación o colocar un signo menos justo antes del término que se desea excluir. Ejemplo: Coche rojo NOT azul.

• Agrupar términos: utiliza los paréntesis para agrupar términos dentro de una consulta. Es de gran utilidad para crear entradas más complejas; por ejemplo, para vincular un término con otros dos que se rijan por un criterio de búsqueda diferente: Coche AND (rojo OR azul).

• Escapar caracteres especiales: si necesitamos utilizar como términos de búsqueda aquellos caracteres que se emplean en la sintaxis de Lucene, podemos combinarlos mediante una barra invertida. Así, podremos insertar, por ejemplo, un signo de interrogación en una consulta de búsqueda sin que el analizador lo interprete como un comodín. Para ello, es necesario escapar este carácter con la barra invertida: “¿Dónde está mi coche\?”.

2.6 BÚSQUEDA DE INFORMACIÓN CON APACHE LUCENE

La búsqueda de documentos constituye la funcionalidad principal proporcionada por Lucene. Para ello, aporta múltiples clases y métodos para la representación de consultas y para realizar búsquedas en el índice de aquellos documentos que son relevantes y cumplen con los criterios de la búsqueda. El código que se muestra a continuación, LuceneBuscador.java, es un ejemplo de cómo buscar en un índice utilizando Lucene.

El siguiente código lo podemos encontrar en el fichero LuceneBuscador.java:




En el código anterior podemos ver que hay dos clases importadas por Lucene que son las más importantes: IndexSearcher y QueryParser. Index-Searcher realiza la búsqueda en el índice y QueryParser es responsable de transformar la consulta de búsqueda a un tipo de información que el motor pueda entender. Las tres clases más importantes que proporcionan métodos relacionados con la búsqueda de documentos indexados son IndexSeacher, Query y TopDocs:

• IndexSeacher: es el corazón del proceso de búsqueda a través del cual se pueden realizar consultas (Query) que devuelven resultados (TopDocs).

• Query: es una clase abstracta que permite definir la consulta sobre la que queremos realizar la búsqueda. Nos permite buscar a partir de los campos (Field) de los documentos.

• TopDocs: contiene una lista con los documentos que cumplen las condiciones de la búsqueda.

La clase LuceneBuscador constituye la base para cualquier búsqueda en un índice de documentos (figura 2.8). En esta se crea un objeto del tipo IndexSearcher, y se pasa como parámetro al constructor el directorio donde se encuentra el índice de documentos. El método más importante de esta clase es el método search(), que devuelve todos aquellos documentos que cumplen con las condiciones de la búsqueda.

La clase Query y su clase hija QueryParser permiten analizar gramaticalmente la consulta con el método parse(). Para crear el objeto del tipo QueryParser, partimos del campo en el que deseamos buscar dentro del índice junto con el analizador gramatical.

Posteriormente, aplicamos el método parse() a la consulta y obtenemos un objeto de tipo Query, que es el que debemos usar en la búsqueda. Una vez que se obtiene una Query, se le pasa como argumento al método search(), y se realiza la búsqueda.

El método search() devuelve un objeto de tipo TopDocs, que representa una colección ordenada de documentos. El orden se determina por la relevancia de la Query en cada documento. De esta manera, se obtienen en primer lugar aquellos documentos que se ajustan mejor a las expectativas de la búsqueda.

Figura 2.8 Ejecución del programa LuceneBuscador.java.

En la salida vemos, para cada documento, donde aparece el término de búsqueda, la puntuación correspondiente:

Total de documentos: 3 hits

Puntuacion:0.24604079/python.txt

Puntuacion:0.22728294/lucene.txt

Puntuacion:0.22728294/golang.txt

2.7 BÚSQUEDA EN MÚLTIPLES ÍNDICES DE LUCENE

Algunas aplicaciones necesitan mantener índices de Lucene separados, pero permiten que una sola búsqueda devuelva resultados combinados de todos los índices. A veces, dicha separación se realiza por cuestiones de rendimiento o por temas de permisos; por ejemplo, si diferentes desarrolladores o grupos mantienen el índice para diferentes colecciones de documentos. Otras veces se puede hacer debido al alto volumen; por ejemplo, un sitio de noticias puede considerar implementar un nuevo índice para cada nuevo mes y, luego, elegir en qué meses buscar.

Para estos casos, Lucene proporciona las clases MultiReader para buscar en múltiples índices. Con la clase MultiReader podemos realizar búsquedas en varios índices con los resultados combinados en un orden específico.

El uso de MultiReader es comparable al uso de IndexSearcher, excepto en que el resultado devuelto es una matriz de objetos IndexReaders para buscar, en lugar de un solo directorio. El siguiente ejemplo muestra cómo buscar en dos índices de forma separada.

El siguiente código lo podemos encontrar en el fichero LuceneMultiSearcher.java:



2.8 HERRAMIENTAS DE ADMINISTRACIÓN DE LUCENE

Para poder ver y realizar búsquedas en el propio índice existen un par de herramientas: Luke y Limo. Con ellas podemos monitorizar para ver si todo ha ido bien al crear el índice.

Luke (https://code.google.com/p/luke) es una herramienta de diagnóstico. Puede acceder a un índice existente y permite modificar y ver el contenido de varias formas:

• Buscar por número de documento o por término.

• Ver todos los documentos.

• Recuperar una lista de términos.

• Optimizar el índice.

Para utilizarla, solo necesitamos descargar el fichero jar y ejecutarlo. Al abrirlo, debemos indicarle cuál es la ruta del índice que hemos creado con Lucene. Una vez hecho esto, nos indicará todos los documentos que se han indexado, el número de términos, el ranking y otras estadísticas.

Limo (http://limo.sourceforge.net) es otra herramienta open source que debe su nombre a Lucene Index Monitor. Se trata de una aplicación web que proporciona información básica acerca de los índices utilizados por el motor de búsqueda de Lucene. Para instalar y utilizar Limo solo es necesario tener un contenedor de Servlets como Tomcat y copiar el fichero war al directorio /web-app. Bastaría con entrar en http://localhost:8080/limo-0.61/ para realizar búsquedas en nuestro índice.

2.9 HERRAMIENTAS DE BÚSQUEDA QUE USAN APACHE LUCENE

A continuación, analizamos algunos proyectos que usan Lucene a bajo nivel para realizar las búsquedas.

2.9.1 Krugle

La herramienta que ofrece Krugle.org permite la búsqueda de código en los repositorios y proyectos open source. Krugle utiliza Lucene para la indexación y búsqueda de código fuente de forma inteligente (figuras 2.9 y 2.10).

Krugle.org proporciona un motor de búsqueda de código fuente que cataloga continuamente más de 5000 proyectos de código abierto (incluidos Lucene y sus proyectos adyacentes bajo el paraguas de Apache Lucene), lo que le permite buscar dentro del código fuente.

https://opensearch.krugle.org/document/search/#query=lucene&qtype=basic

Figura 2.9 Búsqueda de código fuente en Krugle.

Una búsqueda de Lucene muestra coincidencias no solo del código fuente de Lucene, sino también de los muchos proyectos de código abierto que usan Lucene.

Figura 2.10 Proyectos y código fuente en Krugle.

Como vemos, se trata de un servicio que se puede integrar con otras herramientas a nivel empresarial, con el objetivo de crear un catálogo único y completo de código fuente, metadatos del proyecto e información de la organización asociada a los proyectos a nivel de desarrollo y análisis del código fuente.

Este análisis podría ayudar a los proyectos a aumentar la reutilización del código, reducir los costes de mantenimiento, mejorar el análisis de impacto y monitorizar la actividad de desarrollo en equipos grandes y distribuidos.

2.9.2 Google Dataset Search

Google Dataset Search (https://datasetsearch.research.google.com/) es un servicio que permite indexar cualquier contenido procedente de los sitios web que hayan seguido las directrices de Google a la hora de etiquetar y estructurar sus conjuntos de datos.

Entre las nuevas funcionalidades de Dataset Search se encuentra la posibilidad de filtrar los resultados en función del tipo de contenidos deseados (tablas, imágenes, texto) y de acceder al Dataset gratuitamente (figura 2.11).

Figura 2.11 Resultados de búsqueda en Google Dataset Search.

Además, un punto a favor de esta herramienta frente a la clásica búsqueda en Google es que los conjuntos de datos se muestran con una serie de etiquetas que permiten una rápida vista de pájaro de los contenidos disponibles en cada caso. Entre ellas figuran rango de fechas, fuente, descripción, área abarcada y última fecha de actualización.

Ücretsiz ön izlemeyi tamamladınız.

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
516 s. 394 illüstrasyon
ISBN:
9788426733368
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi:

Benzer kitaplar