Principes de base de l'API Search

Amy Unruh, Octobre 2012
Relations avec les développeurs Google

Présentation

Cette leçon aborde les principes de base de l'utilisation de l'API Search : l'indexation de contenu et l'exécution de requêtes sur un index. Vous y apprendrez comment :

  • créer un index de recherche ;
  • ajouter du contenu via un document d'index ;
  • effectuer des requêtes simples de recherche en texte intégral sur ces données indexées.

Objectifs

Apprendre les principes de base de l'utilisation de l'API Search d'App Engine.

Prérequis

Index

L'API Search d'App Engine fonctionne via un objet Index. Cet objet vous permet de stocker des données via un document d'index, de récupérer des documents à l'aide de requêtes de recherche, de modifier des documents et de supprimer des documents.

Chaque index possède un nom d'index et, éventuellement, un espace de noms. Le nom identifie l'index de manière unique dans un espace de noms donné. Il doit s'agir d'une chaîne ASCII visible et imprimable ne commençant pas par !. Les caractères d'espacement sont interdits. Vous pouvez créer plusieurs objets Index, mais si deux de ces objets ont le même nom d'index dans le même espace de noms, alors ils font référence au même index.

Vous pouvez utiliser des espaces de noms et des index pour organiser vos documents. Pour notre exemple d'application de recherche de produit, tous les documents du produit se trouvent dans un index, tandis qu'un autre index contient des informations sur les emplacements de stockage. Par exemple, pour rechercher uniquement des livres, il est possible de filtrer une requête sur la catégorie de produit.

Dans votre code, vous devez créer un objet Index en spécifiant son nom d'index :

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

ou

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

L'index de document sous-jacent sera créé lors du premier accès s'il n'existe pas déjà. Vous n'êtes donc pas obligé de le créer explicitement.

Vous pouvez supprimer des documents d'un index ou supprimer l'intégralité de l'index, comme décrit dans la leçon suivante, intitulée Explications détaillées sur l'API Search pour Python.

Documents

Les documents contiennent un contenu indexable. Un document est un conteneur permettant de structurer des données indexables. D'un point de vue technique, un objet Document représente une collection de champs identifiée de manière unique par un ID de document. Les champs sont des valeurs associées à un nom et un type. Les documents n'ont pas de kinds au sens des entités Datastore.

Dans notre exemple d'application, par exemple, nos catégories de produits sont les livres et les téléviseurs HD. Le magasin propose une sélection de produits plutôt limitée. Chaque document produit dans notre exemple d'application comprend toujours les champs principaux suivants, définis par des variables de classe docs.Product :

  • CATEGORY (défini sur books ou hd_televisions)
  • PID (ID du produit)
  • PRODUCT_NAME
  • DESCRIPTION
  • PRICE
  • AVG_RATING
  • UPDATED (date de la dernière mise à jour)
Champs de document produit
Figure 1 : Champs du document produit

Les catégories Livres et Téléviseurs HD possèdent chacune des champs supplémentaires. Pour les livres, il s'agira des champs :

  • title
  • author
  • publisher
  • pages
  • isbn

Pour les téléviseurs HD, il s'agira des champs :

  • brand
  • tv_type
  • size

L'application elle-même applique une cohérence sémantique au niveau de l'application pour les documents de chaque type de produit. Autrement dit, tous les documents produit incluront toujours les mêmes champs principaux, tous les livres auront les mêmes champs supplémentaires, etc. Cependant, un index de recherche n'impose pas de cohérence schématique entre documents sur les champs utilisés. Il n'existe donc pas de concept explicite de recherche de documents "produit" en particulier.

Types de champ

Chaque champ de document possède un type de champ unique. Le type peut être l'un des suivants, défini dans l'élément search du module Python :

  • TextField : chaîne en texte brut.
  • HtmlField : texte au format HTML. Si votre chaîne est au format HTML, utilisez ce type de champ, car l'API Search peut prendre en compte le balisage lors de la création d'extraits de résultats et dans l'évaluation du document.
  • AtomField : chaîne traitée comme un simple jeton. Une requête ne renverra pas de résultat si elle n'inclut qu'une partie de la chaîne et non la valeur complète.
  • NumberField : valeur numérique (nombre entier ou à virgule flottante).
  • DateField : date sans élément temporel.
  • GeoField : emplacement géographique, désigné par un objet GeoPoint spécifiant les coordonnées de latitude et de longitude.

