Requêtes complexes de l'API Search

Amy Unruh, Octobre 2012
Relations Google Developer

Présentation

Le cours précédent couvrait les bases de la définition, de l'envoi et du traitement d'une requête de recherche. L'API Search accepte des requêtes plus complexes. Vous pouvez par exemple spécifier le point de départ de la requête dans l'index, la manière dont les résultats doivent être triés et formatés, ainsi que les informations sur les documents devant être renvoyés par la requête. L'API est également compatible avec Geosearch (requêtes basées sur la localisation).

Dans cette leçon, nous allons étudier plus en détail certaines de ces fonctionnalités. Vous allez découvrir les concepts suivants :

  • Définir et traiter les résultats de requêtes complexes
  • Contrôler les champs de document renvoyés par une requête
  • Utiliser des décalages et des limites pour contrôler le point de départ d'une requête et le nombre de résultats renvoyés
  • Créer et utiliser des requêtes basées sur la localisation (Geosearch)

Reportez-vous à la documentation de l'API Search pour en savoir plus sur les fonctionnalités décrites dans cette leçon, ainsi que sur d'autres fonctionnalités qui ne seront pas abordées ici.

Objectifs

Savoir effectuer des requêtes de recherche complexes avec l'API Search.

Prérequis

Avoir suivi le cours qui précède celui-ci : Premiers pas avec l'API Python Search.

Nous vous recommandons en outre d'effectuer les actions suivantes :

Options de requête

Le constructeur pour la classe Query accepte un objet facultatif QueryOptions en tant qu'argument, ce qui vous permet de configurer un large éventail d'options :

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

Prenons l'une des configurations de QueryOptions, utilisée dans l'exemple d'application de recherche de produit :

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]
        ))

Cette configuration spécifie un décalage (point de départ de la requête), une limite (nombre maximal de résultats à renvoyer), des options de tri (détaillées dans la leçon suivante), une liste d'extraits de champs, une liste d'expressions renvoyées (champs calculés) et une liste de champs renvoyés. Observons le comportement de chacune de ces options.

Curseurs, limites et décalages des requêtes

Pour contrôler le nombre de résultats renvoyés par une requête, utilisez le paramètre limit du constructeur QueryOptions. L'exemple d'application de recherche de produit se sert de limit pour renvoyer au maximum trois résultats par page.

L'exemple ci-dessus illustre également l'utilisation du paramètre offset. Le décalage indique le nombre de documents correspondants à ignorer avant de commencer à renvoyer les résultats :

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

Un cas d'utilisation courant des paramètres offset et limit consiste à paginer les résultats de la requête. Pour mettre en œuvre la pagination, vous devez connaître le nombre total de correspondances trouvées dans la requête et le nombre de correspondances renvoyées jusqu'à présent. Vous pouvez obtenir ces informations à partir de l'objet SearchResults renvoyé :

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

L'API Search accepte également l'utilisation de curseurs de requête. Les curseurs constituent une autre manière d'indiquer le point de départ d'une requête, ce qui vous permet de poursuivre une recherche à partir de la fin de l'ensemble de résultats précédent. En général, il est plus efficace d'utiliser un curseur plutôt que des décalages. Toutefois, l'API Search n'accepte pas de "curseur inversé" pour le moment, tout comme l'API Datastore, ce qui complique la mise en œuvre de la pagination vers l'arrière. C'est la raison pour laquelle l'exemple d'application utilise des décalages plutôt que des curseurs pour la pagination des résultats de la requête. Cliquez ici pour consulter un exemple utilisant des curseurs.

Extraits

Les extraits de champs vous permettent de renvoyer une partie abrégée d'un champ à la place de l'intégralité du contenu. L'extrait renvoyé inclut le fragment du champ sur lequel la correspondance a été trouvée, avec les termes de recherche correspondants indiqués en gras. Dans l'application de recherche de produit (avec les données définies par défaut), une recherche sur la requête stories renvoie trois correspondances, dans les champs description des documents. Comme nous avons demandé que le champ description soit extrait, le mot "stories" (histoires) est mis en surbrillance dans les expressions extraites.

Extraits de champs avec les termes correspondants mis en surbrillance
Figure 1 : Extraits de champs avec les termes correspondants mis en surbrillance.

Vous devez spécifier l'extrait qui doit être généré en fournissant un itérable des noms de champs à extraire. Le constructeur QueryOptions ci-dessus demande un extrait du champ DESCRIPTION :

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

Ensuite, lors du traitement des résultats de la requête, vous accédez aux extraits générés via la propriété expressions du document renvoyé :

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 ...

La propriété expressions contient une liste de champs calculés, qui constituent les résultats des expressions demandées dans la requête. Le code ci-dessus récupère l'extrait généré pour le champ DESCRIPTION, dans lequel doc correspond à un document noté. Les documents notés sont renvoyés à partir d'une recherche. Outre le contenu du document, ils incluent le score du document, ainsi que les champs calculés (mentionnés ci-dessous) et d'autres informations.

Expressions renvoyées et fonctions d'expression

