Introduction

Dans cet article, nous explorons la création d’un script Python conçu pour transformer automatiquement des corpus de presse au format HTML, provenant d’Europresse, en un format compatible avec le logiciel d’analyse textuelle IRAMUTEQ.
Ce script est une réponse « aux défis » posés par les diverses structures de fichiers rencontrées.

Contexte et problématique

Lors de l’exportation depuis Europresse, les fichiers HTML ne présentent pas une structure uniforme, rendant difficile l’identification automatique des éléments clés comme le nom du journal, la date, l’auteur, le titre, le chapeau (chapô), etc.
La variabilité et le manque de clarté de la structure du « chapeau » par exemple compliquent davantage cette tâche. On aurait aimer trouver une structure éditoriale classique du type :

  • Nom du journal
  • Date
  • Auteur
  • Titre
  • Sous-titre
  • Chapeau (Chapô)
  • Texte-article
  • Illustration(s)-note(s)

Décryptage du fichier HTML : Identification et exclusion des balises non pertinentes

Cette étape initiale d’analyse du code source du fichier HTML est cruciale pour identifier toutes les balises contenant des informations non essentielles à notre objectif.
Voici un aperçu des balises spécifiques qui seront exclues du processus d’importation.

<head>
<div class="rdp__attachnews">
<div class="ImageAttach">
<div class="ImageAttach">
<div class="ImageAttachFont">
<a name="complement"></a>
<div id="entityList">
<div class="icon-logo-container">
<div class="Doc-LegalInfo">
<div id="divPubliC" class="rdp__certificat">
<div class="rdp__DocPublicationName">
<span class="DocPublicationName">
<div class="rdp__DocHeader">
<span class="DocHeader">
<div class="apd-wrapper">
<div class="apd-doc">
<div class="apd-title"><span class="apd-label">
<div class="apd-sources">
<span class="source-name-APD">
<aside>
<footer>
<img class="sm-margin-bottom">

Un atout majeur du script réside dans sa capacité à éliminer les données présentes dans l’en-tête de l’article, similaires à la balise « head » en HTML, ainsi que tout contenu situé en bas de l’article, y compris les « notes » et « illustrations », qui s’apparentent à la zone « footer » en HTML.

 

Pour que le script fonctionne 😉

Pour commencer, assurez-vous de disposer d’un environnement Python 3 sur votre machine. Ensuite, installez la bibliothèque BeautifulSoup4 en utilisant la commande pip install BeautifulSoup4.
Une fois cela fait, intégrez le code du script dans le fichier main.py de votre projet.

Il est important de personnaliser la fin du script en y indiquant le chemin d’accès à votre fichier HTML.

# Exemple d'utilisation 
chemin_html = '/chemin/vers/votre/fichier/monfichier.html' 
chemin_txt = '/chemin/vers/votre/fichier/monfichier.txt' 
extraire_texte_html(chemin_html, chemin_txt)

Si vous utilisez un Mac, une astuce pratique consiste à ouvrir le « Terminal » et à y glisser-déposer votre fichier afin d’en récupérer facilement le chemin complet.

Comme le fichier texte n’existe pas encore (étant donné que le script n’a pas été exécuté), vous pouvez indiquer le même chemin d’accès que celui de votre fichier HTML, en veillant simplement à changer l’extension du fichier de .html par .txt.

 

Variable : Nom de l’auteur

Ce script est une version améliorée par rapport à un traitement initial réussi sur un seul article.
Une modification notable est l’omission du nom de l’auteur en raison de son placement et formatage incohérents dans les fichiers HTML, rendant cette information peu fiable pour l’analyse. J’ai opté pour ne pas intégrer le nom de l’auteur dans le processus d’importation des données. Dans les fichiers HTML : parfois il apparaît en tête d’article, d’autres fois en conclusion, ou même intégré de manière indistincte au sein du texte. De plus, la présence d’articles co-rédigés complexifie davantage la situation, générant des noms d’auteurs parfois confus et peu exploitables pour une analyse structurée.
Il est donc important de noter que le script, tel qu’il est conçu actuellement, ne récupère et n’affiche pas les noms des auteurs.

 

Affichage des variables illustratives *

Le script offre la possibilité d’automatiser l’affichage de variables étoilées en premières lignes et inclut une option pour convertir l’ensemble du corpus en minuscules, ce qui est recommandé pour uniformiser l’analyse.
Cette fonctionnalité peut être activée en dé-commentant une ligne spécifique dans le code. (ITAMUTEQ est en capacité de réaliser cette conversion texte en minuscules)

Le script affiche en première ligne de tous les articles les variables étoilées suivantes :

**** *source_nomdujournal *date_2023-12-22 *am_2023-12 *annee_2023

Il aurait été possible de définir le contenu du nom de la variable journal_1 = texte_1 , journal_2 = texte_2, journal_3 = texte_3 … et de redéfinir le texte pour afficher un nom plus lisible.
Par exemple, pour les variables journal_1, journal_2, journal_3 contenant des noms de journaux ou des textes associés, afin de les redéfinir pour qu’elles contiennent des noms ou des textes plus courts ou différents.
Cela nécessiterait de copier / coller la listes des noms des quotidiens depuis la balise HTML et de les traiter en les renommant.

 

