Étiquetage morphosyntaxique avec Jieba

Mon travail sur le traitement d’un corpus écrit m’a amené à étiqueter le texte avec deux étiqueteurs morphosyntaxiques différents, PyNLPIR et Jieba. Un article a déjà été consacré à la marche à suivre pour réaliser l’étiquetage avec PyNLPIR, voyons maintenant comment faire avec Jieba.

Choisir un étiqueteur

Pour rappel, le choix de l’étiqueteur à utiliser n’est pas anodin dans la constitution d’un corpus textuel et aura un impact sur les analyses établies sur la base de ce corpus. En effet, les différents logiciels d’étiquetage n’appliquent pas les mêmes stratégies d’attribution des étiquettes mais aussi de segmentation du texte : ceci est visible même sur un (très) court extrait.

L’étiquetage avec Jieba

Le script ne sera pas intégralement reproduit ici. Vous pouvez retrouver le script complet, son fichier d’entrée et son fichier de sortie sur Github.

Jieba propose un certain nombre de fonctions (détaillées dans sa documentation), donc il n’est pas nécessaire d’importer toute la librairie mais seulement jieba.posseg pour l’étiquetage.

import jieba.posseg as pseg

Réaliser l’étiquetage est alors extrêmement simple, il suffit d’appeler la fonction cut() avec pour argument la chaîne de caractères à traiter. Comme pour PyNLPIR, j’ai choisi de parcourir le fichier ligne par ligne en gardant la ligne courante dans une variable appelée line :

words = pseg.cut(line)

Le résultat renvoyé ici est une liste d’éléments de type item/étiquette, ce qui est déjà proche de la sortie souhaitée. La liste est simplement parcourue pour pouvoir ajouter un espace de séparation et réinsérer un saut de ligne en fin de chaîne.

            tagged_segment = ""
            for w in words:
                tagged_segment += f"{w} "
            tagged_segment = f"{tagged_segment}\n"

Ceci permet d’obtenir une sortie semblable à l’extrait montré en fin d’article (et au fichier de sortie complet). Les autres fonctions de Jieba permettent de contrôler assez finement les résultats obtenus, mais en l’état il est déjà possible d’ajuster la structure de la sortie. J’ai ici nommé w1 la variable contenant l’ensemble item/étiquette : on peut alors récupérer l’item étiqueté (le « mot ») avec w.word, et son étiquette avec w.flag.

Ce n’est pas ce qui a été fait ici, mais on pourrait tout à fait produire quelque chose comme ceci :

message = f"Le mot {w.word} a été étiqueté {w.flag} par Jieba."

Qui aurait par exemple pour sortie :

Le mot 企鹅 a été étiqueté n par Jieba.

Cette solution n’est toutefois pas la meilleure si on souhaite obtenir un texte segmenté mais non étiqueté, puisque la segmentation sans étiquetage est l’une des utilisations prévues de la fonction cut de Jieba. Ceci est détaillé et illustré par des exemples dans la documentation des fonctions de Jieba.

Comparatif PyNLPIR vs Jieba

Pour le détail de l’étiquetage avec PyNLPIR, voir cet article.

Les deux étiqueteurs utilisent le même jeu d’étiquettes, dont la liste est disponible sur cette page. Vous pouvez consulter le fichier dont est issu cet extrait sur Github : version PyNLPIR et version Jieba.

PyNLPIR

本/rz 條/n 目/ng 存在/v 以下/f 問/n 題/n ,/wd 請/n 協/n 助/v 改善/v 本/rz 條/n 目/ng 或/c 在/p 討/n 論/n 頁/n 針/n 對/n 議/n 題/n 發/n 表/vg 看法/n 。/wj

Jieba

本/r 條目/n 存在/v 以下/f 問題/n ,/x 請/v 協助/v 改善/v 本/r 條目/n 或/c 在/p 討論/v 頁/m 針對/p 議題/n 發表/v 看法/v 。/x
Citer ce billet : “Étiquetage morphosyntaxique avec Jieba”, par Alexander Delaporte. Publié sur Tekipaki le 20/05/2020. Lien : https://tekipaki.hypotheses.org/115.
  1. J’ai simplement conservé le nommage des variables de l’exemple présenté dans la documentation de Jieba. []

Étiquetage morphosyntaxique avec PyNLPIR

L’étiquetage morphosyntaxique (ou POS-tagging) est une étape habituelle dans la constitution d’un corpus textuel, assez étroitement liée à la langue dans laquelle le texte est écrit.

