Extraire les données d’un tableau HTML

Il est assez confortable de lire des données présentées en tableau sur une page HTML, mais dans le cadre de projets de recherche la simple consultation des données est souvent insuffisante. Pour pouvoir traiter les données, il faut alors les extraire.

Voici une façon de le faire avec Python.

Quelques mots sur le tableau

Pour cet exemple, je vais récupérer le comparatif des 6 variantes pa-hng and hm nai présenté sur cette page Wiktionary.

Il illustre bien l’intérêt d’extraire les données du tableau, puisqu’il comporte plusieurs milliers de lignes, ce qui ne facilite pas la lecture. Par ailleurs, dans une perspective comparative on pourrait éventuellement avoir envie d’agencer différemment les lignes et colonnes du tableau.

En l’occurrence, la finalité sera ici de créer un fichier dans lequel les langues ne sont plus présentées en lignes mais en colonnes (on aura donc sur une même ligne le même mot dans chacune des variantes, ainsi que sa glose en chinois).

Ceci se fera en deux étapes :

  1. L’extraction du tableau complet, tel qu’il est présenté sur la page web.
  2. La sélection et le restructuration des données extraites, afin d’obtenir un nouveau tableau, organisé différemment.

Cet article présentera uniquement la première de ces deux étapes, qui auront toutes deux pour format de sortie un fichier CSV.

Le script Python

Le script a été écrit sous Python 3.7 et utilise les librairies suivantes :

Cet article explique la construction et le fonctionnement du script mais celui-ci n’y est pas intégralement reproduit. Le script complet et commenté (en anglais) ainsi que ses fichiers de sortie sont disponibles sur Github.

Accéder à l’URL

Avant de lire le contenu de la page, il faut vérifier que l’URL fonctionne. Je ne détaille pas ici la fonction qui va vérifier l’URL (elle n’est pas très complexe et vous pouvez la retrouver dans le code complet), mais elle sera appelée en premier lieu, avant de tenter de lire le contenu de la page.

Voici les premières lignes de la fonction avec laquelle la page sera parsée, qui prend en paramètre l’URL de la page :

def find_all_tables(url):
    if url_ok(url):
        uClient = uReq(url)
        page_html = uClient.read()
        uClient.close()

Pour le moment, la fonction accède à la page et en “lit” le code HTML, mais sans le parser. En l’état on ne peut pas exploiter la structure HTML.

Récupérer le tableau

On peut très facilement récupérer tous les tableaux de la page en complétant la fonction montrée ci-dessus.

def find_all_tables(url):
    if url_ok(url):
        uClient = uReq(url)
        page_html = uClient.read()
        uClient.close()
        page_soup = soup(page_html, "html.parser")
        tables = page_soup.find_all("table")
        return tables

Grâce à la librairie Beautiful Soup, 2 lignes suffisent pour parser le code HTML de la page web et récupérer tous les tableaux qui s’y trouvent.

L’appel à la fonction dans le code principal :

page_url = 'https://en.wiktionary.org/wiki/Appendix:Pa-Hng_comparative_vocabulary_list'
all_tables = find_all_tables(page_url)

Dans le cas présent, on ne veut que le premier tableau de la page, qui sera donc à l’index 0 de la liste de résultats, stockée dans la variable all_tables.

table_to_extract = all_tables[0]

table_to_extract contient à présent tout le code HTML correspondant au tableau. L’idée étant ici d’enregistrer les données du tableau dans un fichier CSV, le traitement n’est pas terminé. Il faut récupérer le contenu textuel des cellules et le structurer pour obtenir un fichier CSV valide (ce sera particulièrement intéressant pour la suite du traitement).

Pour passer du tableau HTML au CSV, j’ai écrit une fonction qui prend 3 paramètres : le tableau HTML, le chemin du fichier de sortie et le séparateur. J’ai choisi la tabulation comme séparateur par défaut, par préférence personnelle mais aussi parce que la virgule et le point-virgule sont tous deux utilisés à l’intérieur des cellules (le fichier de sortie portera donc l’extension .tsv, mais .csv fonctionnerait aussi).

Voici le traitement pour la ligne d’en-tête du tableau :

