Die Dokumentenanalyse ist ein Schlüssel zur Erschließung von Informationen in großen Textmengen – sei es in Berichten, wissenschaftlichen Arbeiten oder Unternehmensdokumenten. Mit diesem Python-Tool kannst du Themen aus PDF-Dateien automatisch erkennen, indem du die Kraft der Themenmodellierung (LDA) mit einer benutzerfreundlichen PyQt6-Oberfläche kombinierst. Es extrahiert Text, ordnet Seiten Themen zu und zeigt die Ergebnisse direkt in der GUI an – mit der Option, sie als Textdatei zu exportieren. In diesem Beitrag stelle ich den Code vor, erkläre die Installation und zeige dir, wo dieses Tool glänzen kann.


Was macht das Tool?

Das Skript:

  • Extrahiert Text aus einer PDF-Datei, Seite für Seite.
  • Nutzt Latent Dirichlet Allocation (LDA) von Gensim, um Themen im Text zu erkennen.
  • Ermöglicht das Laden von Themen aus einer JSON-Datei oder das dynamische Hinzufügen/Entfernen in der GUI.
  • Zeigt Textauszüge und Themenstatistiken an und exportiert sie als .txt-Datei.

Installation

Du benötigst folgende Python-Bibliotheken:

  • PyQt6: Für die grafische Benutzeroberfläche.
  • gensim: Für die Themenmodellierung.
  • PyMuPDF: Zum Lesen von PDFs (als fitz importiert).
  • pandas: Für interne Datenverarbeitung.
  • openpyxl: Optional, falls du die Excel-Ausgabe erweitern möchtest.

Installiere sie mit:

bash

pip install PyQt6 gensim PyMuPDF pandas openpyxl

Stelle sicher, dass Python 3.8+ installiert ist.


Der Quellcode

Hier ist der vollständige Code:

python

import json
import os
import gensim
from gensim import corpora
import fitz  # PyMuPDF für PDF-Verarbeitung
import pandas as pd
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, 
                             QLabel, QFileDialog, QProgressBar, QTextEdit, QComboBox, 
                             QLineEdit, QMessageBox, QHBoxLayout)
from PyQt6.QtCore import Qt

class TopicExtractorApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PDF Themen-Extraktor")
        self.setGeometry(100, 100, 800, 600)
        self.max_excerpt_length = 3000
        self.topic_labels = {}
        self.init_ui()

    def init_ui(self):
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QVBoxLayout(main_widget)

        self.pdf_label = QLabel("Kein PDF ausgewählt")
        layout.addWidget(self.pdf_label)
        self.select_pdf_button = QPushButton("PDF auswählen")
        self.select_pdf_button.clicked.connect(self.select_pdf)
        layout.addWidget(self.select_pdf_button)

        self.json_label = QLabel("Kein JSON ausgewählt")
        layout.addWidget(self.json_label)
        self.select_json_button = QPushButton("JSON mit Themen auswählen")
        self.select_json_button.clicked.connect(self.select_json)
        layout.addWidget(self.select_json_button)

        layout.addWidget(QLabel("Themen hinzufügen/entfernen:"))
        theme_layout = QHBoxLayout()
        self.theme_input = QLineEdit()
        self.theme_input.setPlaceholderText("Neues Thema eingeben")
        theme_layout.addWidget(self.theme_input)
        self.add_theme_button = QPushButton("Hinzufügen")
        self.add_theme_button.clicked.connect(self.add_theme)
        theme_layout.addWidget(self.add_theme_button)
        self.theme_combo = QComboBox()
        theme_layout.addWidget(self.theme_combo)
        self.remove_theme_button = QPushButton("Entfernen")
        self.remove_theme_button.clicked.connect(self.remove_theme)
        theme_layout.addWidget(self.remove_theme_button)
        layout.addLayout(theme_layout)

        self.start_button = QPushButton("Analyse starten")
        self.start_button.clicked.connect(self.start_analysis)
        self.start_button.setEnabled(False)
        layout.addWidget(self.start_button)

        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)

        self.output_text = QTextEdit()
        self.output_text.setReadOnly(True)
        layout.addWidget(self.output_text)

        self.export_button = QPushButton("Als TXT exportieren")
        self.export_button.clicked.connect(self.export_to_txt)
        self.export_button.setEnabled(False)
        layout.addWidget(self.export_button)

    def select_pdf(self):
        pdf_path = QFileDialog.getOpenFileName(self, "PDF auswählen", "", "PDF Dateien (*.pdf)")[0]
        if pdf_path:
            self.pdf_path = pdf_path
            self.pdf_label.setText(f"PDF: {os.path.basename(pdf_path)}")
            self.check_start_enabled()

    def select_json(self):
        json_path = QFileDialog.getOpenFileName(self, "JSON auswählen", "", "JSON Dateien (*.json)")[0]
        if json_path:
            try:
                with open(json_path, "r", encoding="utf-8") as json_file:
                    self.topic_labels = json.load(json_file)
                self.json_label.setText(f"JSON: {os.path.basename(json_path)}")
                self.update_theme_combo()
                self.check_start_enabled()
            except Exception as e:
                QMessageBox.critical(self, "Fehler", f"Fehler beim Laden der JSON-Datei: {e}")

    def add_theme(self):
        theme = self.theme_input.text().strip()
        if theme and theme not in self.topic_labels.values():
            new_index = str(len(self.topic_labels))
            self.topic_labels[new_index] = theme
            self.update_theme_combo()
            self.theme_input.clear()
            self.check_start_enabled()

    def remove_theme(self):
        theme = self.theme_combo.currentText()
        if theme:
            self.topic_labels = {k: v for k, v in self.topic_labels.items() if v != theme}
            self.update_theme_combo()
            self.check_start_enabled()

    def update_theme_combo(self):
        self.theme_combo.clear()
        self.theme_combo.addItems(self.topic_labels.values())

    def check_start_enabled(self):
        self.start_button.setEnabled(hasattr(self, "pdf_path") and self.topic_labels)

    def start_analysis(self):
        self.output_text.clear()
        self.progress_bar.setVisible(True)
        self.start_button.setEnabled(False)
        self.export_button.setEnabled(False)

        texts = extract_pdf_text(self.pdf_path)
        if not texts:
            QMessageBox.critical(self, "Fehler", "Keine Texte aus PDF extrahiert.")
            self.reset_ui()
            return

        tokenized_sentences, dictionary, corpus = prepare_lda_data(texts)
        num_topics = len(self.topic_labels)
        lda_model = gensim.models.LdaModel(corpus=corpus, id2word=dictionary, num_topics=num_topics, passes=50)

        report_data, topic_counts = analyze_topics(texts, lda_model, dictionary, self.topic_labels, self.max_excerpt_length, self.update_progress)

        self.display_results(report_data, topic_counts)
        self.export_button.setEnabled(True)
        self.reset_ui()

    def update_progress(self, value, total):
        self.progress_bar.setMaximum(total)
        self.progress_bar.setValue(value)
        QApplication.processEvents()

    def display_results(self, report_data, topic_counts):
        output = "=== Textauszüge ===\n"
        for entry in report_data[:5]:
            output += f"Seite {entry['Seite']}:\nTextauszug: {entry['Textauszug']}\nThema: {entry['Erkanntes Thema']}\n\n"
        output += "\n=== Themenstatistik ===\n"
        for theme, count in topic_counts.items():
            output += f"{theme}: {count}\n"
        self.output_text.setText(output)
        self.report_data = report_data
        self.topic_counts = topic_counts

    def export_to_txt(self):
        txt_path = QFileDialog.getSaveFileName(self, "Als TXT speichern", "", "Text Dateien (*.txt)")[0]
        if txt_path:
            with open(txt_path, "w", encoding="utf-8") as f:
                f.write(self.output_text.toPlainText())
            QMessageBox.information(self, "Erfolg", f"Ergebnisse in '{txt_path}' exportiert.")

    def reset_ui(self):
        self.progress_bar.setVisible(False)
        self.start_button.setEnabled(True)

