Introduction

Avant de plonger dans les détails techniques, voici pour mémo les prérequis pour faire fonctionner le script :

  1. Environnement virtuel Python : Assurez-vous d’avoir configuré un environnement virtuel Python (le langage python n’est pas installé par defaut sur Mac et PC).
  2. Installation de BeautifulSoup4 : Cette bibliothèque est indispensable pour le fonctionnement du script (cf article).
  3. Lancement via un éditeur de code : Utilisez un éditeur de code tel que PyCharm pour exécuter le script. Actuellement, le script n’est pas compilé ni exécutable directement, mais cela pourrait changer dans une version future.

Pour mieux comprendre le fonctionnement du script, je vous invite à consulter les deux premiers articles (ici et ici) que j’ai publiés à ce sujet.

 

Une interface graphique simplifiée

L’une des améliorations les plus significatives de cette version est l’introduction d’une interface graphique intuitive, éliminant le besoin de manipuler directement le code du script. Cette évolution majeure rend le script plus accessible et plus facile à utiliser pour tous. Auparavant vous deviez compléter ces lignes de codes en spécifiant le chemin d’accès à votre répertoire.

C’est source d’erreur (très) fréquentes et peut pratique.

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

L’interface graphique de ce script apporte donc une « révolution » notable dans la simplicité d’utilisation.
Avec l’ajout d’un bouton « Parcourir« , il devient extrêmement facile de localiser et de sélectionner le fichier HTML que vous souhaitez traiter, directement depuis votre répertoire de fichiers. Ce niveau d’accessibilité assure que même ceux qui ne sont pas familiers avec la programmation peuvent utiliser le script sans difficulté.

Une fois le fichier HTML sélectionné, le bouton « Envoyer » prend en charge l’extraction des données et enregistre automatiquement le fichier texte résultant dans le même répertoire.
Cette automatisation du processus garantit une expérience utilisateur fluide et sans erreur, depuis le moment où vous choisissez votre fichier jusqu’à l’obtention du fichier texte final, prêt à être utilisé dans IRAMUTEQ.

 

Fonctionnalités et nouveautés

Le script traite les données HTML pour les convertir en un format texte. Il reconstruit la première ligne de chaque article en incluant les variables étoilées nécessaires pour IRAMUTEQ.
Les principales nouveautés sont :

Choix des variables à exporter

Une avancée notable de ce script est la possibilité qu’il vous offre de choisir précisément les variables à exporter.
Vous avez désormais la liberté de sélectionner non seulement le nom du journal, mais aussi le format de la date d’après vos besoins. Que vous ayez besoin de la date complète (année, mois, jour), juste de l’année et du mois, ou uniquement de l’année, le script s’adapte à votre choix. Cette flexibilité est particulièrement utile pour personnaliser l’exportation en fonction des exigences spécifiques de votre analyse.

Champ libre/texte pour variable(s) étoilée(s)

Une fonctionnalité importante de cette version est l’intégration d’un champ de texte libre qui vous permet d’ajouter facilement une variable étoilée à la première ligne de vos données. Le script se charge automatiquement d’ajouter l’astérisque (*), simplifiant ainsi le processus.
Cette fonction est particulièrement utile pour les analyses thématiques où vous filtrez vos données en fonction de mots-clés spécifiques. Imaginez, par exemple, que vous souhaitez extraire des articles contenant le mot « chatgpt » tout en excluant ceux mentionnant « midjourney« .
De même, vous pourriez vouloir séparer la « presse locale » et « nationale » en utilisant des variables étoilées comme *locale et *nationale respectivement, puis fusionner les deux fichiers texte (les deux exports).
Cependant, il est important de rester vigilant sur les éventuels doublons, car certains articles peuvent être publiés à la fois au niveau national et local sans modification.

Gestion du nom du journal

