Dans ce notebook nous utiliserons le parseur lxml qui est un binding de libxml2 et Beautiful Soup
Beautiful Soup nous permet de parser simplement du contenu html. Même si le contenu est mal formé, le module bs reconstitue un arbre et offre des fonctions faciles à utiliser pour parcourir l'arbre ou y rechercher des éléments.
Beautiful Soup n'est pas un parseur, il utilise les parseurs et offre une API simplifiée à ses utilisateurs.
Nous travaillerons directement avec du contenu en ligne. Fini les exercices bidons, cette fois nous allons nous confronter à une question essentielle : combien d'accordages open tuning Neil Young utilise et comment sont-ils répartis dans son oeuvre ?
On trouve les infos sur les chansons de Neil Young et les accordages sur le fabuleux site songx.se (le site ayant changé d'interface, nous utiliserons une archive de wayback archive)
Avec le module requests
que nous avons déjà utilisé, nous allons pouvoir instancier un objet Beautiful Soup sans trop d'efforts
import requests
from bs4 import BeautifulSoup
url = "http://web.archive.org/web/20180430090903/http://songx.se/index.php" # le lien vers le site
html = requests.get(url) # on récupère le contenu
soup = BeautifulSoup(html.text, 'lxml') # on crée un objet pour traiter la page
Voilà nous avons maintenant un objet soup
de classe Beautiful Soup.
La doc est très claire.
# je cherche l'élement avec le tag 'title'
print(soup.title)
# le tag de l'élément
print(soup.title.name)
# le contenu textuel de l'élément
print(soup.title.string)
Les informations qui nous intéressent sont contenues dans des éléments comme celui-ci (formatté pour la lisibilité) :
<div class="songrow">
<a href="?song=505">Clementine</a>
<small>(cover)</small>
<div style="float:right;">EADGBE</div>
</div>
Où on trouve 1. le nom de la chanson ('Clementine') et l'accord utilisé ('EADGBE')
Affichez les titres et les tunings des 10 premières chansons en utilisant la méthode find_all. La méthode renvoie un iterable.
for item in soup.find_all([...]):
print(item.[...], item.[...])
Créez un dict
appelé tunings
qui classe les chansons par tuning
# notre structure de données résultat
# un dictionnaire avec en clé l'accordage et en valeur la liste des chansons qui utilisent cet accordage
tunings = {}
for item in soup.find_all([...]):
song_title = item.[...]
tuning = item.[...]
[...]
'Harvest Moon' utilise l'accordage DADGBE, y en a-t'il d'autres ?
print([...])
Affichez les accordages et le nombre de chansons pour chaque accordage, le tout trié par nombre de chansons décroissant. Pour cela, cous utiliserez la méthode sorted
ainsi que l'argument mot-clé key
(des exemples ici) :
for tuning in sorted(tunings.keys(), key=[...]):
print([...])
Allez hop un histogramme
%matplotlib notebook
import matplotlib.pyplot as plt
values = [len(tunings[x]) for x in tunings]
values
plt.bar(range(0, len(values)), values)
plt.xticks(range(0, len(values)), tunings.keys(), rotation=17)
plt.show()
Exercice 1 : trouvez l'exception
Une chanson a un tuning un peu particulier comparé aux autres (indice, c'est une question de taille). Trouvez le tuning qui est différent des autres et donnez la chanson qui lui correspond. Attention, "b" signifie "bémol", il ne s'agit pas d'une note pour l'accordage (contrairement à A, B, C, D, E, F et G) !
Nous allons travailler sur un fichier au format TEI extrait du corpus Corpus 14
PRAXILING - UMR 5267 (PRAXILING) (2014). Corpus 14 [Corpus]. ORTOLANG (Open Resources and TOols for LANGuage) - www.ortolang.fr, https://hdl.handle.net/11403/corpus14/v1.
Le fichier se nomme josephine-1-150119.xml
. Il s'agit d'une lettre d'une femme de soldat à son époux.
Nous allons extraire du fichier TEI les informations suivantes :
/TEI/teiHeader/fileDesc/titleStmt/title
)/TEI/teiHeader/fileDesc/sourceDesc/p
)/TEI/text/body
)Vous pouvez trouver des indications sur les éléments de la TEI ici (ça pourra être utile pour les questions)
from lxml import etree
tree = etree.parse('josephine-1-150119.xml')
root = tree.getroot()
# Parcours des enfants de la racine (commentaires et éléments)
for child in root:
print(child.tag)
Le fichier utilise l'espace de nom TEI : <TEI xmlns="http://www.tei-c.org/ns/1.0">
, nous devrons l'indiquer dans nos instructions de recherche.
Nous pouvons récupérer un élément particulier qui correspond à un chemin : par exemple, pour récupérer le header TEI de dont le chemin est /TEI/teiHeader
# la méthode find renvoie le premier élément qui correspond au chemin argument (ElementPath et non Xpath)
header = root.find("./tei:teiHeader", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print(header)
Récupérez le titre et affichez son tag XML ainsi que son contenu textuel (/TEI/teiHeader/fileDesc/titleStmt/title
)
title = root.find("[...]", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print("Tag : {}".format(title.tag))
print("Texte : {}".format(title.text))
Idem pour la source (élément sourceDesc
) :
source = root.find("[...]", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print("Tag : {}".format(source.tag))
print("Texte : {}".format(source.text))
lxml a aussi une méthode xpath qui permet d'utiliser directement des expressions xpath (sans oublier les espaces de noms pour notre fichier) :
source = root.xpath("/tei:TEI/tei:teiHeader/tei:fileDesc/tei:sourceDesc/tei:p", namespaces={'tei':'http://www.tei-c.org/ns/1.0'})
print(type(source)) #xpath retourne une liste
print(source[0].text)
#ou bien
source = root.xpath("/tei:TEI/tei:teiHeader/tei:fileDesc/tei:sourceDesc/tei:p/text()", namespaces={'tei':'http://www.tei-c.org/ns/1.0'})
print(source[0])
Pour le contenu il faut ruser. La difficulté ici tient à l'utilisation d'élements <lb/>
de type milestones pour noter les retours à la ligne :
<p>
je reponse a ton aimableux lettres<lb/>
que nous a fait plaisir en naprenas<lb/>
que tu et enbonne santes car il<lb/>
anais de maime pour nous<lb/>
</p>
Récupérez dans un premier temps l'ensemble des balises <p>
en utilisant la méthode findall. la méthode findall
renvoie une liste avec tous les éléments correspondant au chemin argument.
body = root.findall("./[...]", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
for elem in body: # tout le texte ne s'affichera pas, c'est normal !
print(elem.text)
Ici on ne récupère que les noeuds text précédant les éléments <lb/>
.
Utilisez la fonction xpath
pour récupérer tous les noeuds text du corps de la lettre. Vous intégrerez dans votre requête la fonction text
(vue un peu plus haut) dans votre chemin xpath (vous pouvez aussi fouiller par ici pour avoir de la documentation supplémentaire).
body = root.xpath("[...]", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
for text in body:
print(text, end="")
Exercice 2
Écrivez une requête xpath pour récupérer tous les éléments raturés de la lettre de Joséphine.
L'API ElementTree
est propre à Python, DOM
(le site officiel et des informations en français) est une API indépendante d'un langage de programmation. Il existe des implémentations DOM
dans la plupart des langages de programmation modernes.
from xml.dom import minidom
dom = minidom.parse("josephine-1-150119.xml")
# l'objet Document
dom
title = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'title')[0] # un seul élément 'title' dans le document
print(title) # title est un objet Element, pour accèder au contenu textuel il faut récupérer le noeud texte
print(title.lastChild.nodeName)
print(title.lastChild.nodeValue)
idem pour la source, sauf qu'on ne peut pas se permettre de rechercher tous les éléments p
.
Il faut trouver l'élément p
fils de sourceDesc
sourceDesc = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'sourceDesc')[0]
for node in sourceDesc.childNodes:
if node.localName == "p":
print(node.lastChild.nodeValue)
Et maintenant le contenu et ses éléments milestones.
Pour garder la forme vous réécrirez les boucles for
suivies de if
en listes en intension.
body = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'body')[0]
for node in body.childNodes:
if node.localName == "p" or "opener":
for in_node in node.childNodes:
if in_node.nodeName == "#text":
print(in_node.nodeValue, end="")