API Search para serviços agrupados legados

Na API Search, há um modelo para a indexação de documentos contendo dados estruturados. É possível pesquisar um índice, organizar e apresentar os resultados da pesquisa. A API é compatível com correspondência de texto completa em campos de string. Documentos e índices são salvos em um armazenamento permanente separado e otimizado para operações de pesquisa. Qualquer número de documentos pode ser indexado na API Search. O App Engine Datastore pode ser mais apropriado para aplicativos que precisam recuperar grupos de resultados muito grandes.

Visão geral

A API Search é baseada em quatro conceitos principais: documentos, índices, consultas e resultados.

Documentos

Um documento é um objeto com um código exclusivo e uma lista de campos contendo dados do usuário. Cada campo tem um nome e um tipo. Existem vários tipos de campos, identificados pelos tipos de valores que contêm:

  • Campo atômico: uma string de caracteres indivisível.
  • Campo de texto: uma string de texto simples que pode ser pesquisada por palavra.
  • Campo HTML: uma string de caracteres que contém tags de marcação HTML. Somente o texto fora das tags de marcação pode ser pesquisado.
  • Campo numérico: um número de ponto flutuante.
  • Campo de data: um objeto de data com ano/mês/dia e hora opcional.
  • Campo de Geopoint: um objeto de dados com coordenadas de latitude e longitude.

O tamanho máximo de um documento é 1 MB.

Índices

Um índice armazena documentos para recuperação. É possível recuperar um documento pelo código, uma série de documentos com códigos consecutivos ou todos os documentos em um índice. Também é possível pesquisar um índice para recuperar documentos que atendam a determinados critérios nos campos e valores correspondentes, especificados como uma string de consulta. Também é possível gerenciar grupos de documentos colocando-os em índices separados.

Não há limite para a quantidade de documentos em um índice ou o número de índices em uso. O tamanho total de todos os documentos em um único índice é limitado a 10 GB por padrão, mas pode ser aumentado para até 200 GB enviando uma solicitação da página App Engine Search do console do Google Cloud.

Queries

A fim de pesquisar um índice, você constrói uma consulta contendo uma string de consulta e talvez opções adicionais. Na string de consulta, são especificadas as condições para os valores de um ou mais campos do documento. Ao pesquisar um índice, você recupera apenas os documentos com os campos que atendem à consulta.

A consulta mais simples, às vezes chamada de "pesquisa global", é uma string que contém apenas valores de campo. Na pesquisa a seguir, uma string é usada para localizar documentos contendo as palavras "rose" e "water":

def simple_search(index):
    index.search('rose water')

A string a seguir é usada para pesquisar documentos com campos de data que contenham "4 de julho de 1776" ou campos de texto que incluam a string "1776-07-04":

def search_date(index):
    index.search('1776-07-04')

Uma string de consulta também pode ser mais específica. Ela pode conter um ou mais termos, cada um nomeando um campo e uma restrição sobre o valor dele. A forma exata de um termo depende do tipo de campo. Por exemplo, supondo que exista um campo de texto chamado "Produto" e um campo numérico chamado "Preço", aqui está uma string de consulta com dois termos:

def search_terms(index):
    # search for documents with pianos that cost less than $5000
    index.search("product = piano AND price < 5000")

As opções de consulta, como o nome indica, não são necessárias. Com elas, é possível usar diversos recursos:

  • Controlar quantos documentos são retornados nos resultados da pesquisa.
  • Especificar quais campos de documento incluir nos resultados. O padrão é incluir todos os campos do documento original. É possível definir que os resultados incluam apenas um subgrupo de campos, sem que o documento original seja afetado.
  • Classificar os resultados.
  • Criar "campos calculados" para documentos usando FieldExpressions e campos de texto abreviados usando snippets.
  • Dar suporte à paginação por meio dos resultados da pesquisa ao retornar apenas uma parte dos documentos correspondentes em cada consulta, usando deslocamentos e cursores.

Resultados da pesquisa

Uma chamada para search() só pode retornar um número limitado de documentos correspondentes. A pesquisa pode encontrar mais documentos do que podem ser retornados em uma única chamada. Cada chamada de pesquisa retorna uma instância da classe SearchResults, que contém informações sobre quantos documentos foram encontrados e quantos foram retornados, além da lista de documentos retornados. Repita a mesma pesquisa usando cursores ou deslocamentos para recuperar o conjunto completo de documentos correspondentes.