Pour les champs de types texte (TextField, HtmlField et AtomField), les valeurs doivent être des chaînes Unicode.

Exemple : créer les champs d'un document produit et un document

Pour construire un objet Document, vous devez créer une liste de ses champs, définir son ID de document si vous le souhaitez, puis transmettre ces informations au Document constructor.

L'exemple d'application utilise les types de champs TextField, AtomField, NumberField et DateField pour les documents produit.

Définir les champs du document produit

Les principaux champs relatifs au produit (ceux qui sont inclus dans tous les documents produit) se présentent comme ci-dessous. Nous partons du principe que les arguments de valeurs des constructeurs sont définis avec les valeurs appropriées :

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

Notez que le champ catégorie est associé au type AtomField. Les champs Atom sont utiles pour les informations telles que les catégories, où on attend des correspondances exactes. Les champs de type texte conviennent mieux aux chaînes telles que les titres ou les descriptions. Parmi nos exemples de catégories figure hd televisions. Si nous recherchons uniquement sur televisions, nous n'obtiendrons pas de résultat (en supposant que cette chaîne ne soit pas contenue dans un autre champ de produit). Mais si nous recherchons sur la chaîne complète, hd televisions, nous obtiendrons une correspondance sur le champ catégorie.

Notre exemple d'application inclut également des champs spécifiques à la catégorie du produit. Ceux-ci sont eux aussi ajoutés à la liste des champs, en fonction de la catégorie. Par exemple, pour la catégorie télévision, il existe des champs supplémentaires pour la size (un champ numérique), la brand et le tv_type (des champs de type texte). Les livres disposent de champs différents.

Créer des documents

Selon la liste de champs, nous pouvons créer un objet document. Pour chaque document produit, nous allons définir son ID de document comme étant l'ID unique prédéfini du produit :

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

Cette conception présente certains avantages pour nous (comme nous le verrons dans la leçon suivante), mais si nous ne spécifions pas d'ID de document, celui-ci est généré automatiquement lorsque le document est ajouté à un index.

Exemple : utiliser des points géographiques dans des documents relatifs à l'emplacement de stockage

L'API Search accepte Geosearch sur les documents incluant des champs de type GeoField. Si vos documents contiennent de tels champs, vous pouvez effectuer une recherche dans un index, basée sur la comparaison des distances.

Un emplacement est défini par la classe GeoPoint, qui stocke les coordonnées de latitude et de longitude. La latitude spécifie la distance angulaire, en degrés, au nord ou au sud de l'équateur. La longitude spécifie la distance angulaire, toujours en degrés, à l'est ou à l'ouest du premier méridien. Par exemple, l'emplacement de l'Opéra de Sydney est défini par les coordonnées GeoPoint(-33.857, 151.215). Pour stocker un point géographique dans un document, vous devez ajouter un champ GeoField avec un objet GeoPoint défini comme valeur.

Voici comment sont construits les champs des documents relatifs à l'emplacement de stockage dans l'application de recherche de produit :

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

Indexer des documents

Avant de pouvoir interroger le contenu d'un document, vous devez l'ajouter à un index, à l'aide de la méthode put() de l'objet Index. L'indexation permet d'effectuer une recherche dans le document à l'aide du langage de requête et des options de requête de l'API Search.

Vous pouvez spécifier votre propre ID de document lors de la création d'un document. L'ID du document doit être une chaîne ASCII visible et imprimable ne commençant pas par !. Les caractères d'espacement sont interdits. (Comme nous le verrons plus tard, si vous indexez un document en utilisant l'ID d'un document existant, celui-ci sera réindexé). Si vous ne spécifiez pas d'ID de document, un ID numérique unique est généré automatiquement lorsque le document est ajouté à l'index.