Il n’y a pas très longtemps, j’ai réalisé deux versions de l’étiquetage d’un corpus écrit en chinois, sous Python. Je vais donc partager successivement la façon dont j’ai procédé avec chacun des deux étiqueteurs, à savoir PyNLPIR (implémentation Python du logiciel NLPIR/ICTCLAS) et Jieba.

Deux étiqueteurs ?

Je précise que j’en suis venu à utiliser deux étiqueteurs différents sur le même corpus pour les besoins du projet sur lequel je travaillais, mais bien entendu en général on peut en choisir un seul en amont selon le résultat souhaité. Le comparatif entre PyNLPIR et Jieba proposé en fin d’article vous donne un aperçu des différences entre ces deux étiqueteurs. Vous pouvez d’ailleurs remarquer qu’il y a des divergences dans le choix des étiquettes appliquées, mais aussi au niveau de la segmentation (on a par exemple 問/n 題/n avec PyNLPIR mais 問題/n avec Jieba).

Voici d’abord la procédure suivie pour réaliser la segmentation et l’étiquetage avec PyNLPIR.

L’étiquetage avec PyNLPIR

Je ne commente ici que certaines parties du script permettant de réaliser l’étiquetage. Le script complet, son fichier d’entrée et son fichier de sortie sont disponibles sur Github.

Pour cet exemple, j’ai utilisé une extraction du texte principal de cette page Wikipedia (皇帝企鹅, manchot empereur)1. Pour pouvoir utiliser les fonctions de la librairie PyNLPIR, il ne faut pas oublier d’ouvrir (et refermer) l’API via les fonctions open() et close(). Le script de base ressemblera donc à ceci :

import pynlpir

pynlpir.open()
# (...)
pynlpir.close()

Pour segmenter et étiqueter le texte, il suffit de faire appel à la fonction segment() : celle-ci prend en premier argument la chaîne de caractères à traiter, et en deuxième argument le type d’étiquetage souhaité. Ici, je veux traiter le fichier ligne par ligne (j’ai créé une variable line pour cet usage) en lui appliquant l’étiquetage de base (c’est-à-dire les abréviations listées sur cette page) :

postag = pynlpir.segment(line, pos_names='raw')

Comme l’indique la documentation de PyNLPIR, on peut choisir assez précisément le type d’étiquettes souhaitées : il est par exemple possible d’avoir des étiquettes en chinois plutôt qu’en anglais (pos_english=False), ou même simplement segmenter le texte sans l’étiqueter (pos_tagging=False).

Techniquement à ce stade l’étiquetage est fait. Cependant, en fonction du résultat souhaité le traitement ne s’arrête pas forcément là, parce que ce que retourne la fonction segment(), c’est une liste de tuples. En sachant ça, il n’est pas très compliqué de traiter cette liste pour obtenir n’importe quelle structure pour les données de sortie.

Pour avoir quelque chose de type item/étiquette comme dans l’extrait ci-dessous, j’ai procédé comme ceci :

for word, part_of_speech in postag:
    try:
        tagged_word = f"{word}/{part_of_speech} "
    except TypeError:
        tagged_word = f"{word}/None "

Il y a deux choses à remarquer :

  • Pour certains items il n’y a pas d’étiquette disponible, ce qui fera planter le script puisqu’il essaiera alors de récupérer un élément qui n’existe pas (et aura donc le type None). Récupérer les erreurs (TypeError) permet d’éviter ce problème (ici en créant une étiquette None).
  • Dans mon fichier de sortie les groupes item/étiquette sont séparés par un espace. Ce n’est pas généré par l’étiqueteur, je l’ai moi-même ajouté en l’intégrant à la chaîne de caractère associée à la variable tagged_word.

Comparatif PyNLPIR vs Jieba

La description du processus d’étiquetage avec Jieba sera disponible prochainement.

Les deux étiqueteurs utilisent le même jeu d’étiquettes, dont la liste est disponible sur cette page. Vous pouvez consulter le fichier dont est issu cet extrait sur Github : version PyNLPIR et version Jieba (à venir).

PyNLPIR

本/rz 條/n 目/ng 存在/v 以下/f 問/n 題/n ,/wd 請/n 協/n 助/v 改善/v 本/rz 條/n 目/ng 或/c 在/p 討/n 論/n 頁/n 針/n 對/n 議/n 題/n 發/n 表/vg 看法/n 。/wj

Jieba

