Convertir un fichier PDF en Texte

C

Convertir un PDF en Texte

La campagne des législatives de 2024 a démontré qu’il était essentiel de disposer de deux types d’outils pour récupérer les textes des programmes électoraux (par exemple) afin de pouvoir en faire une analyse lexicale (Pascal Marchand, 2024). Les programmes électoraux se présentent généralement sous deux formats :

  1. PDF : Les documents PDF nécessitent une conversion en fichiers texte (.txt) pour être analysés dans un logiciel d’analyse lexicale tel qu’IRaMuTeQ (Pierre Ratinaud).
  2. Web : Les programmes disponibles en ligne doivent être « scrappés » directement via un outil ou un script de récupération de texte. (Cette approche ne sera pas abordé ici)

Dans cet article, je vous propose un script permettant de convertir des fichiers PDF en texte.
À titre d’exemple, j’ai utilisé le programme d’un parti politique et comparé les résultats obtenus avec un outil en ligne.

 

Le script

Le script utilise la bibliothèque PyPDF2 pour extraire le texte des pages d’un document PDF.
Les PDF en double colonne peuvent segmenter les mots, ce qui nécessite un nettoyage. Par exemple, la césure du mot « com-patible » est transformée comme suit « compatible » grâce à la fonctionnalité join_hyphenated qui, lorsqu’elle est activée, joint les mots coupés.
Il est également possible de convertir le texte en minuscule.

Extraction réalisée via PDFgo2

Parfois, l’extraction du texte peut être mal formatée, en fonction de la complexité du PDF, cela peut nécessiter une révision manuelle notamment au niveau des titres, et des pieds de page contenant des informations inutiles (comme un numéro de page, des liens web…).

Enfin, le script ne gère pas les images ou les graphiques présents dans les PDF et l’export se fait au format texte (.txt).

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QVBoxLayout, QWidget, QLineEdit, QCheckBox
from PyQt5.QtCore import Qt
import fitz  # PyMuPDF
import os

