Consultas mais complexas da API Search

Amy Unruh, outubro de 2012
Relações com desenvolvedores do Google

Introdução

A aula anterior abordou os fundamentos da definição, envio e processamento de uma consulta de pesquisa. A API Search é compatível com consultas mais complexas, incluindo a especificação do ponto no índice em que a consulta deve começar, como os resultados devem ser classificados e formatados e quais informações sobre os documentos devem ser retornadas da consulta. A API Search também é compatível com o Geosearch, em que as consultas são feitas com base na localização.

Nesta lição, examinaremos detalhadamente alguns desses recursos. Você aprenderá sobre os seguintes conceitos:

  • Definição e processamento de resultados de consultas complexas
  • Controle dos campos do documento que são retornados em uma consulta
  • Uso de compensações e limites para controlar onde uma consulta é iniciada e quantos resultados são retornados
  • Criação e utilização de consultas baseadas em localização (Geosearch)

Para saber mais detalhes sobre os recursos descritos nesta lição e também sobre alguns recursos adicionais que não serão abordados aqui, consulte a documentação da API Search.

Objetivos

Saiba como realizar consultas de pesquisa complexas com a API Search

Pré-requisitos

Ter participado da aula anterior a esta, Introdução à API Python Search

É preciso ter também o seguinte:

Opções de consulta

O construtor da classe Query aceita um objeto QueryOptions opcional como um argumento. Assim, é possível configurar uma grande variedade de opções:

search_query = search.Query(
    query_string=query.strip(),
    options=search.QueryOptions(...)
    )

Pense em uma das configurações de QueryOptions usadas no app de pesquisa de produto de exemplo:

search_query = search.Query(
    query_string=query.strip(),
    options=search.QueryOptions(
        limit=doc_limit,
        offset=offsetval,
        sort_options=sortopts,
        snippeted_fields=[docs.Product.DESCRIPTION],
        returned_expressions=[search.FieldExpression(name='adjusted_price',
            expression='max(price, 14.99)')],
        returned_fields = [docs.Product.PID, docs.Product.DESCRIPTION,
          docs.Product.CATEGORY, docs.Product.AVG_RATING,
          docs.Product.PRICE, docs.Product.PRODUCT_NAME]
        ))

Isso especifica um desvio (onde iniciar a consulta) e um limite (o número máximo de resultados a serem retornados), algumas opções de classificação (discutidas na próxima lição), uma lista de campos em formato de snippets, uma lista de expressões retornadas (campos computados) e uma lista de campos retornados. Vejamos o que cada uma dessas opções faz.

Limites, cursores e desvios de consultas

Para controlar o número de resultados retornados por uma consulta, use o parâmetro limit do construtor QueryOptions. O app de pesquisa de produto de exemplo usa limit para retornar no máximo três resultados por página.

O exemplo acima também mostra o uso do parâmetro offset. O desvio especifica o número de documentos correspondentes a serem ignorados antes de começar a retornar os resultados:

search.QueryOptions(
    limit=doc_limit,
    offset=offsetval,
    ...)

Um uso comum para os parâmetros offset e limit é paginar os resultados da consulta. Para implementar a paginação, você precisa saber o número total de correspondências que a consulta encontrou e quantas foram retornadas até o momento. Você consegue essa informação do objeto SearchResults retornado:

number_found = search_results.number_found
returned_count = len(search_results.results)

A API Search também é compatível com o uso de cursores de consulta. Os cursores são outra maneira de indicar o ponto a partir do qual iniciar uma consulta, permitindo continuar uma pesquisa a partir do final do conjunto de resultados anterior. Usar um cursor é geralmente mais eficiente do que usar desvios. No entanto, a API Search atualmente não suporta um "cursor invertido" como o faz a API do Datastore, dificultando a implementação de paginação invertida. Por esse motivo, o aplicativo de exemplo usa desvios em vez de cursores para paginar os resultados das consultas. Você pode encontrar um exemplo de uso de cursores aqui.

Snippeting

É possível retornar uma parte abreviada de um campo, em vez do conteúdo completo, usando os campos em formato de snippets. No snippet retornado, estará incluído o fragmento do campo em que a correspondência ocorreu, com os termos de pesquisa correspondentes destacados em negrito. No aplicativo de pesquisa de produtos (com dados padrão), uma pesquisa na consulta stories retorna três correspondências, nos campos de description dos documentos. Como solicitamos que essa description fosse em formato de snippet, as expressões de snippet nos resultados têm a palavra "stories" realçada.

Campos em formato de snippets, com termos correspondentes destacados
Figura 1 : Campos em formato de snippets com termos correspondentes destacados.

Você especifica o snippeting que deve ocorrer, fornecendo um iterável de nomes de campos a transformar em snippet. O construtor QueryOptions acima solicita o snippeting do campo DESCRIPTION:

search.QueryOptions(
  snippeted_fields=[docs.Product.DESCRIPTION],
  ...)

Em seguida, ao processar os resultados da consulta, você acessa os snippets gerados por meio da propriedade de expressions de um documento retornado:

for doc in search_results:
  ...
  for expr in doc.expressions:  # iterate over the computed fields
    if expr.name == docs.Product.DESCRIPTION:
      description_snippet = expr.value
      break
  # ... do something with the document ...

A propriedade expressions contém uma lista de campos calculados que são os resultados de expressões solicitadas na consulta. O código acima pega o snippet gerado no campo DESCRIPTION, em que doc é um documento pontuado. Documentos pontuados são retornados de uma pesquisa. Além do conteúdo do documento, eles incluem a pontuação do documento, os campos computados (discutidos abaixo) e outras informações.

Expressões retornadas e funções de expressão

Com a opção de consulta returned_expression, você define campos calculados com base nos campos do documento, que serão retornados como parte de um documento pontuado nos resultados da pesquisa.

Suponha que você queira calcular e exibir um preço para cada produto que inclua um tributo sobre vendas de 8%. Você cria uma expressão de campo com o nome adjusted_price, que tem como valor a string price * 1.08:

search.QueryOptions(
    returned_expressions=[search.FieldExpression(name='adjusted_price',
        expression='price * 1.08')],
    ...)

Essa expressão informa à API Search para retornar, como o valor de adjusted_price, o valor do campo de price multiplicado por 1,08. A API Search fornece diversas funções de expressão incorporadas que podem ser usadas em tais expressões. Por exemplo, é possível definir expressões como 'max(price, 9.99)'.

Depois de incluir uma lista returned_expression no seu objeto QueryOptions, é possível acessar esse campo calculado nos documentos retornados da consulta de pesquisa, novamente por meio da propriedade expressions:

for doc in search_results:
  ...
  for expr in doc.expressions:  # iterate over the computed fields
    if expr.name == docs.Product.DESCRIPTION:  # get the description snippet
      description_snippet = expr.value
    elif expr.name == 'adjusted_price':  # get the adjusted price
      price = expr.value
  # ... do something with the document ...

Campos retornados

O construtor QueryOptions também aceita um parâmetro returned_fields, que pode ser usado para tornar suas consultas mais eficientes ao solicitar apenas os campos de documentos específicos que você pretende usar. Por exemplo, o objeto QueryOptions, mostrado anteriormente, solicita todos os campos "principais" do produto, exceto a última atualização da data que decidimos não mostrar em nosso resumo de resultados. Também não é solicitado nenhum dos campos específicos da categoria, como publisher para documentos de book ou tv_type para documentos de hd_television:

search.QueryOptions(
    returned_fields = [docs.Product.PID, docs.Product.DESCRIPTION,
        docs.Product.CATEGORY, docs.Product.AVG_RATING,
        docs.Product.PRICE, docs.Product.PRODUCT_NAME]
    ...)

O argumento returned_fields deve ser um iterável nos nomes dos campos a serem retornados nos resultados de pesquisa. Nos documentos retornados nos resultados da pesquisa, estarão incluídos apenas os campos especificados, mesmo que os documentos indexados possam incluir outros campos.

Consultas baseadas em localização (Geosearch)

O suporte da API Search para Geosearch permite a realização de consultas baseadas em localização. É possível, por exemplo, encontrar lojas ou restaurantes próximos ou atualizações de stream de atividades nas proximidades.

Para executar uma consulta baseada em localização, você precisa de três informações:

  • Um local, com coordenadas de latitude e longitude, para calcular distâncias.
  • O raio da pesquisa, por exemplo, 45 quilômetros.
  • O conjunto de pontos em que serão medidas as distâncias.

Os dois primeiros itens são geralmente fornecidos pelo usuário. O último vem dos próprios documentos indexados: em nosso aplicativo de pesquisa de produto de exemplo, consiste das localizações de nossas lojas, obtidas dos documentos com os locais das lojas que criamos na aula anterior de Introdução.

Para procurar locais de loja próximos ao usuário, o aplicativo de exemplo obtém a localização do usuário por meio do navegador e o usuário insere a distância dentro da qual pesquisar. A distância é convertida em metros, a unidade de distância usada pela API Search. Suponha que a localização do usuário seja (-33.857, 151.215) e que ele especifique um raio de pesquisa de 45 quilômetros. O aplicativo criaria uma string de consulta como

"distance(store_location, geopoint(-33.857, 151.215)) < 45000"

e passaria para o método Index.search:

from google.appengine.api import search
...
    # a query string like this comes from the client
    query = "distance(store_location, geopoint(-33.857, 151.215)) &lt; 45000"
    try:
      index = search.Index(config.STORE_INDEX_NAME)
      search_results = index.search(query)
     for doc in search_results:
        # process doc ...
    except search.Error:
      # ...

Resumo e revisão

Nesta lição, aprendemos como especificar uma consulta de pesquisa usando um objeto QueryOptions e observamos algumas propriedades QueryOptions úteis: limit e offset, snippeted_fields, returned_expression e returned_fields. Também mostramos como criar uma consulta Geosearch.

sort_options é uma propriedade QueryOptions importante que tem muitos recursos. Por isso, ela terá uma aula exclusiva e será discutida em seguida. Para outras opções não abordadas nesta aula, consulte a documentação de QueryOptions.

Para testar seus conhecimentos, tente praticar com algumas das propriedades QueryOptions apresentadas aqui. Por exemplo, altere o DOC_LIMIT no arquivo config.py para um valor maior. Este é o valor passado como o argumento QueryOptions limit.

Tente praticar com o recurso returned_expressions. O recurso returned_expressions deve ser definido da seguinte forma no _buildQuery():

search.FieldExpression(name='adjusted_price',
    expression='price * 1.08')

Procure pelas linhas em handlers.py, na classe ProductSearchHandler, que mostram

# uncomment to use 'adjusted price', which should be
# defined in returned_expressions in _buildQuery() below, as the
# displayed price.

Remova os comentários nas linhas abaixo delas:

# elif expr.name == 'adjusted_price':
  # price = expr.value

Quando você reimplanta o aplicativo, deve ver o adjusted_price exibido nos resultados da pesquisa, em vez do preço real. Ou seja, o preço exibido incluirá o tributo sobre as vendas. O link Visualizar detalhes do produto nos resultados da pesquisa ainda mostrará o preço real. O campo adjusted_price será preenchido apenas para um aplicativo implantado.

Na próxima aula, você aprenderá a classificar os resultados de uma pesquisa por consulta na ordem que quiser.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Python no App Engine