def extract_pdf_text(pdf_path):
    try:
        texts = []
        with fitz.open(pdf_path) as pdf_file:
            for page_num in range(len(pdf_file)):
                page = pdf_file.load_page(page_num)
                text = page.get_text()
                text = '\n'.join(line.strip() for line in text.split('\n') if line.strip())
                texts.append({"page": page_num + 1, "text": text})
        return texts
    except Exception as e:
        print(f"Fehler beim Extrahieren der PDF: {e}")
        return []

def prepare_lda_data(texts):
    all_text = " ".join(text["text"] for text in texts)
    sentences = [s.strip() for s in all_text.replace('\n', ' ').split(".") if s.strip()]
    tokenized_sentences = [s.lower().split() for s in sentences]
    dictionary = corpora.Dictionary(tokenized_sentences)
    corpus = [dictionary.doc2bow(tokens) for tokens in tokenized_sentences]
    return tokenized_sentences, dictionary, corpus

def analyze_topics(texts, lda_model, dictionary, topic_labels, max_excerpt_length, progress_callback):
    report_data = []
    topic_counts = {label: 0 for label in topic_labels.values()}
    for i, page in enumerate(texts):
        excerpt = '\n'.join(line.strip() for line in page["text"][:max_excerpt_length].split('\n') if line.strip())
        if len(page["text"]) > max_excerpt_length:
            excerpt += '...'
        bow_vector = dictionary.doc2bow(page["text"].lower().split())
        topics = lda_model.get_document_topics(bow_vector)
        if topics:
            max_topic = max(topics, key=lambda x: x[1])
            max_topic_label = topic_labels.get(str(max_topic[0]), "Unbekanntes Thema")
            report_data.append({
                "Seite": page["page"],
                "Textauszug": excerpt,
                "Erkanntes Thema": max_topic_label
            })
            topic_counts[max_topic_label] += 1
        progress_callback(i + 1, len(texts))
    return report_data, topic_counts

def main():
    app = QApplication([])
    window = TopicExtractorApp()
    window.show()
    app.exec()

if __name__ == "__main__":
    main()

Speichere den Code in topic_extractor.py und starte ihn mit python topic_extractor.py.


Einsatzszenarien

Dieses Tool ist vielseitig einsetzbar. Hier einige Beispiele:

  1. Wissenschaftliche Forschung
    Analysiere lange PDFs wie Studien oder Dissertationen, um Hauptthemen zu identifizieren. Kombiniere es mit Zotero für Literaturverwaltung.
  2. Unternehmensberichte
    Extrahiere Themen aus Geschäftsberichten oder Protokollen, z. B. für SAP oder Power BI Integrationen.
  3. Journalismus
    Finde Schwerpunkte in investigativen Dokumenten oder Leaks. Nutze es mit Evernote für Notizen.
  4. Bildung
    Lehrer können Schülerarbeiten thematisch analysieren und mit Google Classroom verknüpfen.
  5. Rechtswesen
    Untersuche Verträge oder juristische Texte auf thematische Schwerpunkte. Ergänze es mit DocuSign.
  6. Marketing
    Analysiere Kundenfeedback oder Marktstudien, um Trends zu erkennen. Integriere es mit HubSpot.
  7. Archivierung
    Strukturiere historische Dokumente für digitale Archive wie Archive.org.

Warum dieses Tool nutzen?

  • Intuitiv: Die GUI macht die Bedienung einfach.
  • Flexibel: Themen können manuell oder aus JSON geladen werden.
  • Exportierbar: Ergebnisse als .txt für weitere Nutzung.
  • Open Source: Passe es an deine Bedürfnisse an, z. B. mit GitHub.

Erweiterungsmöglichkeiten


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert