Noções básicas sobre a API Search

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

Introdução

Nesta lição, abordaremos os fundamentos do uso da API Search: indexar conteúdo e fazer consultas em um índice. Você aprenderá a:

  • Criar um índice de pesquisa.
  • Adicionar conteúdo a este índice por meio de um documento de índice.
  • Fazer consultas de pesquisa por texto completo simples nesses dados indexados.

Objetivos

Aprender as noções básicas do uso da API Search do App Engine.

Pré-requisitos

Índices

A API Search do App Engine opera por meio de um objeto Index. Esse objeto permite que você armazene dados por meio de um documento de índice, recupere documentos usando consultas de pesquisa, modifique e exclua documentos.

Cada índice tem um nome de índice e, opcionalmente, um namespace. O nome identifica exclusivamente o índice em um determinado namespace. Precisa ser uma string ASCII visível, imprimível, que não comece com !. Caracteres de espaço em branco são excluídos. É possível criar vários objetos Index, mas quaisquer dois objetos com o mesmo nome de índice no mesmo namespace fazem referência ao mesmo índice.

É possível usar namespaces e índices para organizar seus documentos. Para o aplicativo de pesquisa de produto de exemplo, todos os documentos do produto estão em um índice, com um outro índice contendo informações sobre a localização das lojas. Podemos filtrar uma consulta na categoria do produto se quisermos procurar, digamos, apenas livros.

No seu código, você cria um objeto Index especificando o nome do índice:

from google.appengine.api import search
index = search.Index(name='productsearch1')

,

index = search.Index(name='yourindex', namespace='yournamespace')

O índice de documento subjacente será criado no primeiro acesso, se ainda não houver, não é preciso criá-lo explicitamente.

É possível excluir documentos de um índice ou excluir todo o índice, conforme mostraremos na próxima aula: Um olhar mais aprofundado sobre a API Python Search.

Documentos

Os documentos armazenam o conteúdo pesquisável de um índice. Um documento é um contêiner para estruturar dados indexáveis. De um ponto de vista técnico, um objeto Document representa uma coleção de campos com identificação exclusiva, identificada por um código do documento. Campos são valores nomeados, digitados. Os documentos não têm kinds na forma como isso é entendido para as entidades do Datastore.

No nosso aplicativo de amostra, por exemplo, nossas categorias de produtos são livros e TVs HD. A loja tem uma seleção bastante limitada de produtos. Cada documento do produto no aplicativo de exemplo sempre inclui os seguintes campos principais, definidos por variáveis de classe de docs.Product:

  • CATEGORY, definido para books ou hd_televisions
  • PID código do produto
  • PRODUCT_NAME
  • DESCRIPTION
  • PRICE
  • AVG_RATING
  • UPDATED (data da última atualização)
Campos do documento do produto
Figura 1 : Campos do documento do produto.

Cada uma das categorias de livros e de televisores HD tem alguns campos adicionais próprios. No caso dos livros, os campos extras são:

  • title
  • author
  • publisher
  • pages
  • isbn

Já no caso dos televisores HD, são:

  • brand
  • tv_type
  • size

O próprio aplicativo impõe uma consistência semântica no aplicativo, para documentos de cada tipo de produto. Ou seja, todos os documentos do produto sempre incluirão os mesmos campos principais, todos os livros têm o mesmo conjunto de campos adicionais e assim por diante. No entanto, um índice de pesquisa não impõe nenhuma consistência esquemática de documento cruzado nos campos que são usados, portanto, não há um conceito explícito de se consultar especificamente por documentos "do produto".

Tipos de campo

Cada campo do documento possui um tipo de campo exclusivo. O tipo pode ser qualquer um da lista a seguir, que é definido na search do módulo Python:

  • TextField: uma string de texto simples.
  • HtmlField: texto formatado em HTML. Se a string for HTML, use esse tipo de campo, já que a API Search pode considerar a marcação ao criar snippets de resultado e na pontuação do documento.
  • AtomField: uma string tratada como um único token. Não haverá resultados em uma consulta que inclua apenas uma substring em vez do valor total do campo.
  • NumberField: um valor numérico (inteiro ou de ponto flutuante).
  • DateField: uma data sem componente de hora.
  • GeoField : uma localização geográfica indicada por um objeto GeoPoint, que especifica coordenadas de latitude e longitude.

Para campos de texto (TextField , HtmlField e AtomField), os valores devem ser strings Unicode.

Exemplo: como criar campos de documento do produto e como criar um documento

Para criar um objeto Document, crie uma lista dos campos dele e, se quiser, definir o código do documento. Depois, transmita essas informações para o Document constructor.

O aplicativo de exemplo usa os tipos de campo TextField, AtomField, NumberField e DateField para documentos do produto.

Como definir os campos do documento do produto

Os campos principais do produto (aqueles presentes em todos os documentos do produto) tem a seguinte aparência, em que assumimos que os argumentos de valor dos construtores abaixo estão definidos para os valores apropriados:

from google.appengine.api import search
...
fields = [
      search.TextField(name=docs.Product.PID, value=pid), # the product id
      # The 'updated' field is set to the current date.
      search.DateField(name=docs.Product.UPDATED,
                       value=datetime.datetime.now().date()),
      search.TextField(name=docs.Product.PRODUCT_NAME, value=name),
      search.TextField(name=docs.Product.DESCRIPTION, value=description),
      # The category names are atomic
      search.AtomField(name=docs.Product.CATEGORY, value=category),
      # The average rating starts at 0 for a new product.
      search.NumberField(name=docs.Product.AVG_RATING, value=0.0),
      search.NumberField(name=docs.Product.PRICE, value=price) ]

Observe que o campo de categoria é digitado como AtomField. Os campos Atom são úteis para coisas como categorias, em que correspondências exatas são requeridas; Campos de texto são melhores para strings como títulos ou descrições. Uma das nossas categorias de exemplo é hd televisions . Se pesquisarmos apenas por televisions, não obteremos uma correspondência, supondo que essa sequência não esteja contida em nenhum outro campo de produto. Mas, se pesquisarmos a string de campo completa, hd televisions, verificaremos uma correspondência no campo Categoria.

O aplicativo de exemplo também inclui campos específicos para categorias de produtos individuais. Estes também são adicionados à lista de campos, dependendo da categoria. Por exemplo, para a categoria de televisão, existem campos adicionais para size (um campo numérico), brand e tv_type (campos de texto). Livros contam com um conjunto diferente de campos.

Como criar documentos

Dada a lista de campos, podemos criar um objeto de documento. Para cada documento do produto, definiremos o código do documento como o código exclusivo predefinido desse produto:

d = search.Document(doc_id=product_id, fields=fields)

Este projeto oferece algumas vantagens para nós (como discutiremos na próxima aula), mas se não especificássemos o código do documento, um código seria gerado automaticamente quando o documento fosse adicionado a um índice .

Exemplo: como usar geopoints em documentos de localização de lojas

A API Search é compatível com o uso do Geosearch em documentos que incluem campos do tipo GeoField . Se seus documentos contiverem esses campos, você poderá consultar um índice para correspondências com base em comparações de distância

Um local é definido pela classe GeoPoint, que armazena as coordenadas de latitude e longitude. A latitude especifica a distância angular, em graus, ao norte ou ao sul do equador. A longitude especifica a distância angular, novamente em graus, a leste ou a oeste do meridiano principal. Por exemplo, a localização da Opera House, em Sydney, é definida pelo GeoPoint(-33.857, 151.215). Para armazenar um geoponto em um documento, é preciso adicionar um campo GeoField com um objeto GeoPoint definido como seu valor.

Veja como são construídos os campos para os documentos de localização de lojas no aplicativo de pesquisa do produto:

from google.appengine.api import search
...
geopoint = search.GeoPoint(latitude, longitude)
fields = [search.TextField(name=docs.Store.STORE_NAME, value=storename),
             search.TextField(name=docs.Store.STORE_ADDRESS, value=store_address),
             search.GeoField(name=docs.Store.STORE_LOCATION, value=geopoint)  ]

Documentos de indexação

Para consultar o conteúdo de um documento, é preciso adicionar o documento a um índice usando o método put() do objeto Index. A indexação permite que o documento seja pesquisado com a linguagem de consulta e as opções de consulta da API Search.

Você pode especificar seu próprio código de documento ao construir um documento. O código do documento deve ser uma string ASCII visível e imprimível, e não pode começar com o caractere !. Caracteres de espaço em branco são excluídos. Como veremos adiante, se você indexar um documento usando o código de um documento existente, esse documento existente será reindexado. Se o código de um documento não for especificado, um código numérico exclusivo será gerado automaticamente quando o documento for adicionado ao índice.

Os documentos podem ser adicionados um de cada vez. Também é possível adicionar uma lista de documentos em lote, o que é mais eficiente. Veja como criar um documento, receber uma lista de campos e adicioná-lo a um índice:

from google.appengine.api import search

# Here we do not specify a document ID, so one will be auto-generated on put.
d = search.Document(fields=fields)
try:
  add_result = search.Index(name=INDEX_NAME).put(d)
except search.Error:
  # ...

É preciso identificar e corrigir quaisquer exceções resultantes do put(), que serão do tipo search.Error.

Para especificar o código do documento, passe-o para o construtor Document, da seguinte forma:

d = search.Document(doc_id=doc_id, fields=fields)

É possível receber o código dos documentos que foram adicionados por meio das propriedades id da lista de objetos search.AddResult retornada da operação put():

doc_id = add_result[0].id

Consultas de pesquisa básica

Adicionar documentos a um índice torna o conteúdo do documento pesquisável. Torna-se possível então realizar consultas de pesquisa de texto completo nos documentos no índice.

Existem duas maneiras para enviar-se uma consulta de pesquisa. A mais simples é passar uma string de consulta para o método search() do objeto Index. Outra possibilidade é criar um objeto Query e transmiti-lo ao método search(). A construção de um objeto de consulta permite especificar opções de consulta, classificação e apresentação de resultados para a pesquisa.

Nesta lição, veremos como criar consultas simples usando as duas abordagens. Lembre-se de que algumas consultas de pesquisa não são totalmente compatíveis com o servidor da Web de desenvolvimento (em execução localmente), portanto, será necessário executá-las usando um aplicativo implantado.

Pesquisa usando uma string de consulta

Uma string de consulta pode ser qualquer string Unicode que possa ser analisada pela linguagem de consulta da API Search. Depois de criar uma string de consulta, passe-a para o método Index.search(). Exemplo:

from google.appengine.api import search

# a query string like this comes from the client
query = "stories"
try:
  index = search.Index(INDEX_NAME)
  search_results = index.search(query)
  for doc in search_results:
    # process doc ..
except search.Error:
  # ...

Pesquisar usando um objeto de consulta

Um objeto Query oferece mais controle sobre suas opções de consulta do que uma string de consulta. Neste exemplo, primeiro construímos um objeto QueryOptions. Seus argumentos especificam que a consulta deve retornar o número de resultados doc_limit. Se você verificar o código do aplicativo de pesquisa de produto, vai ver que há objetos QueryOption mais complexos. Vamos examiná-los na próxima aula: Um olhar mais aprofundado sobre a API Search do Python. Em seguida, criamos o objeto Query usando a string de consulta e o objeto QueryOptions. Passamos então o objeto Query para o método Index.search(), da mesma forma que fizemos anteriormente com a string de consulta.

from google.appengine.api import search

# a query string like this comes from the client
querystring = “stories”
try:
  index = search.Index(INDEX_NAME)
  search_query = search.Query(
      query_string=querystring,
      options=search.QueryOptions(
          limit=doc_limit))
  search_results = index.search(search_query)
except search.Error:
  # ...

Como processar os resultados da consulta

Depois de enviar uma consulta, os resultados da pesquisa correspondentes são retornados ao aplicativo em um objeto iterável SearchResults. Esse objeto inclui o número de resultados encontrados, os resultados reais retornados e um objeto de cursor de consulta opcional.

Os documentos retornados podem ser acessados por meio da iteração no objeto SearchResults. O número de resultados retornados é o comprimento da propriedade de results do objeto. A propriedade number_found é configurada para o número de ocorrências encontradas. A iteração no objeto retornado fornece os documentos retornados, que você pode processar como quiser.

try:
  search_results = index.search("stories")
  returned_count = len(search_results.results)
  number_found = search_results.number_found
  for doc in search_results:
    doc_id = doc.doc_id
    fields = doc.fields
    # etc.
except search.Error:
  # ...

Resumo e revisão

Nesta lição, aprendemos os fundamentos da criação de documentos indexados e de como consultar o conteúdo deles. Para testar seus conhecimentos no tema, tente recriar essas etapas você mesmo em seu próprio aplicativo simples:

  • Crie um objeto de Index.
  • Crie uma lista de campos de documentos usando, por exemplo, o tipo TextField. Em seguida, crie um objeto Document com essa lista de campos. Adicione o documento ao índice.
  • Pesquise o índice usando uma string de pesquisa com um termo presente em um dos seus valores de campo. O documento que você criou retornou como uma correspondência?

Na próxima lição, veremos mais de perto os índices da API Search.

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

Enviar comentários sobre…

Ambiente padrão do App Engine para Python 2