« Paie Ton Patch !™ » : Weboob – Partie 1/2

Combien de fois vous êtes-vous dit « Pourquoi c’est pas corrigé ça ? » ou « faudrait patcher ce truc » sans oser le faire ? Voici une occasion !

Web Outside of Browsers [1] est un ensemble d’outils modulaires en ligne de commandes écrits en Python, ainsi que quelques applications graphiques Qt. Son but est de pouvoir utiliser des sites web comme l’on utilise d’autres ressources sous Unix, à l’aide d’outils simples composables et scriptables. Parmi les outils de scraping existants, il s’agit probablement du plus complet, et décrire ses possibilités nécessiterait plusieurs articles. Ses compétences vont de la récupération de vidéos de sites web (et non-web en Flash) à l’émission de virements bancaires, en passant par l’édition de tickets dans un bugtracker, ou la recherche d’emploi. C’est cette dernière fonction que nous testerons, en contribuant au support du site LinuxJobs.fr [2].

Sur le canal IRC [3] de Weboob, il est de tradition lorsque quelqu’un râle sur une fonctionnalité manquante de dire « PTP », c’est-à-dire « Paie Ton Patch », comme invitation à contribuer, certes pas toujours efficace. L’expression, consacrant le patch comme monnaie officielle de la do-ocratie, est donc toute désignée comme titre.

1. Architecture

Weboob est constitué de nombreux modules, chacun implémentant une ou plusieurs capacités (Capability) telles que CapVideo, CapMessages, CapJob, etc. et de nombreuses applications utilisant ces différentes capacités. Un module est composé de plusieurs fichiers, chacun déclarant une ou plusieurs classes :

  • __init__.py : exporte la classe de base Module ;
  • module.py : définit une sous-classe de Module indiquant son nom, ses capacités, etc. ;
  • browser.py : dérive la classe Browser qui implémente l’interaction avec le site web ;
  • pages.py : implémente plusieurs classes décrivant chaque type de page que le site retourne et comment y récupérer les informations désirées ;
  • test.py : le truc que tout le monde oublie ;
  • favicon.png : une icône PNG de 64×64 avec transparence. Les icônes dans Weboob sont volontairement caricaturales pour contourner un éventuel problème de droit des marques. Donc lâchez-vous !

Enfin, le framework propose tout ce qu’il faut pour gérer les connexions HTTP(S), et parser le HTML ou le JSON. Quelques modules datent encore des « heures les plus sombres de l’histoire de Weboob », avant l’avènement du Browser2. Un module important weboob.deprecated.browser est une invitation à contribuer à une mise à jour.

Le tout est documenté sur le site de développement [4]. Nous utiliserons l’application handjoob pour tester notre module.

2. Code source et installation

Weboob documente [5] plusieurs méthodes d’installation, suivant votre distribution et vos besoins. Pour tester notre code plus simplement, on peut installer en mode développeur, ou même lancer directement les commandes sans installation à l’aide d’un script.

Weboob a quelques dépendances, sur Debian par exemple :

Le code source officiel est disponible sur un serveur dédié [6], où les principaux contributeurs ont aussi leur propre dépôt. Récupérons le dépôt de développement, dans ~/src/ par exemple :

2.1 Installation « développeur »

Un script est proposé pour installer sans toucher au système (passez –deps au script si vous n’avez pas trouvé toutes les dépendances dans votre distro, il les installera par pip) :

Ensuite il nous faut déclarer notre dépôt local comme source de modules, puis forcer une mise à jour :

2.2 Pas d’install

Le script local_run.sh dans le dépôt permet d’éviter l’installation :

Si vous optez pour cette méthode, il vous suffira donc de lancer les commandes par ce script.

3. Génération d’un squelette de module

Vous avez probablement maintenant déjà fouillé le code source et vous vous apprêtez à copier un module existant vers modules/linuxjobs suivi d’un coup de sed. Je sais, on l’a tous fait, mais pour une fois on vous propose une méthode plus propre [7]. Depuis la racine du dépôt, créons une branche, puis invoquons l’outil magique :