本/r 條目/n 存在/v 以下/f 問題/n ,/x 請/v 協助/v 改善/v 本/r 條目/n 或/c 在/p 討論/v 頁/m 針對/p 議題/n 發表/v 看法/v 。/x
Citer ce billet : “Étiquetage morphosyntaxique avec PyNLPIR”, par Alexander Delaporte. Publié sur Tekipaki le 13/05/2020. Lien : https://tekipaki.hypotheses.org/117.
  1. Le texte des pages Wikipedia est disponible sous licence CC BY-SA 3.0 []

Lexique linguistique en japonais – 02

Encore une fois, quelques mots repérés pendant une lecture de dictionnaire bilingue. Comme pour le précédent article, les prononciations sont indiquées en kana ainsi qu’en en rōmaji (transcription Hepburn) pour qu’elles soient accessibles même sans apprentissage des hiragana et katakana.

Les mots et équivalents traductionnels présentés ici viennent du dictionnaire suivant :

  • Sanseido’s New Concise English-Japanese Dictionary, Revised Edition | 新コンサイス英和辞典・第2版 (1985). Tokyo : Sanseido.

Il s’agit d’un dictionnaire anglais-japonais (dont j’ai montré quelques photos sur Instagram, que vous pouvez retrouver ci-dessous, après le tableau). J’ai ici traduit l’anglais en français pour préserver la cohérence avec le précédent article consacré au lexique linguistique du japonais, qui proposait uniquement des équivalents en français.

Les tableaux des articles sur le lexique linguistique en japonais sont téléchargeables sur Github, compilés dans un fichier TSV.

副詞ふくしfukushiadverbe
接続詞せつぞくしsetsuzokushiconjonction
定冠詞ていかんしteikanshiarticle défini
不定冠詞ふていかんしfuteikanshiarticle indéfini
男性形だんせいけいdanseikeimasculin
女性形じょせいけいjoseikeiféminin
間投詞かんとうしkantoushiinterjection
過去形かこけいkakokeipassé (temps verbal)
過去分詞かこぶんしkakobunshiparticipe passé
単数形たんすうけいtansuukeisingulier
複数形ふくすうけいfukusuukeipluriel
接頭辞せっとうじsettoujipréfixe
接尾辞せつびじsetsubijisuffixe
前置詞ぜんちしzenchishipréposition
関係代名詞かんけいだいめいしkankeidaimeishipronom relatif
動詞どうしdoushiverbe
他動詞たどうしtadoushiverbe transitif
自動詞じどうしjidoushiverbe intransitif
助動詞じょどうしjodoushiverbe auxiliaire
最上級さいじょうきゅうsaijoukyuusuperlatif
Le dictionnaire utilisé pour cet article, présenté sur Instagram.
Citer ce billet : “Lexique linguistique en japonais – 02”, par Alexander Delaporte. Publié sur Tekipaki le 05/05/2020. Lien : https://tekipaki.hypotheses.org/97.

Revue Koko

Il reste 15 jours pour financer ce projet de première revue interculturelle bilingue français-japonais (日本語とフランス語でカルチャーを紹介する初めての雑誌). Si l’objectif est atteint, le premier numéro sortira en juin, comportera 132 pages et sera consacré au thème de l’art urbain.

Koko se veut une passerelle entre la France et le Japon
Kokoはフランスと日本の懸け橋になりたいと考えています

Chaque numéro sera l’occasion d’explorer un thème et d’en révéler les spécificités culturelles grâce à une série d’articles bilingues : cette approche interculturelle et multilingue constitue l’identité et l’originalité de notre revue.

毎号2か国語で書かれた記事によって、テーマに沿ったカルチャーの特徴を掘り下げ、文化的な面から多言語によるアプローチで当雑誌の個性と独創性を表現します。

Présentation du projet sur la page Kickstarter

Retrouvez une présentation plus détaillée sur le site web de la revue Koko.

Citer ce billet : “Revue Koko”, par Alexander Delaporte. Publié sur Tekipaki le 01/05/2020. Lien : https://tekipaki.hypotheses.org/260.

Restructurer un tableau CSV

Le module csv intégré par défaut à Python permet de lire et écrire facilement des fichiers CSV. Voyons ici comment modifier un tableau de données préalablement récupéré sur une page web, avec la seconde étape de traitement du tableau des variantes pa-hng and hm nai.

Fichier d’entrée

Le fichier que j’utilise ici en entrée est le fichier de sortie du script présenté dans l’article Extraire les données d’un tableau HTML. Le script et son fichier de sortie sont disponibles sur Github.

Il s’agit d’un tableau de données au format TSV, reprenant telles quelles les données du premier tableau présenté sur cette page Wiktionary.

