Noções básicas de pesquisa

Agora que você criou um índice no Elasticsearch e carregou alguns documentos nele, está pronto para implementar a pesquisa de texto completo.

Como funcionará a busca

Vamos fazer uma breve revisão de como a solução de busca funcionará no aplicativo tutorial. Com a aplicação Flask em execução, você pode acessar http://localhost:5001 para visualizar a página principal, que tem a seguinte aparência:

O código que renderiza esta página está implementado no arquivo app.py :

Este é um endpoint muito simples que renderiza um modelo HTML. Em aplicações Flask, os templates estão localizados em um subdiretório chamado `templates` , então você encontrará este e outros templates incluídos na aplicação lá.

Vamos analisar a implementação do campo de pesquisa no arquivo templates/index.html. Segue a parte relevante deste modelo:

Aqui você pode ver que este é um formulário HTML com um único campo do tipo text chamado query. O atributo method do formulário está definido como POST, o que indica ao navegador que este formulário deve ser enviado em uma solicitação POST. O atributo action é definido para o URL que corresponde ao endpoint handle_search do aplicativo Flask. Quando o formulário for submetido, a função handle_search() será executada.

A implementação atual de handle_search() é mostrada abaixo:

A função obtém o texto digitado pelo usuário no campo de texto do dicionário request.form do Flask e o armazena na variável local query . A função então renderiza o modelo index.html , mas desta vez ela passa alguns argumentos adicionais para que a página possa exibir os resultados da pesquisa. Os quatro argumentos que o modelo recebe são:

  • query: o texto da consulta inserido pelo usuário no formulário.
  • results: uma lista de resultados de pesquisa
  • from_: o índice de base zero do primeiro resultado
  • total: o número total de resultados

Como a funcionalidade de busca não está implementada, por enquanto os argumentos que são passados para a função render_template() indicam que nenhum resultado foi encontrado.

A tarefa agora é implementar uma consulta de texto completo e passar os resultados reais para que a página index.html possa exibi-los.

Os serviços Elasticsearch utilizam uma DSL (Linguagem de Domínio Específico) de consulta baseada no formato JSON para definir consultas.

O cliente Elasticsearch para Python tem um método search() que é usado para enviar uma consulta de pesquisa. Vamos adicionar um método auxiliar search() em search.py que usa este método:

Este método invoca o método search() do cliente Elasticsearch com o nome do índice. O argumento query_args captura todos os argumentos de palavra-chave fornecidos ao método e, em seguida, os passa para o método es.search() . Esses argumentos serão a forma como o chamador especificará o que procurar.

Consultas de correspondência

A DSL de consulta do Elasticsearch oferece muitas maneiras diferentes de consultar um índice. Ao consultar as subseções da documentação, você se familiarizará com os diferentes tipos de consultas possíveis. A tarefa muito comum de pesquisar texto é abordada na seção de consultas de texto completo .

Para a primeira implementação de pesquisa, vamos usar a consulta Match. Abaixo você pode ver um exemplo que utiliza essa consulta:

O exemplo acima é apresentado em um formato que se assemelha a uma requisição HTTP bruta. É útil estar familiarizado com esse formato, pois ele é amplamente utilizado na documentação do Elasticsearch e no Console da API do Elasticsearch. Felizmente, esse formato é muito fácil de traduzir em uma chamada usando a biblioteca cliente do Python. Abaixo você pode ver o código Python equivalente para o exemplo acima:

Ao converter exemplos do Console da API para Python, lembre-se de que as chaves de nível superior no corpo da consulta precisam ser convertidas em argumentos nomeados na chamada do Python. Os exemplos também não especificam um índice, que seria necessário ao fazer a chamada em Python.

Analisando a estrutura da consulta, você provavelmente consegue deduzir que tipo de pesquisa está sendo solicitada. A chamada solicita uma consulta match em um campo chamado name e o texto a ser pesquisado é search text here.

Esse estilo de consulta é relativamente fácil de incorporar nos aplicativos de tutorial. Abra o arquivo app.py e encontre o método handle_search() . Substitua a versão atual por esta nova:

A chamada para es.search() na segunda linha desta nova versão do endpoint invoca o método search() adicionado acima em search.py. que por sua vez chama o método search() do cliente Elasticsearch.

Você consegue descobrir o que a consulta vai fazer? Esta é uma consulta match semelhante ao exemplo acima. O campo que será pesquisado é name, que contém os títulos dos documentos no índice my_documents que você construiu na seção anterior. O texto a ser pesquisado é o que o usuário digitou no campo de pesquisa na página da web, que é armazenado na variável local query .