Dans le cadre du développement de ce script, deux méthodes distinctes ont été mises en place pour gérer le nom du journal.
La première (méthode 1 – classique), que l’on pourrait qualifier de méthode classique, consiste à importer le nom complet du journal directement depuis la balise HTML. Cette approche capture tous les éléments contenus dans la balise, ce qui inclut non seulement le nom du journal mais aussi, dans certains cas, d’autres éléments moins pertinents tels que le numéro du journal ou les noms des départements où l’article a été publié, en particulier dans les versions en ligne ou web.
Bien que cette méthode puisse parfois intégrer des informations superflues, elle reste la plus fidèle à la structure originale des données et peut s’avérer cruciale pour des recherches nécessitant un niveau de détail élevé.

La structure html de l’article contenant les balises :

<div class="rdp__DocPublicationName>
<span class="DocPublicationName">

Mais on trouve également des information après la balise <br>

La seconde méthode, dite simplifiée, vise à éliminer les détails superflus présents après la balise <br>.

Cette approche permet d’obtenir un nom de journal plus concis, en écartant les informations annexes qui pourraient ne pas être utiles à l’analyse. Cette méthode est particulièrement avantageuse pour ceux qui préfèrent une présentation épurée, en se concentrant uniquement sur les éléments essentiels du nom du journal.

Dans l’exemple ci dessous, vous pouvez comparer le contenu du fichier html (figure 1), l’export réalisée à l’aide de la méthode 1 et le résulta avec la méthode 2.

(Fichier html)

Résultat avec la méthode 1 (Classique) :

(export méthode 2)

Résultat avec la méthode 2 (Simplifiée) :

(export méthode 2)

En offrant ces deux méthodes, le script permet aux utilisateurs de choisir celle qui convient le mieux à leurs besoins spécifiques de recherche (disons qu’avec la méthode on applique le principe de précaution…), garantissant ainsi une grande flexibilité et adaptabilité dans le traitement des données.

 

Le script

le script en licence GNU (V2) dans le même esprit que le logiciel IRAMUTEQ.
Vous pouvez facilement copier et coller le code en utilisant le bouton situé à droite.

import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import IntVar
from tkinter import StringVar
import os
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):
    print(f"Nom du journal avant nettoyage: '{nom_journal}'")  # Pour débogage
    nom_journal_sans_numero = nom_journal.split(",")[0]
    nom_journal_sans_numero = re.sub(r"[ ']", "_", nom_journal_sans_numero)
    nom_journal_nettoye = f"*source_{nom_journal_sans_numero}"
    print(f"Nom du journal après nettoyage: '{nom_journal_nettoye}'")  # Pour débogage
    return nom_journal_nettoye

def extraire_texte_html(chemin_html, chemin_txt, variable_suppl_texte, nom_journal_checked, date_annee_mois_jour_checked, date_annee_mois_checked, date_annee_checked, methode_extraction):
    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()

        nom_journal_formate = ""  # Valeur par défaut
        texte_article = article.get_text(" ", strip=True)

        # Choix de la méthode d'extraction du nom du journal
        # Méthode classique
        if methode_extraction == 0:
            div_journal = article.find("div", class_="rdp__DocPublicationName")
            if div_journal:
                span_journal = div_journal.find("span", class_="DocPublicationName")
                if span_journal:
                    nom_journal = span_journal.get_text(strip=True)
                    nom_journal_formate = nettoyer_nom_journal(nom_journal)

        # Méthode 2
        if methode_extraction == 1:
            div_journal = article.find("div", class_="rdp__DocPublicationName")
            if div_journal:
                span_journal = div_journal.find("span", class_="DocPublicationName")
                if span_journal:
                    # Obtenir tout le contenu du span comme une liste
                    content_list = list(span_journal.stripped_strings)
                    if content_list:
                        # Prendre le premier élément de la liste, qui devrait être le nom du journal
                        nom_journal = content_list[0]
                    else:
                        # Si la liste est vide, utiliser une chaîne vide
                        nom_journal = ""

                    nom_journal_formate = nettoyer_nom_journal(nom_journal)
                    print("Nom du journal (méthode 2):", nom_journal_formate)  # Pour débogage

        # Suppression du nom du journal du texte de l'article si nécessaire
        texte_article = texte_article.replace(nom_journal_formate, '').strip()

        if div_journal:  # Vérifier à nouveau si div_journal existe avant d'appeler decompose()
                div_journal.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()

        # Construire info_debut basé sur les cases à cocher
        info_debut = "****"
        if nom_journal_checked:
            info_debut += f" {nom_journal_formate}"
        if date_annee_mois_jour_checked:
            info_debut += f" {date_formattee}"
        if date_annee_mois_checked:
            info_debut += f" {am_formattee}"
        if date_annee_checked:
            info_debut += f" {annee_formattee}"
        if variable_suppl_texte:
            info_debut += f" *{variable_suppl_texte}"
        info_debut += "\n"
        print("Info début:", info_debut)  # Afficher info_debut pour débogage

        # 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)


