Desarrollo de motores de búsqueda utilizando herramientas open source

Tekst
Loe katkendit
Märgi loetuks
Kuidas lugeda raamatut pärast ostmist
Šrift:Väiksem АаSuurem Aa

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.

Olete lõpetanud tasuta lõigu lugemise. Kas soovite edasi lugeda?