BeautifulSoup / parser vos XML et HTML

Nous avons vu précédemment comment parser du XML, il est également possible de parser du HTML et l'outil qui fait le mieux le job selon moi c'est le librairy BeautifulSoup

Installer la bibliothèque BeautifulSoup

Qui dit lib python dit pip

sudo pip install beautifulsoup4

Récupérer le contenu d'une balise spécifiée

BeautifulSoup vous propose par exemple de récupérer toutes les balises p d'une page HTML

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup

html_doc = """
<html>
    <head>
    <title>Titre de votre site</title>
    </head>
    <body>
        <p>Texte à lire 1</p>
        <p>Texte à lire 2</p>
    </body>
</html>
"""
soup = BeautifulSoup(html_doc)
    
for p in soup.find_all('p'):
    print p

Ce qui retournera:

<p>Texte à lire 1</p>
<p>Texte à lire 2</p>

Changer le contenu de balises

Trouver les éléments qui nous intéresse c'est une chose, mais pouvoir les modifier c'est encore mieux!

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup

html_doc = """
<html>
    <head>
    <title>Titre de votre site</title>
    </head>
    <body>
        <p>Texte à lire 1</p>
        <p>Texte à lire 2</p>
    </body>
</html>
"""
soup = BeautifulSoup(html_doc)
    
for p in soup.find_all('p'):
    p.string = "Nouveau texte"
    
print soup

Résultat:

<html>
<head>
<title>Titre de votre site</title>
</head>
<body>
<p>Nouveau texte</p>
<p>Nouveau texte</p>
</body>
</html>

Remplacer des balises

Vous pouvez remplacer les balises avec la méthode replace_with:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup

html_doc = """
<html>
    <head>
    <title>Titre de votre site</title>
    </head>
    <body>
        <p>Texte à lire 1</p>
        <p>Texte à lire 2</p>
    </body>
</html>
"""
soup = BeautifulSoup(html_doc)
    
for p in soup.find_all('p'):
    n = BeautifulSoup('<pre>%s</pre>' % p.string)
    p.replace_with(n.body.contents[0])
    
print soup

Réponse du script:

<html>
<head>
<title>Titre de votre site</title>
</head>
<body>
<pre>Texte à lire 1</pre>
<pre>Texte à lire 2</pre>
</body>
</html>

Lire les attributs

Il est possible de lire les attributs des éléments avec la méthode get:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup

html_doc = """
<html>
    <head>
    <title>Titre de votre site</title>
    </head>
    <body>
        <p class="c1 c2">Texte à lire 1</p>
        <p class="c3">Texte à lire 2</p>
    </body>
</html>
"""
soup = BeautifulSoup(html_doc)
    
for p in soup.find_all('p'):
    print p.get("class")

Résultat:

>>> ['c1', 'c2']
>>> ['c3']

Les méthodes de la classe BeautifulSoup

clear(decompose=False)

Extrait tous les enfants

decode_contents(indent_level=None, eventual_encoding='utf-8', formatter='minimal')

Créer un rendu en chaine unicode

decompose()

Detruit récursivement les contenus de l'arbre

encode(encoding='utf-8', indent_level=None, formatter='minimal', errors='xmlcharrefreplace')

encode

encode_contents(indent_level=None, encoding='utf-8', formatter='minimal')

Créer des rendus du tag en bytestring

find(name=None, attrs={}, recursive=True, text=None, **kwargs)

Retourne seulement le premier enfant de la balise correspondant pour le critère donné

find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

Retourne une liste d'objet balise correspondant à la demande.

find(name=None, attrs={}, recursive=True, text=None, **kwargs)

Retourne seulement le premier enfant de la balise cherchée

findChildren(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

Retourne une liste d'objet balise correspondant à la demande

get(key, default=None)

Retourne la valeur de l'attribut "key" de la balise ou retourne la valeur default

get_text(self, separator=u'', strip=False, types=(<class 'bs4.element.NavigableString'>, <class 'bs4.element.CData'>))

Retourne toutes les chaines de caractères des enfants concaténé utilisant le séparateur indiqué

has_attr(key)

True si l'attribut demandé est présent

has_key(key)

Vérifie la présence de la clé

index(element)

Retourne l'index de l'élément

prettify(self, encoding=None, formatter='minimal')

Améliore la lecture du code

recursiveChildGenerator()

append(self, tag)

Ajoute la balise donnée à l'objet en cours

extract()

Extrait l'élément de l'arbre

find_next_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Renvoi les objects frères de l'objet en cours

find_parents(self, name=None, attrs={}, limit=None, **kwargs)

Renvoi les parents

find_all_previous(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne tous les items qui match avec le critère donné avant l'objet en cours

find_previous_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne les objets frères de l'objet en cours qui sont avant celui-ci

find_all_next(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne les objets qui correpondent à la recherche mais qui se situent après l'objet en cours

find_all_previous(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne les objets qui correspondent à la recherche mais qui se situent avant l'objet en cours

find_next(self, name=None, attrs={}, text=None, **kwargs)

Retourne le premier objet frère après l'objet en cours

find_next_sibling(self, name=None, attrs={}, text=None, **kwargs)

Retourne l'objet frère le plus proche après lui

find_next_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne les objets frères suivants

find_parent(self, name=None, attrs={}, **kwargs)

Retourne le parent le plus proche

find_parents(self, name=None, attrs={}, limit=None, **kwargs)

Retourne les parents

find_previous(self, name=None, attrs={}, text=None, **kwargs)

Retourne le premier item avant l'objet en cours

find_previous_sibling(self, name=None, attrs={}, text=None, **kwargs)

Retourne l'item frère le plus proche précédent l'objet en cours

find_previous_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne les items frères les plus proches précédents l'objet en cours

find_all_next(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne tous les items qui suivent l'objet en cours

find_all_previous(self, name=None, attrs={}, text=None, limit=None, **kwargs)

Retourne tous les items qui précédent l'objet en cours

Exemple d'utilisation

J'avais besoin d'un parseur HTML pour mettre en forme et colorer le code que je présente sur ce site; je partage ce petit script:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import glob
from bs4 import BeautifulSoup
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
import sys

def pygments_file(pathname):

    with open(pathname, "r" ) as f:
        html_doc = f.read()
        
    soup = BeautifulSoup(html_doc)
    
    for pre in soup.find_all('pre'):
        
        try:
            if "code" in pre.get("class"):
                texte = highlight(pre.get_text(), PythonLexer(), \
                HtmlFormatter(nowrap=True))
                n = BeautifulSoup('<pre class="code">%s</pre>' % texte)        
                pre.replace_with(n.body.contents[0])
        except:
            print "Erreur avec %s" % pre
        
    if soup.body:
        with open(pathname, "w") as f:
            f.write(soup.body.encode_contents())
       
p = "/home/olivier/*.html"

if sys.argv[1]:
    p = str(sys.argv[1])

pathnames = glob.glob(p)
for pathname in pathnames:
    pygments_file(pathname)

Vous pouvez aussi bien lui renseigner un dossier qu'un seul fichier.