Ahora que creaste un índice de Elasticsearch y cargado algunos documentos en él, estás listo para implementar la búsqueda en texto completo.
Cómo funcionará la búsqueda
Hagamos un repaso rápido de cómo funcionará la solución de búsqueda en la aplicación tutorial. Con la aplicación Flask en marcha, puedes ir a http://localhost:5001 para acceder a la página principal, que se ve así:

El código que renderiza esta página está implementado en el archivo app.py :
Este es un endpoint muy sencillo que genera una plantilla HTML. En Flask, las plantillas de aplicaciones se encuentran en un subdirectorio de plantillas , así que allí encontrarás esta y otras plantillas incluidas con la aplicación.
Veamos la implementación del campo de búsqueda en el archivo templates/index.html. Aquí está la parte relevante de esta plantilla:
Aquí puedes ver que se trata de un formulario HTML con un solo campo de tipo text llamado query. El atributo method del formulario está configurado como POST, lo que indica al navegador que envíe este formulario en una solicitud POST. El atributo action se establece en la URL que corresponde al extremo handle_search de la aplicación Flask. Cuando se envía el formulario, la función handle_search() se ejecutará.
La implementación actual de handle_search() se muestra a continuación:
La función obtiene el texto escrito por el usuario en el campo de texto del diccionario request.form de Flask y lo almacena en la variable local query . La función luego muestra la plantilla de index.html , pero de este tipo pasa algunos argumentos adicionales para que la página pueda mostrar los resultados de búsqueda. Los cuatro argumentos que recibe la plantilla son:
query: el texto de consulta introducido por el usuario en el formulario.results: una lista de resultados de búsquedafrom_: el índice basado en cero del primer resultadototal: el número total de resultados
Dado que la funcionalidad de búsqueda no está implementada, por ahora los argumentos que se pasan a la función render_template() indican que no se encontraron resultados.
La tarea ahora es implementar una consulta en texto completo y pasar resultados reales para que la página index.html pueda mostrarlos.
Los servicios de Elasticsearch emplean un Query DSL (Lenguaje Específico de Dominio) basado en el formato JSON para definir consultas.
El cliente Elasticsearch para Python tiene un método search() que se emplea para enviar una consulta de búsqueda. Vamos a agregar un método de ayuda search() en search.py que emplea este método:
Este método invoca el método search() del cliente Elasticsearch con el nombre del índice. El argumento query_args captura todos los argumentos de palabra clave proporcionados al método y luego los pasa al método es.search() . Estos argumentos van a ser la forma en que el llamante especifica qué buscar.
Consultas de coincidencia
El DSL de consulta Elasticsearch ofrece muchas formas diferentes de consultar un índice. Al revisar las subsecciones de la documentación te familiarizarás con los diferentes tipos de consultas posibles. La tarea muy común de buscar texto se cubre en la sección de consultas de texto completo .
Para la primera implementación de búsqueda, usemos la consulta Match. A continuación puedes ver un ejemplo que emplea esta consulta:
El ejemplo anterior se da en un formato que se asemeja a una solicitud HTTP en bruto. Es útil familiarizar con este formato, ya que se emplea ampliamente en la documentación de Elasticsearch y en la Consola de la API de Elasticsearch. Por suerte, este formato es muy fácil de traducir a una llamada usando la biblioteca cliente de Python. A continuación puedes ver el código equivalente en Python para el ejemplo anterior:
Al convertir ejemplos de API Console a Python, recuerda que las claves de primer nivel en el cuerpo de la consulta deben convertir en argumentos de palabra clave en la llamada de Python. Los ejemplos tampoco especifican un índice, que sería necesario al hacer la llamada a Python.
Al observar la estructura de la consulta probablemente puedas deducir qué tipo de búsqueda se está pidiendo. La llamada solicita una cotización match en un campo llamado name, y el texto a buscar es search text here.
Este tipo de consulta es bastante fácil de incorporar en las aplicaciones de tutorías. Abre app.py y encuentra el método handle_search() . Sustituye la versión actual por esta nueva:
La llamada a es.search() en la segunda línea de esta nueva versión del endpoint invoca el método search() añadido anteriormente en search.py, que a su vez llama al método search() del cliente Elasticsearch.
¿Puedes averiguar qué va a hacer la consulta? Esta es una consulta match similar al ejemplo anterior. El campo que se va a buscar es name, que contiene los títulos de los documentos en el índice de my_documents que creaste en la sección anterior. El texto a buscar es el que el usuario escribió en el campo de búsqueda del sitio web, que se almacena en la variable local query .
La parte de la respuesta de búsqueda que contiene los resultados es response['hits']. Este es un objeto con varias claves, de las cuales dos son de interés en esta implementación:
response['hits']['hits']: la lista de resultados de búsqueda.response['hits']['total']: el número total de resultados disponibles. El número de resultados se da en unavaluesubclave, por lo que en la práctica, la expresión para obtener el número total de resultados esresults['hits']['total']['value']. Observar que el número total de resultados puede ser una aproximación cuando hay un gran número de resultados. Consulta la documentación del cuerpo de respuesta para más detalles.
La llamada a render_template() en esta nueva versión del endpoint pasa la lista de resultados en el argumento de plantilla results y el número total de resultados en total. El argumento query recibe la cadena de consulta como antes, y from_ sigue codificada a 0, ya que se implementará más adelante cuando se agregue la paginación.
Y con esto, la aplicación tiene una primera implementación de búsqueda en texto completo. Vuelve a tu navegador sitio web y navega hasta http://localhost:5001 para abrir la aplicación. Si por cualquier motivo no tienes la aplicación Flask abierta, vuelve a iniciar la aplicación antes de hacerlo. Introduce un texto de búsqueda como policy o work from home y verás resultados relevantes. A continuación puedes ver los resultados al buscar work from home:

La plantilla de index.html que descargaste con la aplicación inicial incluye toda la lógica para renderizar los resultados de búsqueda. Si tienes curiosidad sobre esto, aquí tienes la sección de esta plantilla que muestra la lista de resultados:
De este código es interesante notar que los datos asociados a un resultado devuelto están disponibles bajo la clave _source . También hay un campo _id que contiene el identificador único asignado al resultado.
Se puede obtener un puntaje asociado a cada resultado de _score. El puntaje proporciona una medida de relevancia, con puntajes más altos indicando una coincidencia más cercana al texto de la consulta. Por defecto, los resultados se devuelven en orden según su puntaje, de mayor a menor. Los puntajes en Elasticsearch se calculan usando el algoritmo Okapi BM25 .
Si te interesa explorar los temas tratados en esta sección con más detalle, emplea los siguientes enlaces:
- Consulta de coincidencia
- Cuerpo de solicitud de API de búsqueda
- Cuerpo de respuesta de la API de búsqueda
- Serial de artículos prácticos sobre BM25
Obtención de resultados individuales
Quizá notaste que la plantilla de index.html muestra el título de cada resultado de búsqueda como un enlace. El enlace apunta al tercer y último punto final que se implementó en la aplicación inicial Flask, llamado get_document. La implementación que se proporciona devuelve un texto codificado en formato fijo "Documento no encontrado", así que esto es lo que verás si haces clic en cualquiera de los resultados mientras juegas con la aplicación.
Para renderizar correctamente documentos individuales, agreguemos un método de ayuda retrieve_document() en search.py, usando el método get() del cliente Elasticsearch:
Aquí puedes ver cómo estos identificadores únicos asignados a cada documento son útiles, ya que es lo que la aplicación puede usar para referir a documentos individuales.
Aquí está la implementación actual del punto final get_document() :
Puedes ver que la URL asociada a este endpoint incluye el documento id, y los enlaces que se muestran para cada resultado de búsqueda también tienen el id incorporado en las respectivas URLs, por lo que lo único que falta es reemplazar esta implementación simplista por una que recupere el documento y lo renderice. Sustituye el endpoint por esta versión actualizada:
Aquí se emplea el método retrieve_document() de search.py para obtener el documento aplicar. A continuación, se representa el document.html , con un título que proviene del campo name y una lista de párrafos de content.
Prueba a hacer más consultas y a hacer clic en resultados, lo que debería permitirte ver el contenido completo.
Búsqueda en múltiples campos
Luego de jugar un rato con la aplicación, puede que notaste que muchas consultas no demuestran resultados. Como recordarás, la búsqueda se implementa actualmente en el campo name de cada documento, que es donde se almacenan los títulos de los documentos. Los documentos también tienen campos summary y content , que tienen textos más largos que también suelen ser buscados, pero ahora mismo estos se ignoran.
En esta sección vas a aprender sobre otra consulta común de búsqueda en texto completo, la Multi-coincidencia, que aplicar que se realice una búsqueda en varios campos de un índice.
Aquí está el ejemplo de consulta multi-coincidencia de la documentación:
Usemos este ejemplo como base para expandir el extremo de handle_search() y así ejecutar consultas de múltiples coincidencias en los campos de name, summary y content combinados. Aquí está el código final actualizado:
Con este cambio, hay mucho más texto para buscar, tanto que algunas consultas pueden tener más de un máximo de 10 resultados que se devuelven por defecto. En el siguiente capítulo aprenderás cómo gestionar largas listas de resultados mediante la paginación.
Previamente
Crear un índicePróximo
Paginación