# Fonction pour parcourir et choisir un fichier HTML
def parcourir_fichier():
    global chemin_fichier_html
    chemin_fichier_html = filedialog.askopenfilename(filetypes=[("HTML files", "*.html")])
    if chemin_fichier_html:
        label_chemin.config(text=os.path.basename(chemin_fichier_html))

# Fonction pour traiter le fichier HTML sélectionné
def traiter_fichier_html():
    if chemin_fichier_html:
        chemin_txt = os.path.splitext(chemin_fichier_html)[0] + '.txt'
        variable_suppl_texte = variable_suppl.get()
        print("Variable supplémentaire:", variable_suppl_texte)  # Pour débogage

        # Récupérer les états des cases à cocher
        nom_journal_checked = options_variables["nom_du_journal"].get() == 1
        date_annee_mois_jour_checked = options_variables["date_année_mois_jour"].get() == 1
        date_annee_mois_checked = options_variables["date_année_mois"].get() == 1
        date_annee_checked = options_variables["date_année"].get() == 1

        # Récupérer l'état du bouton radio pour le filtrage des articles courts
        # doit_filtrer_articles_courts = filtre_articles_courts.get()

        # Récupérer la méthode d'extraction du nom du journal sélectionnée par l'utilisateur
        methode_extraction = methode_extraction_nom_journal.get()

        # Passer ces états à la fonction extraire_texte_html
        extraire_texte_html(chemin_fichier_html, chemin_txt, variable_suppl_texte, nom_journal_checked, date_annee_mois_jour_checked, date_annee_mois_checked, date_annee_checked, methode_extraction)

        messagebox.showinfo("Succès", f"Le fichier a été traité et enregistré en tant que : {os.path.basename(chemin_txt)}")
    else:
        messagebox.showwarning("Avertissement", "Veuillez d'abord sélectionner un fichier HTML.")


root = tk.Tk()
root.title("Convertisseur HTML vers TXT")
root.geometry("1200x800")  # Taille de la fenêtre ajustée

# Texte d'introduction avec des retours à la ligne
texte_intro = ("Ce script Python vise à convertir les fichiers au format html provenant du site Europresse au format texte (.txt) pour le logiciel IRAMUTEQ.\n"
               "Le script effectue un nettoyage du corpus et formate la première ligne de chaque article selon les exigences du logiciel.\n\n"
               "Le script affiche en première ligne de tous les articles les variables étoilées suivantes :\n"
               "**** *source_nomdujournal *date_2023-12-22 *am_2023-12 *annee_2023\n\n"
               "Vous pouvez activer les variables ci-dessous et ajouter une variable (champ texte)\n")

label_intro = tk.Label(root, text=texte_intro, font=("Arial", 14), wraplength=root.winfo_screenwidth(), justify=tk.CENTER)
label_intro.pack(pady=5)

# Variables pour les options, créées après l'initialisation de root
options_variables = {
    "nom_du_journal": IntVar(),
    "date_année_mois_jour": IntVar(),
    "date_année_mois": IntVar(),
    "date_année": IntVar()
}

# Ajout des cases à cocher pour les options
frame_options = tk.Frame(root)
frame_options.pack(pady=5)

for option, var in options_variables.items():
    tk.Checkbutton(frame_options, text=option.replace("_", " ").capitalize(), variable=var).pack(side="left")