class ConvertisseurPDFenTexte(QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setWindowTitle('Convertisseur PDF en Texte')

        # Layout
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QVBoxLayout(self.central_widget)

        # Sélection du fichier PDF
        self.pdf_label = QLabel('Sélectionnez le fichier PDF:', self)
        self.layout.addWidget(self.pdf_label)
        self.pdf_path_line = QLineEdit(self)
        self.layout.addWidget(self.pdf_path_line)
        self.pdf_button = QPushButton('Parcourir', self)
        self.pdf_button.clicked.connect(self.selectionner_pdf)
        self.layout.addWidget(self.pdf_button)

        # Sélection du répertoire de sortie
        self.output_label = QLabel('Sélectionnez le répertoire de sortie:', self)
        self.layout.addWidget(self.output_label)
        self.output_path_line = QLineEdit(self)
        self.layout.addWidget(self.output_path_line)
        self.output_button = QPushButton('Parcourir', self)
        self.output_button.clicked.connect(self.selectionner_repertoire_de_sortie)
        self.layout.addWidget(self.output_button)

        # Option pour unir les mots coupés
        self.join_hyphenated_words_checkbox = QCheckBox('Unir les mots coupés', self)
        self.layout.addWidget(self.join_hyphenated_words_checkbox)

        # Option pour convertir en minuscules
        self.lowercase_checkbox = QCheckBox('Convertir en minuscules', self)
        self.layout.addWidget(self.lowercase_checkbox)

        # Bouton de conversion
        self.convert_button = QPushButton('Convertir le PDF en Texte', self)
        self.convert_button.clicked.connect(self.convertir_pdf_en_texte)
        self.layout.addWidget(self.convert_button)

        # Bouton pour quitter
        self.quit_button = QPushButton('Quitter', self)
        self.quit_button.clicked.connect(QApplication.quit)
        self.layout.addWidget(self.quit_button)

        # Étiquette de statut
        self.status_label = QLabel('', self)
        self.layout.addWidget(self.status_label)

        # Lien cliquable
        self.link_label = QLabel('<a href="https://www.codeandcortex.fr">www.codeandcortex.fr</a>', self)
        self.link_label.setOpenExternalLinks(True)
        self.link_label.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.link_label)

        self.setGeometry(100, 100, 600, 400)
        self.show()

    def selectionner_pdf(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(self, "Sélectionnez le fichier PDF", "", "Fichiers PDF (*.pdf);;Tous les fichiers (*)", options=options)
        if file_name:
            self.pdf_path_line.setText(file_name)

    def selectionner_repertoire_de_sortie(self):
        options = QFileDialog.Options()
        directory = QFileDialog.getExistingDirectory(self, "Sélectionnez le répertoire de sortie", options=options)
        if directory:
            self.output_path_line.setText(directory)

    def convertir_pdf_en_texte(self):
        pdf_path = self.pdf_path_line.text()
        output_directory = self.output_path_line.text()
        join_hyphenated_words = self.join_hyphenated_words_checkbox.isChecked()
        convert_to_lowercase = self.lowercase_checkbox.isChecked()
        if pdf_path and output_directory:
            output_path = os.path.join(output_directory, os.path.splitext(os.path.basename(pdf_path))[0] + '.txt')
            self.pdf_en_texte_avec_colonnes(pdf_path, output_path, join_hyphenated_words, convert_to_lowercase)
            self.status_label.setText(f'Fichier texte créé : {output_path}')
        else:
            self.status_label.setText('Veuillez sélectionner un fichier PDF et un répertoire de sortie.')

    def pdf_en_texte_avec_colonnes(self, pdf_path, txt_path, join_hyphenated_words, convert_to_lowercase):
        # Ouvrir le fichier PDF
        pdf_document = fitz.open(pdf_path)
        
        # Initialiser une chaîne de caractères pour contenir le texte extrait
        text = ""

        # Parcourir chaque page du PDF
        for page_num in range(len(pdf_document)):
            page = pdf_document.load_page(page_num)
            blocks = page.get_text("blocks")  # Récupérer les blocs de texte
            
            # Trier les blocs par position verticale, puis par position horizontale
            blocks.sort(key=lambda b: (b[1], b[0]))
            
            # Extraire le texte de chaque bloc
            for block in blocks:
                block_text = block[4].replace('\n', ' ')
                text += block_text + "\n"

        # Post-traitement pour joindre les lignes
        lines = text.splitlines()
        joined_text = ""
        for i, line in enumerate(lines):
            if line.strip():  # S'il ne s'agit pas d'une ligne vide
                if join_hyphenated_words and joined_text.endswith('-'):  # Si la ligne précédente se termine par un trait d'union et l'option est activée
                    joined_text = joined_text[:-1] + line.strip()  # Joindre sans le trait d'union
                elif i > 0 and not joined_text.endswith(('.', '!', '?')):  # Si la ligne précédente ne se termine pas par une ponctuation
                    joined_text += " " + line.strip()
                else:
                    joined_text += "\n" + line.strip()
            else:
                joined_text += "\n"
        
        # Suppression des espaces autour des traits d'union
        if join_hyphenated_words:
            joined_text = joined_text.replace('- ', '').replace(' -', '')

        # Conversion en minuscules
        if convert_to_lowercase:
            joined_text = joined_text.lower()

        # Écrire le texte extrait dans un fichier .txt
        with open(txt_path, 'w', encoding='utf-8') as txt_file:
            txt_file.write(joined_text.strip())

        print(f"Le texte a été extrait et sauvegardé dans {txt_path}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ConvertisseurPDFenTexte()
    sys.exit(app.exec_())

 

Comparaison avec un outil en ligne

Pour valider l’efficacité de ce script, j’ai comparé les résultats avec un outil en ligne pdf2go de conversion de PDF en texte.

Comme illustré dans la capture d’écran ci-dessous, le script ne prend pas en compte la mise en page en deux colonnes du document et ne relie pas les phrases correctement.
Le format de sortie nécessite une retouche manuelle pour joindre les phrases et les mots coupés.

Extraction réalisée avec pdf2go

Conclusion

Ce script offre une solution simple et efficace pour convertir des PDF en texte. Bien qu’il ne soit pas parfait, il permet d’extraire rapidement du texte et de l’utiliser (avec quelques corrections) dans des logiciels comme Iramuteq.
Pour un résultat optimal, vous devrez tout de même y mettre un peu d’huile de coude !

 

A propos de l'auteur

Stéphane Meurisse

1 Commentaire

Stéphane Meurisse