Pourquoi transformer ce fichier ?

Le tableau en entrée comporte 4 colonnes : Chinese gloss, Language (location), Word et Page no. La même glose en chinois se retrouve donc sur autant de lignes qu’elle a d’équivalents dans les différentes variantes présentées, soit 6 lignes au maximum. Cette représentation fonctionne bien si on souhaite comparer l’une de ces variantes au chinois mais n’est peut-être pas la plus adaptée lorsqu’il s’agit de comparer ces 6 variantes entre elles.

Dans cette perspective, la restructuration du tableau aura pour but de représenter sur une même ligne la glose en chinois, et tous ses équivalents dans les langues pa-hng et hm nai. Les étiquettes de colonnes seront donc les suivantes : Chinese gloss, Pa Hng (Guangxi Wenjie), Pa Hng (Guizhou Gundong), Hm Nai (Longhui Huxingshan), Hm Nai (Tongdao Chuansuo), Hm Nai (Longhui Xiaoshajiang) et Hm Nai (Xupu).

Le tableau est long et toutes les données dont on a besoin s’y trouvent déjà, il y a donc tout intérêt à le traiter automatiquement.

Le script Python

Aucune librairie à installer pour les besoins de ce script (écrit sous Python 3.7), seul le module csv a été utilisé.

Cet article détaille la construction du script mais ce dernier n’y est pas reproduit dans son intégralité. Le script complet et commenté (en anglais) ainsi que ses fichiers de sortie sont disponibles sur Github.

Recréer la ligne d’en-tête

Le fichier d’entrée, dont le chemin d’accès est stocké dans la variable full_table, est lu grâce à une instance de la classe DictReader.

reader = csv.DictReader(full_table, delimiter='\t') 

La classe DictReader, comme son nom le laisse deviner, lit les données du tableau sous forme de dictionnaire. Ce qui va permettre de constituer la nouvelle ligne d’en-tête en créant une liste alimentée par row['Language (location)'] pour chaque ligne (row désignant la ligne courante).

    languages = ['Chinese gloss']
    # (...)
    for row in reader:
        language = row['Language (location)'].replace("]", "")
        if language not in languages:
            languages.append(language)
    full_table.seek(0)

L’instruction full_table.seek(0) permet de remonter au début du fichier lorsque ce traitement est terminé (les étapes de traitement suivantes ne fonctionneront pas sinon).

Pour écrire cette ligne d’en-tête dans le fichier de sortie (new_table), on crée une instance de la classe DictWriter, qui comme DictReader s’appuie sur des données structurées en dictionnaire.

    writer = csv.DictWriter(new_table, fieldnames=languages, delimiter='\t')
    writer.writeheader()

L’objet Dictwriter a reçu en paramètre fieldnames la liste des langues obtenue précédemment : les éléments de cette liste peuvent maintenant être utilisés comme clés pour insérer des valeurs dans les cellules du tableau.

Remplir les lignes de données

Dans ce nouveau tableau, chaque ligne de données doit représenter le même mot dans toutes les langues où il est disponible dans le tableau d’origine.

La solution que j’ai adoptée est de créer une liste des gloses en chinois (de la même manière que la liste des langues pour la ligne d’en-tête) qui sera parcourue pour traiter chaque glose une à une. Il faut maintenant reconstituer la ligne de données correspondant à cette glose, en récupérant dans reader (lecture des données du tableau d’origine) les valeurs qui seront transmises à writer (écriture du tableau restructuré).

Il ne faut pas oublier que reader et writer fonctionnent avec des données structurées en dictionnaire. La ligne de données sera donc un dictionnaire, avec pour clés les étiquettes de la ligne d’en-tête (= les différentes langues) et pour valeurs les différents équivalents de la glose en chinois.

Concevoir la ligne de données comme un dictionnaire est alors très pratique, puisqu’il suffira de l’alimenter avec une syntaxe de type dictionnaire[langue] = mot. Or, la langue et le mot peuvent être récupérés sans problème grâce à reader.

Pour une ligne de données, le traitement est donc le suivant :

mini_dico = {}
    for gloss in glosses:
        mini_dico['Chinese gloss'] = gloss
        for row in reader:
            if row['Chinese gloss'] == gloss:
                lang = row['Language (location)'].replace("]", "")
                word = row["Word"]
                mini_dico[lang] = word
        writer.writerow(mini_dico)
        mini_dico = {}
        full_table.seek(0)