# Champ pour variable supplémentaire
variable_suppl = StringVar()
tk.Label(root, text="Votre variable supplémentaire (Laisser le champ vide si pas nécessaire) :").pack()
entry_variable_suppl = tk.Entry(root, textvariable=variable_suppl)
entry_variable_suppl.pack()


# extraction du nom du journal - 2 méthodes
methode_extraction_nom_journal = IntVar(value=0)  # 0 pour la méthode classique, 1 pour la méthode 2

label_methode_extraction = tk.Label(root, text="\nSélectionnez la méthode d'extraction du nom du journal :")
label_methode_extraction.pack()

radio_button_methode_classique = tk.Radiobutton(root, text="Méthode classique (On touche à rien et on exporte!)", variable=methode_extraction_nom_journal, value=0)
radio_button_methode_classique.pack()

radio_button_methode_2 = tk.Radiobutton(root, text="Méthode 2 : (conseillée) Texte avant la balise <br> - permet de raccourcir le nom du journal", variable=methode_extraction_nom_journal, value=1)
radio_button_methode_2.pack()

# Bouton pour parcourir le fichier
bouton_parcourir = tk.Button(root, text="Parcourir", command=parcourir_fichier)
bouton_parcourir.pack(pady=(root.winfo_screenheight() // 3 - 200, 10))

# Label pour afficher le chemin du fichier sélectionné
label_chemin = tk.Label(root, text="Aucun fichier sélectionné")
label_chemin.pack(pady=5)

# Bouton pour envoyer (traiter) le fichier
bouton_envoyer = tk.Button(root, text="Envoyer", command=traiter_fichier_html)
bouton_envoyer.pack(pady=5)

# Création d'un Frame pour contenir le texte de bas de page
frame_bas_page = tk.Frame(root)
frame_bas_page.pack(side=tk.BOTTOM, fill=tk.X, pady=50)

# Ajout du texte en bas de page
texte_bas_page = "#################\nAuteur : Stéphane Meurisse\nVersion : v2 - 10/02/2024 - Licence : GPL v2\n#################"
label_bas_page = tk.Label(frame_bas_page, text=texte_bas_page, justify=tk.CENTER)
label_bas_page.pack()

root.mainloop()

Conseils pour contrôler vos exports

Pour optimiser l’export de votre corpus depuis Europresse, il est conseillé de préparer soigneusement votre export. L’idéal est de sélectionner avec précision vos sources, en choisissant vos journaux un par un, plutôt que de compter sur la sélection rapide d’Europresse, comme par exemple la catégorie « presse nationale ».

Après l’extraction, il est fortement recommandé de vérifier le contenu de votre corpus en comparant les versions html et texte.
J’ai réalisé mes tests sur un corpus de 1000 articles, le maximum exportable en une fois sur Europresse.
Une première méthode consiste à vérifier le premier et le dernier article de la liste, et à en prendre un au hasard au milieu. Cette vérification, rapide (de type industriel sur une ligne de montage), permet de détecter les principaux problèmes.
Une autre méthode est de prendre un article de chaque quotidien inclus dans votre export, ce qui permet notamment de vérifier le traitement du nom du quotidien.
Enfin, une fois l’export conforme, des corrections mineures seront inévitables, comme ajuster des termes accolés sans espace ou supprimer les balises « édito », etc…

 

Conclusion

Parmi les points d’amélioration l’analyse des doublons se distingue comme une problématique nécessitant une attention particulière.
La question de savoir quels articles supprimer en cas de doublons – ceux issus de la presse locale ou de la presse nationale – reste un dilemme délicat. Cette problématique soulève (peut être) la possibilité de développer un script spécifique pour gérer efficacement les doublons, tout en conservant l’intégrité et la représentativité des données.

Le dernier acte de ce projet sera de transformer le script en un programme exécutable, libérant ainsi l’utilisateur de la nécessité d’un environnement Python.

N’hésitez pas à partager vos suggestions d’amélioration ou à signaler des bugs rencontrés lors du traitement de vos fichiers 😉

 

4 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.