Vous pouvez ajouter des documents un à un, ou les ajouter par lots, ce qui est plus efficace. Voici comment créer un document en fonction d'une liste de champs et l'ajouter à un index :

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

Vous devez intercepter et gérer les exceptions résultant de l'opération put(), qui seront du type search.Error

Si vous souhaitez spécifier l'ID du document, transmettez-le au constructeur du Document comme indiqué ci-dessous :

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

Vous pouvez obtenir l'ID du ou des documents ajoutés, via les propriétés id de la liste des objets search.AddResult renvoyés par l'opération put() :

doc_id = add_result[0].id

Requêtes de recherche simples

L'ajout de documents à un index rend possible la recherche sur le contenu du document. Vous pouvez alors effectuer des requêtes de recherche en texte intégral sur les documents de l'index.

Il existe deux manières de soumettre une requête de recherche. La plus simple consiste à transmettre une chaîne de requête à la méthode search() de l'objet Index. Vous pouvez également créer un objet Query et le transmettre à la méthode search(). La création d'un objet de requête vous permet de spécifier des options de requête, de tri et de présentation des résultats pour votre recherche.

Dans cette leçon, nous verrons comment construire des requêtes simples en utilisant les deux approches. Pour rappel, certaines requêtes de recherche ne sont pas totalement compatibles avec le serveur Web de développement (en exécution locale). Vous devrez donc les exécuter à l'aide d'une application déployée.

Effectuer une recherche à l'aide d'une chaîne de requête

Une chaîne de requête peut être n'importe quelle chaîne Unicode pouvant être analysée par le langage de requête de l'API Search. Une fois que vous avez construit une chaîne de requête, transmettez-la à la méthode Index.search(). Exemple :

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

Effectuer une recherche à l'aide d'un objet de requête

Un objet Query permet de mieux contrôler vos options de requête qu'une chaîne de requête. Dans cet exemple, nous créons tout d'abord un objet QueryOptions. Ses arguments spécifient que la requête doit renvoyer un nombre de résultats doc_limit. Si vous avez observé le code de l'application de recherche de produit, vous aurez remarqué des objets QueryOption plus complexes. Nous les étudierons dans la prochaine leçon, intitulée Explications détaillées sur l'API Search pour Python . Nous construisons ensuite l'objet Query à l'aide de la chaîne de requête et de l'objet QueryOptions. Nous transmettons ensuite l'objet Query à la méthode Index.search(), comme nous l'avons fait ci-dessus avec la chaîne de requête.

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

Traiter les résultats de la requête

Une fois que vous avez soumis une requête, les résultats de recherche correspondants sont renvoyés à l'application dans un objet itérable SearchResults. Cet objet inclut le nombre de résultats trouvés, les résultats réels renvoyés et un objet curseur de requête facultatif.

Les documents renvoyés sont accessibles en effectuant une itération sur l'objet SearchResults. Le nombre de résultats renvoyés correspond à la longueur de la propriété results de l'objet. La propriété number_found est définie sur le nombre de résultats trouvés. Les itérations effectuées sur l'objet renvoyé fournissent les documents correspondants, que vous pouvez traiter comme vous le souhaitez :

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

Résumé et vérification

Dans cette leçon, nous avons abordé les bases de la création de documents indexés et appris comment interroger leur contenu. Pour vérifier vos connaissances, essayez de recréer ces étapes vous-même dans votre propre application :

  • Créez un objet Index.
  • Créez une liste de champs de document (par exemple, en utilisant le type TextField) et créez un objet Document avec cette liste de champs. Ajoutez le document à l'index.
  • Effectuez une recherche dans l'index à l'aide d'une chaîne de recherche composée d'un terme correspondant à l'une de vos valeurs de champs. Le document que vous avez créé est-il affiché comme résultat ?

Dans la leçon suivante, nous étudierons de plus près les index de l'API Search.

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

Envoyer des commentaires concernant…

Environnement standard App Engine pour Python 2