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:
- Wissenschaftliche Forschung
Analysiere lange PDFs wie Studien oder Dissertationen, um Hauptthemen zu identifizieren. Kombiniere es mit Zotero für Literaturverwaltung. - Unternehmensberichte
Extrahiere Themen aus Geschäftsberichten oder Protokollen, z. B. für SAP oder Power BI Integrationen. - Journalismus
Finde Schwerpunkte in investigativen Dokumenten oder Leaks. Nutze es mit Evernote für Notizen. - Bildung
Lehrer können Schülerarbeiten thematisch analysieren und mit Google Classroom verknüpfen. - Rechtswesen
Untersuche Verträge oder juristische Texte auf thematische Schwerpunkte. Ergänze es mit DocuSign. - Marketing
Analysiere Kundenfeedback oder Marktstudien, um Trends zu erkennen. Integriere es mit HubSpot. - 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
- Mehr Formate: Füge Unterstützung für .docx mit python-docx hinzu.
- Visualisierung: Nutze Matplotlib für Diagramme.
- Cloud-Integration: Verbinde es mit Google Drive oder Dropbox.
Schreibe einen Kommentar