def table_to_csv(table, output_file, delimiter="\t"):
    with open(output_file, "w") as output_file:
        header = ""
        th_set = table.find_all("th")
        for i in range(len(th_set)):
            th_data = th_set[i].text.rstrip('\n')
            if i == len(th_set) - 1:
                header += f"{th_data}\n"
            else:
                header += f"{th_data}{delimiter}"
        output_file.write(header)

La ligne d’en-tête est unique, donc il n’y a pas besoin de passer par une balise <tr>, les cellules sont récupérées directement grâce aux éléments <th>. Comme pour les tableaux extraits précédemment, tous ces éléments <th> sont stockés dans une liste, dont on peut exploiter l’index pour récupérer les données.

La boucle for parcourt chaque élément <th> (= chaque étiquette de la ligne d’en-tête) pour l’ajouter à la chaîne de caractères contenue dans la variable header, qui deviendra la ligne d’en-tête du fichier de sortie. La condition if qui intervient ensuite permet d’insérer soit un saut de ligne si l’élément est le dernier de la liste, soit le séparateur choisi s’il ne l’est pas.

Je reviens rapidement sur th_set[i].text.rstrip('\n') ; c’est ici qu’est extrait le contenu textuel de la cellule :

  • th_set[i] est la cellule en cours, c’est-à-dire l’élément à l’index i de la liste de résultats renvoyée par find_all("th") de Beautiful Soup.
  • Les objets Beautiful Soup ont un attribut text, il est appelé ici avec th_set[i].text. Cet attribut text permet de récupérer le contenu textuel de l’élément, débarrassé de son balisage HTML.
  • L’ajout de rstrip('\n') permet de retirer les sauts de ligne présents dans ce contenu textuel, sans quoi une même ligne pourrait se retrouver divisée en plusieurs lignes dans le fichier de sortie.
    Attention, cette solution n’est pas la meilleure dans tous les cas, puisqu’il peut arriver qu’on ait besoin de conserver les sauts de lignes à l’intérieur des cellules, par exemple si elles contiennent un texte assez long ou si ces sauts de ligne ont une signification. Dans ce cas, on pourrait définir th_data = th_set[i].text puis header += f'"{th_data}"\n' (et header += f'"{th_data}"{delimiter}'). Le contenu textuel maintenant entouré de guillemets, les sauts de lignes y seront conservés en tant que contenu de cellule et ne généreront pas de changement de ligne dans le CSV de sortie1.

Le traitement pour récupérer le contenu des cellules est très similaire, mais il ne faut pas oublier de l’appliquer ligne par ligne (en parcourant les résultats de tr_set = table.find_all("tr")) et non directement sur chaque élément <td>, puisque contrairement à la ligne d’étiquettes les lignes de données sont multiples.

Ne reste plus qu’à compléter le code principal pour définir le fichier de sortie et appeler cette fonction :

csv_file = 'Pa-Hng_and_Hm_Nai_dialects_FullTable.tsv'
table_to_csv(table_to_extract, csv_file)

Le fichier de sortie contiendra ainsi l’ensemble des données du tableau, non modifiées (à l’exception de la suppression des sauts de ligne), au format CSV (TSV). Il pourra être ouvert et manipulé dans un tableur, ou par d’autres moyens de traitement automatique.

Exploitation du tableau obtenu

Outre la lecture et la modification du fichier dans un tableur, il est maintenant possible d’exploiter les librairies Python consacrées aux fichiers CSV. Vous trouverez dans un prochain article le détail de la deuxième étape de traitement (cf les deux grandes étapes énoncées précédemment) : la restructuration du tableau.

Pour rappel, le script décrit ici est mis à disposition sur Github.

Citer ce billet : “Extraire les données d’un tableau HTML”, par Alexander Delaporte. Publié sur Tekipaki le 14/04/2020. Lien : https://tekipaki.hypotheses.org/14.
  1. Cet ajout de guillemets serait aussi indispensable si on souhaitait utiliser un séparateur par ailleurs présent dans le texte des cellules, par exemple la virgule. []

Auteur/autrice : Alexander Delaporte

Ingénieur d'études au Centre de recherches linguistiques sur l'Asie Orientale

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Rechercher dans OpenEdition Search

Vous allez être redirigé vers OpenEdition Search