Bien sûr il ne faut pas oublier d’ajouter la ligne au fichier de sortie grâce à la méthode writerow() (dont le nom est assez explicite). Il est aussi très important de vider le dictionnaire avant de passer à la ligne suivante (mini_dico = {}) : si ce n’est pas fait, quand une glose n’a pas d’équivalent dans une variante donnée, la valeur précédente restera en mémoire et sera reportée dans une cellule qui aurait dû rester vide.

Le script complet et son fichier de sortie sont librement téléchargeables. Je mets aussi à disposition une autre version du tableau retravaillé, intégrant des colonnes pour les numéros de page (avec script et fichier de sortie).

Citer ce billet : “Restructurer un tableau CSV”, par Alexander Delaporte. Publié sur Tekipaki le 28/04/2020. Lien : https://tekipaki.hypotheses.org/64.

Lexique linguistique en japonais – 01

Quelques termes trouvés « par hasard » lors de l’étude de dictionnaires bilingues japonais, qui peuvent être utiles pour parler/écrire à propos de linguistique. J’indique les prononciations en kana mais également en rōmaji pour qu’elles soient accessibles même sans connaître les hiragana et katakana. Pour ce faire, j’ai utilisé la transcription Hepburn (ヘボン式ローマ字).

Le lexique présenté dans cet article provient majoritairement des dictionnaires suivants :

  • KURAKATA et al. (2003) プチ・ロワイヤル仏和辞典 (Nouveau Petit Royal dictionnaire français -japonais), troisième édition. Tokyo : Obunsha.
  • TSUNEKAWA et al. (2002) Petit dictionnaire japonais – français Royal. Tokyo : Obunsha.

Les tableaux présentés sur cette page sont téléchargeables sur Github, compilés dans un fichier TSV.

Dans le dictionnaire

Utiles dans le cadre de la lecture d’un dictionnaire.

付録ふろくfurokuappendice
辞典じてんjitendictionnaire, lexique
英和辞典えいわじてんeiwa jitendictionnaire anglais-japonais
英英辞典えいえいじてんeiei jitendictionnaire anglais-anglais
仏和辞典ふつわじてんfutsuwa jitendictionnaire français-japonais
和仏辞典わふつじてんwafutsu jitendictionnaire japonais-français
辞書じしょjishodictionnaire, lexique
辞書形じしょけいjishokeiforme de citation
電子辞書でんしじしょdenshi jishodictionnaire électronique
見出しみだしmidashientrée (de dictionnaire), table des matières
省略しょうりゃくshōryakuabréviation
リエゾンリエゾンriezonliaison
エリジョンエリジョンerijonélision

Entités linguistiques

Désignent des entités ou catégories d’entités linguistiques.

形容詞けいようしkeiyōshiadjectif (peut s’abréger en 形)
代名詞だいめいしdaimeishipronom (peut s’abréger en 代)
総称そうしょうsōshōterme générique, emploi générique
冠詞かんしkanshiarticle (peut s’abréger en 冠)
主格関係しゅかくかんけいshukaku kankeinominatif (déclinaison)
対象たいしょうtaishōobjet (rôle sémantique ~patient)
非人称表現ひにんしょうきょうげんhininshō hyōgentournure impersonnelle

Communication

Désignent des entités du schéma de communication.

話し手はなしてhanashitelocuteur (émetteur)
聞き手ききてkikiteinterlocuteur, auditeur (récepteur)

Emploi spécialisé

Ne sont pas directement liés à la linguistique ou aux dictionnaires mais ont un emploi utile dans un de ces domaines.

物質ぶっしつbusshitsumatière, substance
変化へんかhenkatransformation (par exemple, de l’article des en de en présence de négation ou d’adjectif épithète antéposé)
対比たいひtaihicontraste
つねtsunenormal, habituel (pour parler d’une forme régulière par exemple)
Citer ce billet : “Lexique linguistique en japonais – 01”, par Alexander Delaporte. Publié sur Tekipaki le 21/04/2020. Lien : https://tekipaki.hypotheses.org/69.

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. []

TEKIPAKI

Ce carnet présente les travaux menés dans le cadre de l’ingénierie informatique pour le Centre de recherches linguistiques sur l’Asie Orientale (CRLAO), ainsi que quelques réflexions plus personnelles à propos de lexicographie, traitement automatique des langues, apprentissage des langues d’Asie Orientale (japonais en particulier).

Citer ce billet : “TEKIPAKI”, par Alexander Delaporte. Publié sur Tekipaki le 01/04/2020. Lien : https://tekipaki.hypotheses.org/1.