A parte da resposta da pesquisa que contém os resultados é response['hits']. Este é um objeto com algumas chaves, das quais duas são de interesse nesta implementação:

  • response['hits']['hits']: a lista de resultados da pesquisa.
  • response['hits']['total']: o número total de resultados disponíveis. O número de resultados é dado em uma subchave value , portanto, na prática, a expressão para obter o número total de resultados é results['hits']['total']['value']. Note que o número total de resultados pode ser uma aproximação quando houver um grande número de resultados. Consulte a documentação do corpo da resposta para obter detalhes.

A chamada para render_template() nesta nova versão do endpoint passa a lista de resultados no argumento de modelo results e o número total de resultados em total. O argumento query recebe a string de consulta como antes, e from_ ainda está codificado como 0, pois será implementado posteriormente quando a paginação for adicionada.

Com isso, o aplicativo passa a contar com uma primeira implementação de busca de texto completo. Volte ao seu navegador e acesse http://localhost:5001 para abrir o aplicativo. Se, por algum motivo, o aplicativo Flask não estiver em execução, inicie-o novamente antes de prosseguir. Digite um texto de pesquisa como policy ou work from home e você verá resultados relevantes. Abaixo você pode ver os resultados da busca por work from home:

O modelo index.html que você baixou com o aplicativo inicial inclui toda a lógica para exibir os resultados da pesquisa. Caso tenha curiosidade, aqui está a seção deste modelo que renderiza a lista de resultados:

A partir desse código, é interessante notar que os dados associados a um resultado retornado estão disponíveis sob a chave _source . Existe também um campo _id que contém o identificador único atribuído ao resultado.

Uma pontuação associada a cada resultado pode ser obtida a partir de _score. A pontuação fornece uma medida de relevância, sendo que pontuações mais altas indicam uma correspondência mais próxima com o texto da consulta. Por padrão, os resultados são retornados em ordem decrescente de pontuação. As pontuações no Elasticsearch são calculadas usando o algoritmo Okapi BM25 .

Se você tiver interesse em explorar os tópicos abordados nesta seção com mais detalhes, utilize os seguintes links:

Recuperando resultados individuais

Você deve ter notado que o modelo index.html renderiza o título de cada resultado da pesquisa como um link. O link aponta para o terceiro e último endpoint que veio implementado no aplicativo Flask inicial, chamado get_document. A implementação fornecida retorna um texto fixo "Documento não encontrado", portanto, é isso que você verá se clicar em qualquer um dos resultados ao usar o aplicativo.

Para renderizar corretamente os documentos individuais, vamos adicionar um método auxiliar retrieve_document() em search.py. usando o método get() do cliente Elasticsearch:

Aqui você pode ver como esses identificadores únicos atribuídos a cada documento são úteis, pois é isso que o aplicativo usa para se referir a documentos individuais.

Aqui está a implementação atual do endpoint get_document() :

Você pode ver que a URL associada a este endpoint inclui o documento id, e os links que são renderizados para cada resultado de pesquisa também têm o id incorporado nas respectivas URLs, então tudo o que falta é substituir esta implementação simplista por uma que recupere o documento e o renderize. Substitua o endpoint por esta versão atualizada:

Aqui, o método retrieve_document() do arquivo search.py é usado para obter o documento solicitado. O documento.html é então renderizado, com um título que vem do campo name e uma lista de parágrafos de content.

Tente executar mais algumas consultas e clicar nos resultados; agora você deverá conseguir visualizar todo o conteúdo.

Pesquisando em vários campos

Depois de usar o aplicativo por um tempo, você pode ter notado que muitas consultas não retornam resultados. Como você deve se lembrar, a pesquisa é atualmente implementada no campo name de cada documento, que é onde os títulos dos documentos são armazenados. Os documentos também têm campos summary e content , que contêm textos mais longos que também podem ser pesquisados, mas no momento estes são ignorados.

Nesta seção, você aprenderá sobre outra consulta comum de pesquisa de texto completo, a Multi-match, que solicita que uma pesquisa seja realizada em vários campos de um índice.

Segue um exemplo de consulta com múltiplas correspondências, retirado da documentação:

Vamos usar este exemplo como base para expandir o endpoint handle_search() para executar consultas de correspondência múltipla nos campos name, summary e content combinados. Aqui está o código do endpoint atualizado:

Com essa mudança, há muito mais texto para pesquisar, tanto que algumas consultas podem ter mais do que o máximo de 10 resultados retornados por padrão. No próximo capítulo, você aprenderá como lidar com longas listas de resultados por meio da paginação.

Anteriormente

Criar um índice

Próximo

Paginação

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)