Material de treinamento adicional

Além desta documentação, leia a aula de treinamento em duas partes sobre a API Search na Google Developer's Academy. A aula inclui um aplicativo Python de amostra.

Documentos e campos

A classe Document representa documentos. Cada documento tem um identificador e uma lista de campos.

Identificador do documento

Todo documento em um índice precisa ter um identificador exclusivo ou doc_id. O identificador pode ser usado para recuperar um documento de um índice sem realizar uma pesquisa. Por padrão, a API Search gera automaticamente um doc_id quando um documento é criado. É possível definir o doc_id ao criar um documento. O doc_id pode conter apenas caracteres ASCII visíveis e imprimíveis (códigos ASCII 33 a 126 inclusos) e não pode ter mais de 500 caracteres. O identificador de documento não pode começar com um ponto de exclamação ('!'), e não pode começar e terminar com sublinhados duplos ("__").

É conveniente criar identificadores de documentos exclusivos, legíveis e significativos, mas não é possível incluir o doc_id em uma pesquisa. Considere esta situação: você tem um índice com documentos que representam peças, usando o número de série da peça como doc_id. Será muito eficiente recuperar o documento de uma peça única, mas será impossível pesquisar por uma faixa de vários números de série junto com outros valores de campo, como data de compra. Armazenar o número de série em um campo atômico resolve o problema.

Campos do documento

Um documento contém campos com um nome, um tipo e um único valor desse tipo. Dois ou mais campos podem ter o mesmo nome, mas tipos diferentes. Por exemplo, é possível definir dois campos com o nome "idade": um com tipo texto (o valor "vinte e dois") e outro com o tipo número (valor 22).

Nomes de campos

Os nomes dos campos diferenciam maiúsculas de minúsculas e só podem conter caracteres ASCII. Eles precisam começar com uma letra e podem conter letras, números ou sublinhados. O nome do campo não pode ter mais de 500 caracteres.

Campos com vários valores

Um campo pode conter apenas um valor, que precisa corresponder ao tipo do campo. Os nomes dos campos não precisam ser exclusivos. Um documento pode ter vários campos com o mesmo nome e o mesmo tipo. Essa é uma maneira de representar um campo com vários valores. No entanto, os campos de data e número com o mesmo nome não podem ser repetidos. Um documento também pode conter vários campos com o mesmo nome e diferentes tipos.

Tipos de campo

Existem três tipos de campos para armazenar strings de caracteres. Eles são conhecidos conjuntamente como campos de string:

  • Campo de texto: uma string com comprimento máximo de 1024**2 caracteres.
  • Campo HTML: uma string formatada em HTML com comprimento máximo de 1024**2 caracteres.
  • Campo atômico: uma string com comprimento máximo de 500 caracteres.

Existem também três tipos de campos para armazenar dados não-textuais:

  • Campo numérico: um ponto flutuante de dupla precisão entre -2.147.483.647 e 2.147.483.647.
  • Campo de data: um datetime.date ou datetime.datetime.
  • Campo de Geopoint: um ponto na Terra descrito em coordenadas de latitude e longitude.

Os tipos de campo são especificados pelas classes TextField, HtmlField, AtomField, NumberField, DateField e GeoField.

Tratamento especial de campos de string e data

Quando um documento com campos de data, texto ou HTML é adicionado a um índice, ocorre um tratamento especial. É útil entender o que está acontecendo nos bastidores para usar a API Search de forma eficaz.

Tokenização de campos de string

Quando um campo de HTML ou texto é indexado, o conteúdo é tokenizado. A string é dividida em tokens onde houver espaços em branco ou caracteres especiais, como sinais de pontuação, barra invertida etc. O índice inclui uma entrada para cada token. Isso permite pesquisar palavras-chave e frases que compõem apenas parte do valor de um campo. Por exemplo, em uma pesquisa por "noite", um documento com um campo de texto contendo a string "era uma noite escura e tormentosa" será retornado. Em uma pesquisa por "tempo", um documento com um campo de texto contendo a string "isto é um sistema em tempo real" será retornado.