# Définition initiale des variables 
journal_1 = "Le Monde - Édition du 15 Avril" 
journal_2 = "The New York Times - International Edition" 
journal_3 = "Mon journal qui a un nom de journal beaucoup trop long que je voudrais simplifier" 

# Redéfinition des variables avec des noms plus courts 
journal_1 = "Le Monde" 
journal_2 = "NY Times" 
journal_3 = "Mon journal" 

# Affichage des nouveaux noms 
print(journal_1) 
print(journal_2) 
print(journal_3)
Le script ne fonctionne pas ainsi (pour le moment) il se contente donc d’afficher le nom du journal tel qu’il apparait dans le fichier HTML d’Europresse en remplaçant les espaces des noms composés par des underscores (_).
Par exemple, pour le quotidien « l’Obs » le formatage avec la syntaxe de l’underscore à la place des espaces ainsi que l’ajout de *source_ permet d’obtenir : *source_L_Obs
Enfin, l’évolution du script offrira la possibilité d’ajouter (si nécessaire), en premières lignes, des variables étoilées définies par l’utilisateur.

Suppression des liens web dans les articles

Le script est conçu pour éliminer tous les liens web présents dans les paragraphes des articles.
Cette fonctionnalité est particulièrement utile pour traiter du journal comme « La Tribune », où l’abondance de ces liens nécessitait une règle spécifique pour les supprimer.

 

# Nettoyer les expressions de liens
texte_article = re.sub(r'\(lien : https?://[^)]+\)', '', texte_article)

Gestion des termes spécifiques tels que les « Éditos »

Un problème rencontré est la présence de termes comme « Opinions » ou « La chronique » intégrés dans un élément <div> sans attribut class, rendant leur suppression délicate. Je n’ai pas trouvé de solution efficace pour supprimer cette balise. La solution temporaire est d’utiliser un éditeur de texte pour les supprimer manuellement.

Voici une liste des termes qui ne sont pas supprimés  (la liste n’est pas exhaustive) : « AUTRE », « Opinions », « La chronique », « Le point de vue », « ANTICIPATION », « Tech », « Une-ECO », « spécial ia france », « Edito »

Il est important de noter que le script, tel qu’il est conçu actuellement, ne supprime pas ces termes.

Nettoyage et optimisation du corpus

L’exportation depuis Europresse peut entraîner des défauts tels que des mots coupés ou des signatures multiples de l’auteur.
Ces anomalies requièrent un nettoyage manuel. Par exemple le « chapeau » est parfois dupliqué selon la structure utilisée par différents journaux, nécessitant une attention particulière lors du nettoyage.

Ces problèmes proviennent de l’export d’Europresse avec parfois des mots coupés et des retours à la ligne visibles dans le fichier html. Dans ces cas, il s’avère difficile d’apporter des corrections automatiques via le script Python.

Le script dans son intégralité

Le script fonctionne, il a été testé sur différents corpus d’Europresse. Je m’efforce d’optimiser le script, en mettant l’accent sur l’amélioration de la fonctionnalité de nettoyage de textes, dans le but de renforcer son efficacité.
Comme le logiciel IRAMUTEQ, le script est libre d’utilisation, encourageant la collaboration, le partage et l’innovation.

En le partageant avec la communauté, j’espère qu’il évoluera grâce aux contributions de tous, rendant l’analyse de texte plus efficiente en rendant le temps de « toilettage » des corpus Europresse moins chronophage.

 

#############################
#
# Titre du Script : [Traitement des corpus Europresse pour IRAMUTEQ]
# Auteur : Stéphane Meurisse
# Date : 28/12/2023
#
# Ce script est un logiciel libre : vous pouvez le redistribuer et/ou le modifier
# selon les termes de la GNU (version 3)
#
# Ce script est distribué dans l'espoir qu'il sera utile,
# mais SANS AUCUNE GARANTIE
#
#############################

from bs4 import BeautifulSoup
import re
import html
from datetime import datetime
import locale

# Définir la locale pour interpréter les dates en français
locale.setlocale(locale.LC_TIME, 'fr_FR')  # ou 'fr_FR.utf8'

def nettoyer_nom_journal(nom_journal):
    # Extraire uniquement la partie avant la virgule (si elle existe)
    nom_journal_sans_numero = nom_journal.split(",")[0]
    # Remplacer les espaces et apostrophes par des underscores - supprimer le n° dans le nom
    nom_journal_sans_numero = re.sub(r"[ ']", "_", nom_journal_sans_numero)
    return f"*source_{nom_journal_sans_numero}"