L'option de requête returned_expression vous permet de définir des champs calculés en fonction de vos champs de document. Ces champs seront renvoyés au sein d'un document noté dans les résultats de recherche.

Supposons que vous souhaitiez calculer et afficher un prix pour chaque produit comprenant une taxe de vente de 8 %. Vous devez créer une expression de champ avec le nom adjusted_price, dont la valeur correspond à la chaîne price * 1.08 :

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

Cette expression indique à l'API Search de renvoyer, en tant que valeur du champ adjusted_price, la valeur du champ price multipliée par 1,08. L'API Search fournit plusieurs fonctions d'expression intégrées, que vous pouvez utiliser dans des expressions de ce type. Par exemple, vous pouvez définir des expressions telles que 'max(price, 9.99)'.

Après avoir inclus une liste returned_expression dans votre objet QueryOptions, vous pouvez accéder à ce champ calculé dans les documents renvoyés par la requête de recherche, de nouveau via la propriété 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 ...

Champs renvoyés

Le constructeur QueryOptions accepte également le paramètre returned_fields, qui peut vous permettre d'augmenter l'efficacité de vos requêtes en ne demandant que les champs de documents que vous envisagez d'utiliser. Par exemple, l'objet QueryOptions évoqué précédemment demande tous les champs de produit "core" (principaux), à l'exception de la date de la dernière mise à jour, que nous avons choisi de ne pas afficher dans le résumé des résultats. En outre, il ne demande aucun des champs spécifiques à une catégorie, tels que publisher pour les documents book ou tv_type pour les documents 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]
    ...)

L'argument returned_fields doit être un itérable des noms de champs à renvoyer dans les résultats de la recherche. Les documents renvoyés dans les résultats de la recherche n'incluent que les champs spécifiés, même si les documents indexés peuvent en inclure d'autres.

Requêtes basées sur la localisation (Geosearch)

La compatibilité de l'API Search avec Geosearch vous permet d'effectuer des requêtes basées sur la localisation. Celles-ci vous permettent, par exemple, de trouver des magasins ou des restaurants à proximité, ou encore des mises à jour de flux d'activités à proximité.

Pour exécuter une requête basée sur la localisation, les trois informations suivantes sont nécessaires :

  • Un emplacement, exprimé en coordonnées de latitude et de longitude, à partir duquel mesurer les distances
  • Le rayon dans lequel effectuer la recherche (par exemple, 45 kilomètres)
  • L'ensemble des points à partir desquels mesurer les distances

En général, les deux premiers éléments sont fournis par l'utilisateur. Le dernier élément est issu des documents indexés eux-mêmes. Par exemple, dans notre application de recherche de produit, il se compose des emplacements des magasins, récupérés à partir des documents relatifs à l'emplacement du magasin que nous avons créés dans le cours précédent intitulé Premiers pas.

Pour rechercher des emplacements de magasins à proximité de l'utilisateur, l'exemple d'application obtient la localisation de l'utilisateur via le navigateur et l'utilisateur indique le rayon dans lequel effectuer la recherche. La distance est convertie en mètres, l'unité de distance utilisée par l'API Search. Supposons que l'emplacement de l'utilisateur soit (-33.857, 151.215) et que son rayon de recherche soit de 45 kilomètres. L'application va créer une chaîne de requête semblable à celle-ci :

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

puis la transmettre à la méthode 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:
      # ...

Résumé et vérification

Dans cette leçon, nous avons appris à spécifier une requête de recherche à l'aide d'un objet QueryOptions. Nous avons également étudié certaines propriétés QueryOptions utiles : limit et offset, snippeted_fields, returned_expression et returned_fields. Enfin, nous avons abordé la création d'une requête Geosearch.

La propriété sort_options de QueryOptions est suffisamment riche en fonctionnalités pour faire l'objet d'une leçon à part entière. Nous en discuterons dans un prochain article. Reportez-vous à la documentation sur QueryOptions pour consulter des options supplémentaires qui n'ont pas été abordées dans cette leçon.

Pour vérifier que vous avez bien compris, essayez d'utiliser certaines des propriétés QueryOptions mentionnées dans cette leçon. Par exemple, remplacez la valeur DOC_LIMIT du fichier config.py par une valeur plus élevée. Il s'agit de la valeur transmise en tant qu'argument QueryOptions limit.

Essayez d'utiliser la fonctionnalité returned_expressions. Dans _buildQuery(), returned_expressions aurait été définie de la manière suivante :

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

Recherchez les lignes de handlers.py, dans la classe ProductSearchHandler, qui indiquent les éléments suivants :

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

Annulez la mise en commentaire des lignes ci-dessous :

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

Lorsque vous redéployez l'application, le champ adjusted_price doit s'afficher dans les résultats de la recherche à la place du prix réel. En d'autres termes, le prix affiché inclura la taxe de vente. Le lien View product details (Afficher les informations détaillées sur le produit) dans les résultats de la recherche indiquera toujours le prix réel. (Le champ adjusted_price ne sera renseigné que pour une application déployée).

Dans la prochaine leçon, vous allez apprendre à trier les résultats d'une requête de recherche dans l'ordre de votre choix.

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Python sur App Engine