Si vous avez déjà configuré git proprement, il utilisera même vos nom et email pour l’attribution. Forçons ensuite une mise à jour du cache des modules, et vérifions qu’il trouve bien notre bébé :

4. Choisir la source des données

Avant de foncer tête baissée, il convient d’étudier un peu le site : comment se fait une recherche ? Les données sont-elles bien balisées ? Y a-t-il une source plus fiable et plus stable que le code HTML de la page, via des requêtes AJAX récupérant du XML ou du JSON ? Le scraping reste un dernier recours fragile, sensible au moindre changement des pages.

Ici le site est récent, et les annonces sont présentées très simplement, il faudra donc valider proprement les données. Si le site ne fait pas de requêtes AJAX, il existe par contre des flux RSS pour chaque catégorie, où la description est en Markdown au lieu de HTML. Par contre, rechercher une annonce nécessiterait de savoir dans quel flux chercher, donc parser la catégorie dans la page HTML de toute façon. Et puis nous voulons un exemple bien gore pour l’article, donc restons sur le hache-thé-aime-elle.

5. Remplissons les blancs

Nous allons maintenant remplir naïvement le squelette généré avant de réaliser un premier test, en nous appuyant sur la documentation de CapJob et d’autres modules comme adecco (mais il utilise l’ancienne API) ou popolemploi (si, le module existe !). L’exemple reste simple, mais parfois les interactions entre les classes dans certains modules complexes peuvent nécessiter une aspirine. N’hésitez pas à demander aux gens bons sur IRC !

5.1 module.py

La classe LinuxJobsModule définit trois méthodes non implémentées que nous devons donc remplir :

  • advanced_search_job, qui effectue une recherche sur des critères configurés au préalable. Le site visé ne proposant pas de recherche multicritère il nous faudrait filtrer les résultats, et gérer aussi la configuration du module. Nous allons donc laisser cette méthode de côté. Mais vous pourrez proposer un patch !
  • get_job_advert doit retourner un objet décrivant l’annonce correspondant à l’identifiant unique en paramètre. Le site semble utiliser un nombre croissant monotone dans l’URL pour cet usage. Nous passons simplement la requête au browser comme font les autres modules :

  • search_job doit itérer sur une recherche par mots-clefs. Ici aussi, c’est le browser qui se chargera de la requête :

5.2 browser.py

La classe LinuxJobsBrowser est chargée de l’interaction avec le site, et utilise différentes Pages en fonction de l’URL demandée.

Plutôt que Page1 et Page2, nous utiliserons SearchPage et AdvertPage en indiquant les regexp idoines, l’une récupérant l’ID de l’annonce, l’autre indiquant où spécifier la chaîne à rechercher. Corrigeons au passage l’URL de base, par défaut en HTTP et .com :

Nous aurions également pu spécifier un PROFILE de navigateur (Weboob se fait passer pour Firefox par défaut), ce qui est utile entre autres pour récupérer des vidéos HLS à la place du Flash sur un site codé avec les pieds. Il est également possible de spécifier un TIMEOUT différent des 10s par défaut.

En lieu et place du get_stuff proposé, nous ajouterons les méthodes que nous appelons depuis LinuxJobsModule :

Dans les deux cas, nous appelons la méthode go() pour envoyer la requête après avoir préparé les paramètres. Si l’id n’a pas vraiment besoin de traitement, la chaîne de recherche doit être encodée en UTF-8 et les espaces remplacés par des signes plus.

Comme suggéré dans get_stuff, un assert s’assure que nous sommes bien passés sur la bonne page, dont une instance sera alors assignée à self.page. Puis nous appelons une méthode de la classe correspondante pour remplir l’objet décrivant l’annonce, ou itérer sur les résultats de la recherche.

François Revol
[Serial Patcheur, contributeur de plusieurs modules (europarl, gdcvault, vimeo) et de leurs icônes (oui, j’ai honte)]

La seconde partie de cet article sera publiée prochainement sur le blog, restez connectés 😉

Retrouvez cet article (et bien d’autres) dans GNU/Linux Magazine n°204, disponible sur la boutique et sur la plateforme de lecture en ligne Connect !

Laisser un commentaire