def extraire_texte_html(chemin_html, chemin_txt):
    with open(chemin_html, 'r', encoding='utf-8') as fichier:
        contenu_html = fichier.read()

    soup = BeautifulSoup(contenu_html, 'html.parser')
    articles = soup.find_all('article')
    texte_final = ""

    for article in articles:
        # Suppression des balises non nécessaires
        for element in article.find_all(["head", "aside", "footer", "img", "a"]):
            element.decompose()

        for element in article.find_all("div", class_=["apd-wrapper"]):
            element.decompose()

        for element in article.find_all("p", class_="sm-margin-bottomNews"):
            element.decompose()

        # Extraire et nettoyer le nom du journal
        span_journal = article.find("span", class_="DocPublicationName")
        nom_journal_formate = ""
        if span_journal:
            nom_journal = span_journal.get_text(strip=True)
            nom_journal_formate = nettoyer_nom_journal(nom_journal)
            # Supprimer la balise pour qu'elle n'apparaisse pas dans le texte de l'article
            span_journal.parent.decompose()

        # Extraire la date
        span_date = article.find("span", class_="DocHeader")
        date_texte = html.unescape(span_date.get_text()) if span_date else ""
        date_formattee = am_formattee = annee_formattee = ""
        if span_date:
            match = re.search(r'\d{1,2} \w+ \d{4}', date_texte)
            if match:
                date_str = match.group()
                try:
                    date_obj = datetime.strptime(date_str, '%d %B %Y')
                    date_formattee = date_obj.strftime('*date_%Y-%m-%d')
                    am_formattee = date_obj.strftime('*am_%Y-%m')
                    annee_formattee = date_obj.strftime('*annee_%Y')
                except ValueError:
                    pass
            span_date.decompose()

        # Extraire le texte de l'article + saut de ligne après le Titre
        texte_article = ""
        for p in article.find_all("p", class_="sm-margin-TopNews titreArticleVisu rdp__articletitle"):
            texte_article += p.get_text(" ", strip=True) + "\n\n"

            texte_article += article.get_text(" ", strip=True)

        # Extraire le texte de l'article
        texte_article = article.get_text(" ", strip=True)

        # Traiter spécifiquement la balise <p class="sm-margin-TopNews titreArticleVisu rdp__articletitle">
        if article.find("p", class_="sm-margin-TopNews titreArticleVisu rdp__articletitle"):
            titre_article = article.find("p",class_="sm-margin-TopNews titreArticleVisu rdp__articletitle").get_text(strip=True)
            texte_article = texte_article.replace(titre_article, titre_article + "\n", 1)

        # Nettoyer les expressions de liens
        texte_article = re.sub(r'\(lien : https?://[^)]+\)', '', texte_article)

        # Traitement des lignes pour ajouter un point à la première ligne et supprimer l'espace en début de chaque ligne
        lignes = texte_article.splitlines()
        if lignes and not lignes[0].endswith('.'):  # Vérifier si la première ligne n'a pas déjà un point
            lignes[0] += '.'  # Ajouter un point à la fin de la première ligne

        # Supprimer les espaces en début de chaque ligne
        lignes = [ligne.strip() for ligne in lignes]

        # Rejoindre les lignes en une seule chaîne de texte
        texte_article = '\n'.join(lignes)

        # Conversion de tout le texte en minuscules -> Vous pouvez décommenter ligne du dessous
        # pour passer le texte en minuscule
        # texte_article = texte_article.lower()

        # Concaténer les informations en première ligne
        info_debut = f"**** {nom_journal_formate} {date_formattee} {am_formattee} {annee_formattee}\n"

        # Ajouter le texte traité de chaque article à texte_final
        texte_final += info_debut + texte_article + "\n\n"

    # Écrire le texte final dans le fichier de sortie
    with open(chemin_txt, 'w', encoding='utf-8') as fichier_txt:
        fichier_txt.write(texte_final)

# Exemple d'utilisation
chemin_html = 'chemin/vers/le/fichier.html'
chemin_txt = 'chemin/vers/le/fichier.txt'
extraire_texte_html(chemin_html, chemin_txt)

 

 

Conclusion

Le script fonctionne efficacement et peut être utilisé même avec un nettoyage minimal du corpus. Une approche itérative peut être bénéfique pour affiner le corpus, en particulier en retravaillant vos mots clés pour cibler des articles spécifiques et répondant mieux à votre problématique.
A l’issue de l’exécution du script, un ajustement manuel du dictionnaire d’Iramuteq reste nécessaire pour certains termes.
Vous pouvez toutefois réaliser cet ajustement par la fonction rechercher / remplacer dans l’éditeur de texte.
Ainsi les deux occurrences « intelligence » « artificielle » peuvent former une seule occurrence grâce à à la syntaxe de l’underscore -> « intelligence_artificielle ».
Pour cette partie de la gestion des expressions je vous encourage à vous référer à la documentation que vous trouverez sur le site d’Iramuteq.

Vos retours, commentaires et signalements de bugs sont vivement encouragés pour améliorer continuellement cet outil 😉

Édit 12/01/2024 => Ici le script version 2

2 commentaires

Laisser un commentaire

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