Em campos HTML, o texto dentro das tags de marcação não é tokenizado. Portanto, um documento com um campo HTML que contém it was a <strong>dark</strong> night será retornado para "night", mas não para "strong". Para pesquisar um texto de marcação, armazene-o em um campo de texto.

Os campos atômicos não são tokenizados. Um documento com um campo atômico que contenha o valor "tempo ruim" só será retornado em uma pesquisa para toda a string "tempo ruim". Ele não será retornado em uma pesquisa pelos termos "tempo" ou "ruim" separados.

Regras de tokenização
  • As palavras não são divididas em tokens pelos caracteres e comercial (&) e sublinhado (_).

  • As palavras são sempre divididas em tokens pelos seguintes caracteres de espaço em branco: espaço, retorno de carro, avanço de linha, tabulação horizontal, tabulação vertical, avanço de formulário e NULL.

  • As palavras são divididas em tokens pelos caracteres a seguir, que são tratados como sinais de pontuação:

    !"%()
    *,-|/
    []]^`
    :=>?@
    {}~$
  • Geralmente, as palavras são divididas em tokens pelos caracteres a seguir, mas eles podem ser manipulados de forma diferente dependendo do contexto em que aparecem:

    Caractere Regra
    < Em um campo HTML, o sinal "menor que" indica o início de uma tag HTML, que é ignorada.
    + Uma string de um ou mais sinais de adição é tratada como parte da palavra se aparecer no final dela (C++).
    # O sinal de jogo da velha é tratado como parte da palavra se for precedido de a, b, c, d, e, f, g, j ou x (a# - g# são notas musicais, j# e x# são linguagens de programação e c# é as duas coisas). Se um termo é precedido por '#' (#google), ele é tratado como uma hashtag, e o sinal de jogo da velha se torna parte da palavra.
    ' O apóstrofo é uma letra se preceder a letra "s" seguida de uma quebra de palavra, como em "John's hat".
    . Se um ponto decimal aparecer entre dígitos, ele faz parte de um número, ou seja, é o separador de decimais. Ele também pode ser parte de uma palavra se usado em uma sigla (A.B.C).
    - O traço é parte de uma palavra se usado em uma sigla (I-B-M).
  • As palavras são divididas em tokens pelos outros caracteres de 7 bits que não sejam letras e dígitos ("A-Z", "a-z", "0-9"). Eles são tratados como sinais de pontuação.

  • Todo o resto é analisado como um caractere UTF-8.

Siglas

A tokenização tem regras especiais para reconhecer as siglas. Por exemplo, strings como "I.B.M.", "a-b-c" ou "C.I.A". Uma sigla é uma string de caracteres alfabéticos individuais com o mesmo caractere separador entre todos eles. Os separadores válidos são o ponto, o traço ou qualquer número de espaços. O caractere separador é removido da string quando a sigla é tokenizada. Assim, as strings de exemplo mencionadas acima tornam-se os tokens "ibm", "abc" e "cia". O texto original permanece no campo do documento.

Ao lidar com siglas, observe que:

  • Uma sigla não pode conter mais de 21 letras. Uma string de sigla válida com mais de 21 letras será dividida em uma série de siglas, cada uma com até 21 letras.
  • Se as letras de uma sigla forem separadas por espaços, todas precisarão ter a mesma capitalização. As siglas formadas com pontos e traços podem usar letras maiúsculas e minúsculas.
  • Ao pesquisar uma sigla, insira a forma canônica dela (a string sem separadores) ou a sigla pontuada com o traço ou o ponto (mas não ambos) entre as letras. Portanto, o texto "IBM" pode ser recuperado com qualquer um dos termos de pesquisa: "I-B-M", "I.B.M" ou "IBM".

Precisão do campo de data

Ao criar um campo de data em um documento, você define seu valor como datetime.date ou datetime.datetime. Observe que apenas objetos de data e hora "naive" do Python podem ser usados. Os objetos "conscientes" não são permitidos. . Para fins de indexação e pesquisa do campo de data, qualquer componente de horário é ignorado e a data é convertida no número de dias desde 1/1/1970 UTC. Isso significa que, mesmo que um campo de data possa conter um valor de hora preciso, uma consulta de data só pode especificar um valor de campo de data no formulário yyyy-mm-dd. Também significa que a ordem de classificação dos campos de data com a mesma data não está bem definida.

Outras propriedades do documento

A classificação de um documento é um número inteiro positivo que determina a ordem padrão dos documentos retornados de uma pesquisa. Por padrão, ela é definida no momento em que o documento é criado, pelo número de segundos desde 1º de janeiro de 2011. É possível definir a classificação explicitamente ao criar um documento. Não é uma boa ideia atribuir a mesma classificação a muitos documentos. Nunca defina a mesma classificação para mais de 10.000 documentos. Ao especificar opções de classificação, é possível usar a classificação como uma chave. Quando a classificação é usada em uma expressão de classificação ou de campo, ela é mencionada como _rank.

A propriedade "language" especifica o idioma em que os campos são codificados.

Consulte a página de referência da classe Document para mais detalhes sobre esses atributos.

Como vincular de um documento a outros recursos

É possível usar o doc_id e outros campos de um documento como links para outros recursos do aplicativo. Por exemplo, se você usa o Blobstore, associe o documento a um blob específico. Basta configurar doc_id ou o valor de um campo atômico como BlobKey dos dados.

Criar documentos

No exemplo de código a seguir, mostramos como criar um objeto de documento. O construtor "Document" é chamado com o argumento "fields" definido como uma lista de objetos de campo. Cada objeto na lista é criado e inicializado usando a função construtora da classe do campo. Observe o uso do construtor GeoPoint e da classe datetime do Python para criar os tipos apropriados de valores de campo.

def create_document():
    document = search.Document(
        # Setting the doc_id is optional. If omitted, the search service will
        # create an identifier.
        doc_id='PA6-5000',
        fields=[
            search.TextField(name='customer', value='Joe Jackson'),
            search.HtmlField(
                name='comment', value='this is <em>marked up</em> text'),
            search.NumberField(name='number_of_visits', value=7),
            search.DateField(name='last_visit', value=datetime.now()),
            search.DateField(
                name='birthday', value=datetime(year=1960, month=6, day=19)),
            search.GeoField(
                name='home_location', value=search.GeoPoint(37.619, -122.37))
        ])
    return document

Como trabalhar com um índice

Como colocar documentos em um índice

Ao colocar um documento em um índice, ele é copiado para o armazenamento permanente, e cada um dos campos é indexado de acordo com nome, tipo e doc_id.

Veja a seguir um exemplo de código para acessar um índice e colocar um documento nele.

def add_document_to_index(document):
    index = search.Index('products')
    index.put(document)
É possível transmitir até 200 documentos por vez para o método put(). Fazer isso em lote é mais eficiente do que adicionar documentos um de cada vez.

Quando você coloca um documento em um índice e este já contém um documento com o mesmo doc_id, o novo documento substitui o anterior. Nenhum aviso é exibido. Chame Index.get(id) antes de criar ou adicionar um documento a um índice para verificar se determinado doc_id já existe.

O método put retorna uma lista de PutResults, um para cada documento transmitido como um argumento. Se você não especificou doc_id, pode examinar o atributo id do resultado para descobrir o doc_id que foi gerado:

def add_document_and_get_doc_id(documents):
    index = search.Index('products')
    results = index.put(documents)
    document_ids = [document.id for document in results]
    return document_ids

Observe que criar uma instância da classe Index não garante que um índice permanente realmente exista. O índice permanente é criado na primeira vez em que um documento é adicionado a ele usando o método put. Se você quiser verificar se um índice realmente existe antes de começar a usá-lo, use a função search.get_indexes() .

Atualizar documentos

Um documento não pode ser alterado depois de adicionado a um índice. Não é possível adicionar ou remover campos nem alterar o valor de um campo. No entanto, é possível substituir o documento por outro que tenha o mesmo doc_id.

Como recuperar documentos por doc_id

Há duas maneiras de recuperar documentos de um índice usando identificadores de documentos:
  • Use Index.get() para buscar um único documento pelo doc_id.
  • Use Index.get_range() para recuperar um grupo de documentos consecutivos classificados por doc_id.

Cada chamada é demonstrada no exemplo abaixo.

def get_document_by_id():
    index = search.Index('products')

    # Get a single document by ID.
    document = index.get("AZ125")

    # Get a range of documents starting with a given ID.
    documents = index.get_range(start_id="AZ125", limit=100)

    return document, documents

Pesquisar documentos pelo conteúdo

Para recuperar documentos de um índice, crie uma string de consulta e chame o Index.search(). É possível transmitir a string de consulta diretamente como argumento ou incluí-la em um objeto Consulta transmitido como argumento. Por padrão, search() retorna documentos correspondentes classificados em ordem decrescente. Para controlar quantos documentos são retornados, como eles são classificados ou adicionar campos calculados aos resultados, você precisa usar um objeto Query, que contém uma string de consulta e também pode especificar outras opções de pesquisa e classificação.

def query_index():
    index = search.Index('products')
    query_string = 'product: piano AND price < 5000'

    results = index.search(query_string)

    for scored_document in results:
        print(scored_document)

Como excluir um índice

Cada índice consiste em documentos indexados e um esquema de índice. Para excluir um índice, exclua todos os documentos inclusos nele e também o esquema dele.

Você pode excluir documentos em um índice especificando o doc_id de um ou mais documentos que queira excluir para o método Index.delete(). Faça a exclusão em lotes para melhorar a eficiência. É possível transmitir até 200 IDs de documentos por vez para o método delete().

def delete_index(index):
    # index.get_range by returns up to 100 documents at a time, so we must
    # loop until we've deleted all items.
    while True:
        # Use ids_only to get the list of document IDs in the index without
        # the overhead of getting the entire document.
        document_ids = [
            document.doc_id
            for document
            in index.get_range(ids_only=True)]

        # If no IDs were returned, we've deleted everything.
        if not document_ids:
            break

        # Delete the documents for the given IDs
        index.delete(document_ids)

    # delete the index schema
    index.delete_schema()
É possível transmitir até 200 documentos por vez para o método delete(). A exclusão em lote é mais eficiente do que o processamento individual.

Consistência eventual

Quando você manipula um documento em um índice usando put, update ou delete, a alteração é propagada por vários data centers. Normalmente esse processo é rápido, mas o tempo pode variar. Uma consistência eventual (link em inglês) é garantida pela API Search. Isso significa que, em alguns casos, uma pesquisa ou a recuperação de documentos pode retornar resultados que não refletem as alterações mais recentes.

Como determinar o tamanho de um índice

Um índice armazena documentos para recuperação. É possível recuperar um documento pelo código, uma série de documentos com códigos consecutivos ou todos os documentos em um índice. Também é possível pesquisar um índice para recuperar documentos que atendam a determinados critérios nos campos e valores correspondentes, especificados como uma string de consulta. Também é possível gerenciar grupos de documentos colocando-os em índices separados. Não há limite para a quantidade de documentos em um índice nem para o número de índices em uso. O tamanho total de todos os documentos em um único índice é limitado a 10 GB por padrão, mas pode ser aumentado para até 200 GB por meio do envio de uma solicitação da página App Engine Search do Console do Google Cloud. A propriedade de índice storage_limit é o tamanho máximo permitido de um índice.

A propriedade de índice storage_usage é uma estimativa do espaço de armazenamento usado por um índice. Esse número é uma estimativa porque o sistema de monitoramento de índice não é executado continuamente. O uso real é computado periodicamente. O storage_usage é ajustado entre pontos de amostragem contabilizando-se as adições de documentos, mas não as exclusões.

Como realizar operações assíncronas

Use chamadas assíncronas para executar várias operações sem bloqueio e recupere todos os resultados ao mesmo tempo com apenas um bloqueio. Por exemplo, o seguinte código executa várias pesquisas de maneira assíncrona:

def async_query(index):
    futures = [index.search_async('foo'), index.search_async('bar')]
    results = [future.get_result() for future in futures]
    return results

Esquemas de índice

Todos os índices têm esquemas que mostram os nomes e os tipos de campos que aparecem nos documentos que contêm. Não é possível definir um esquema. Os esquemas são mantidos dinamicamente e são atualizados conforme os documentos são adicionados ao índice. Um esquema simples pode ter a seguinte aparência em um formulário estilo JSON:

{'comment': ['TEXT'], 'date': ['DATE'], 'author': ['TEXT'], 'count': ['NUMBER']}

Cada chave do dicionário é o nome de um campo de documento. O valor da chave é uma lista dos tipos de campo usados com esse nome. Se você usar o mesmo nome de campo com tipos diferentes, o esquema listará mais de um tipo para cada nome, como no exemplo a seguir:

{'ambiguous-integer': ['TEXT', 'NUMBER', 'ATOM']}

Quando um campo aparece em um esquema, ele nunca mais pode ser removido. Não há como excluir um campo, mesmo que o índice não contenha mais nenhum documento com esse nome de campo específico.

É possível ver os esquemas dos índices assim:

from google.appengine.api import search
...
for index in search.get_indexes(fetch_schema=True):
    logging.info("index %s", index.name)
    logging.info("schema: %s", index.schema)
Observe que uma chamada para get_indexes não pode retornar mais de 1.000 índices. Para recuperar mais índices, chame a função repetidamente usando o argumento start_index_name.

No esquema, não há a definição de uma "classe" no sentido da programação de objeto. No que diz respeito à API Search, cada documento é único, e os índices podem conter diferentes tipos de documentos. Se você quiser tratar coleções de objetos com a mesma lista de campos como instâncias de uma classe, isso é uma abstração que precisará ser aplicada no seu código. Por exemplo, é possível garantir que todos os documentos com o mesmo conjunto de campos sejam mantidos no próprio índice. O esquema de índice pode ser visto como a definição da classe, e cada documento no índice é uma instância dessa classe.

Como visualizar índices no console do Google Cloud

No Console, é possível visualizar informações sobre os índices do aplicativo e os documentos que eles contêm. Clique em um nome de índice para exibir os documentos contidos nele. Você verá todos os campos de esquema definidos para o índice e, para cada documento que contém um campo com aquele nome, você verá o valor desse campo. Também é possível emitir consultas aos dados do índice diretamente do console.

Cotas da API Search

A API Search tem várias cotas gratuitas:

Recurso ou chamada de API Cota gratuita
Armazenamento total (documentos e índices) 0,25 GB
Consultas 1.000 consultas por dia
Adição de documentos a índices 0,01 GB por dia

Esses limites são impostos pela API para garantir a confiabilidade do serviço. Eles são válidos para aplicativos gratuitos e pagos:

Recurso Cota de segurança
Uso máximo da consulta 100 minutos agregados de tempo de execução da consulta por minuto
Máximo de documentos adicionados ou excluídos 15.000 por minuto
Tamanho máximo por índice (número ilimitado de índices permitido) 10 GB

O uso da API é contado de diferentes maneiras, dependendo do tipo de chamada:

  • Index.search(): cada chamada de API conta como uma consulta. O tempo de execução é equivalente à latência da chamada.
  • Index.put(): quando você adiciona documentos a índices, o tamanho de cada documento e o número de documentos contam para a cota de indexação.
  • Todas as outras chamadas da API Search são contadas com base no número de operações que envolvem:
    • search.get_indexes(): uma operação é contada para cada índice realmente retornado ou uma operação se nada for retornado.
    • Index.get() e Index.get_range(): uma operação contada para cada documento realmente retornado ou uma operação se nada for retornado.
    • Index.delete(): uma operação é contada para cada documento da solicitação, ou uma operação se a solicitação estiver vazia.

A cota sobre a capacidade da consulta é imposta para que um único usuário não monopolize o serviço de pesquisa. Como as consultas podem ser executadas simultaneamente, cada aplicativo pode executar consultas que consumam até 100 minutos de tempo de execução por minuto no relógio. Se executar várias consultas curtas, você provavelmente não atingirá esse limite. porém, se exceder a cota, as consultas subsequentes falharão até a próxima fração de tempo, quando a cota é restaurada. A cota não é imposta rigorosamente em frações de um minuto. Uma variação do algoritmo leaky bucket é usada para controlar a largura de banda da pesquisa em incrementos de cinco segundos.

Veja mais informações na página Cotas. Se o app tentar exceder esses valores, é exibido um erro de cotas insuficientes.

Esses limites são aplicados por minuto, mas o console exibe os totais diários para cada um. Os clientes com suporte Silver, Gold ou Platinum podem solicitar limites de capacidade maiores. Basta entrar em contato com o representante do suporte.

Preços da API Search

As seguintes cobranças são aplicadas ao uso além das cotas gratuitas:

Recurso Custo
Armazenamento total (documentos e índices) US$ 0,18 por GB/mês
Consultas US$ 0,50 por 10.000 consultas
Indexação de documentos pesquisáveis US$ 2,00 por GB

Para mais informações, acesse a página de Preços.