Accessibility Tools

- Le blog participatif de bioinformatique francophone depuis 2012 -

Ze amazing HTML cat (CC-BY Tomomi)
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'.

#!/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.

#!/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.

#!/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.




Commentaires

4 réponses à “Parser des fichiers HTML en Python”

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

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

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

  2. 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/​1​9​9​9​/​x​h​tml"
    parsed_​html = html5parser.parse(uri)
    title = parsed_html.xpath('//h:title', namespaces={'h': HTMLNS})[0].text

Laisser un commentaire

Pour insérer du code dans vos commentaires, utilisez les balises <code> et <\code>.