
I can haz HTML (CC-BY Tomomi)
Langage : Python
Bibliothèques : bioservices, HTMLParser, re (partiellement)
Niveau : débutant-intermédiaire
Dans un article précédent, je vous ai présenté le module bioservices en Python. Au cours de mon travail j'ai été amenée à récupérer des informations sur les termes Gene Ontology, et notamment sur les relations entre différents termes. Cependant, les formats de fichiers récupérés sont différents en fonction des données qu'ils renferment. Dans cet article, je vais vous présenter comment récupérer dans un format facile à lire les données dans un fichier au format HTML à partir de la bibliothèque standard HTMLParser.
Récupérer les termes GO au format HTML
Dans un premier temps, nous allons utiliser le module bioservices pour récupérer les données sur le terme GO:0003824, qui correspond à l'activité catalytique. Le format demandé est le format 'mini'.
1 2 3 4 5 6 7 8 |
#!/usr/bin/env python from bioservices import QuickGO qg = QuickGO() term = qg.Term("GO:0003824", format="mini") print term |
Parser le fichier HTML récupéré
La question que l'on se pose maintenant est quelle sera la bibliothèque Python à utiliser pour parser le fichier récupéré. Dans la bibliothèque standard il existe un module permettant de découper le format HTML selon les balises et les attributs, voyons comment nous récupérons les données de notre activité catalytique.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#!/usr/bin/env python from bioservices import QuickGO from HTMLParser import HTMLParser qg = QuickGO() term = qg.Term("GO:0003824", format="mini") class BaseParser(HTMLParser): def handle_starttag(self, tag, attrs): print "start tag:",tag print "attributes:",attrs def handle_endtag(self, tag): print "end tag:",tag def handle_data(self, data): print "data:",data if __name__ == "__main__": p = BaseParser() p.feed(term) exit() |
Dans ce script, je crée la classe BaseParser qui hérite de la classe HTMLParser (du module du même nom). Puis je surcharge les méthodes handle_starttag, handle_endtag et handle_data.
La méthode handle_starttag gère les ouvertures de balises (ou tag) tag ainsi que leurs attributs (comme l'attribut href de la balise <a>) attrs.
La méthode handle_endtag gère les fermetures de balises tag.
La méthode handle_data gère les données data contenues entre les balises d'ouverture et de fermeture.
Récupérer uniquement les fils de GO:0003824
Maintenant que l'on voit mieux comment fonctionne le module HTMLParser, nous allons pouvoir surcharger les méthodes à notre convenance afin de ne récupérer que les données du tableau des fils.
Pour cela nous définissions 3 variables booléennes : in_td, in_h2 et todo.
Si on rencontre un tag d'ouverture h2, alors la variable in_h2 vaut True. Si on rencontre un tag de fermeture h2, alors la variable in_h2 vaut False.
Si, pendant que in_h2 vaut True, les données valent "Children", alors la variable todo vaut True.
Si on rencontre un tag d'ouverture td, alors la variable in_td vaut True. Si on rencontre un tag de fermeture td, alors la variable in_td vaut False.
Lorsque l'on observe les données, si les variables in_td et todo valent toutes les deux True, alors on affiche les données contenues entre les balises d'ouverture et de fermeture de td.
Pour chaque données lue, on supprime le retour à la ligne (\n) et on supprime l'espace en début de chaîne avec le module de manipulation d'expression régulière re.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#!/usr/bin/env python from bioservices import QuickGO from HTMLParser import HTMLParser import re qg = QuickGO() term = qg.Term("GO:0003824", format="mini") class Parser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.in_td = False self.in_h2 = False self.todo = False def handle_starttag(self, tag, attrs): if tag == 'h2': self.in_h2 = True if tag == 'td': self.in_td = True def handle_endtag(self, tag): if tag == 'td': self.in_td = False if tag == 'h2': self.in_h2 = False def handle_data(self, data): if self.in_h2: if data == "Children": self.todo = True if data != "Children": self.todo = False if self.todo and self.in_td: data = re.sub(r'\n', '', data) data = re.sub(r'^ ', '', data) if data != "": print data if __name__ == "__main__": p = Parser() p.feed(term) exit() |
Conclusion
En conclusion je dirai qu'il n'est pas toujours nécessaire de chercher de gros modules très complets et complexes pour traiter certains formats de fichiers, dans cet exemple vous pouvez constater que la bibliothèque standard de Python peut amplement suffire.
La raison principale pour laquelle je me suis penchée sur cette solution réside dans le fait que l'on ne peut pas toujours installer des mille et des cents de modules ou de programmes dans un environnement de travail professionnel. J'ai d'abord préféré chercher dans les modules de base avant de demander à ce que l'on m'installe un module plus complet et complexe.
Merci à Wocka, Yoann M. et ZaZo0o pour leur relecture et leurs commentaires.
Hub
avril 2, 2014 à 3:24
Hello,
Tout d'abord merci pour l'article qui est très clair sur la marche à suivre pour utiliser HTMLParser.
Par contre, j'ai quelques critiques à émettre.
2 améliorations possibles sur le code écrit :
- supprimer le exit() à la fin, il est inutile.
- Pourquoi laisser les variables 'qg' et 'term' en globales ? Elles ont tout à fait leur place dans le main.
C'est beaucoup plus propre et ca sera beaucoup plus facile de généraliser le script plus tard. 😉
Enfin, j'ai beaucoup tiqué sur la conclusion qui est en partie fausse et en partie de mauvaise foi. Je m'explique :
Ce que tu dis (sur le fait d'installer pleins de paquets) est vrai pour pas mal de langages (C++ par exemple) mais c'est en grande partie faux pour python.
Python a un formidable outil de gestion de modules nommé 'pip' (qui est d'ailleurs standard maintenant dans la 3.4). Couplé facultativement avec un virtualenv,
tu peux installer toi-même tes modules dans ton HOME sans rien demander à personne (avec l'option --user). Pip gère toutes les dépendances du modules et t'auras toujours la dernière version (contrairement à un apt-get ou yum. D'ailleurs il ne faudrait jamais gérer ses modules python avec ces installeurs).
Donc, la majorité du temps, tu pourras toujours installer mille et cents modules dans ton environnement de travail professionnel sans ne rien casser ni demander à quelqu'un. C'est, ce que je trouve, une véritable force de Python.
La où je trouve qu'il y a un peu de mauvaise foi, c'est lorsque tu dis "qu'il n'est pas nécessaire de chercher de gros modules très complets et complexes [...]" or tu utilises le module bioservices qui est une véritable usine 🙂 (11 dépendances d'après pip).
D'ailleurs, dans ces dépendances, il y a "BeautifulSoup" qui est un très bon parser de HTML. Pourquoi ne pas l'avoir utiliser ?
Nolwenn
avril 2, 2014 à 3:41
Bonjour et merci pour votre retour.
Alors, pour commencer, il est vrai que je ne l'ai pas précisé dans l'article, mais dans mon unité, sur une vingtaine de personnes, nous sommes 2 à coder en Python. Donc, installer VirtualEnv pour 2 personnes, et qui n'ont aucune formation dessus, je n'en vois pas trop l'intérêt dans l'immédiat, il est possible de s'en passer.
De plus, nous avons des HOME très restreint en taille, ce qui fait que nous sommes très vite limité, donc VirtualEnv nous bloquerait rapidement pour ne serait-ce qu'un petit projet 🙂 !
Pour BeautifulSoup, j'avoue que je l'ignorais, merci de m'en informer, je ne l'ai pas vu passer dans la liste des dépendances, pip gérant l'installation comme un grand, dépendances comprises, je n'ai pas prêté grande attention lorsque j'avais testé Bioservices sur une machine virtuelle 🙂 ! Ce qui explique pourquoi je ne l'ai pas utilisé. En revanche, je l'ai découvert après en voulant manipuler des données retournées dans une classe de BeautifulSoup (après avoir cherché à comprendre la classe en question).
Pour finir, l'objectif de ce billet était de présenter HTMLParser, je l'ai fait en utilisant des données retournées grâce à Bioservices, tout comme j'aurais pu le faire avec une page de La Poste 🙂 !
Donc, pour la mauvaise foi, un peu mais pas trop 🙂 !
Bonne journée et encore merci pour votre retour.
Hub
avril 3, 2014 à 9:45
On est d'accord qu'on peut très bien se passer de l'utilisation de virtualenv. Ca apporte juste une surcouche pour gérer différentes versions de modules.
Par contre, rien n'empêche d'installer des modules localement via pip ailleurs que dans le HOME 😉
karl
avril 3, 2014 à 5:25
HTMLParser est intéressant.
Mais il y a en effet BeautifulSoup et aussi lxml surtout combiné avec html5lib. Par exemple
from bs4 import BeautifulSoup
htmlfile = urllib2.urlopen(uri)
soup = BeautifulSoup(htmlfile, "html5lib")
list = soup.find(class_='toto')
ou bien
from lxml.html import html5parser
HTMLNS = "http://www.w3.org/1999/xhtml"
parsed_html = html5parser.parse(uri)
title = parsed_html.xpath('//h:title', namespaces={'h': HTMLNS})[0].text