PDF nach Zeichen trennen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ninaebez
User
Beiträge: 7
Registriert: Montag 16. November 2020, 10:07

Liebe Community,

folgendes, wahrscheinlich sehr simples Problem, ich übe noch ;):

Ich möchte ein PDF in verschiedene Teildateien zerlegen und zwar unregelmäßig. Die Trennung soll immer dann erfolgen, wenn eine bestimmte Zeichenfolge auf einer Seite enthalten ist. Kann das jemand?

Danke bereits im Voraus und herzliche Grüße
ninaebez
Benutzeravatar
noisefloor
User
Beiträge: 4194
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

das ist nicht wirklich trivial.

Grund: PDF ist eine Seitenbeschreibungssprache. Vereinfacht gesagt beschreibt PDF, wo um Dokument was steht. D.h. Text muss nicht vorlaufend im PDF hinterlegt sein. Wenn du das PDF einfach stumpf nach der Zeichenkette trennst, dann hast du ziemlich sicher ein kaputtes PDF.

Theoretisch ginge das, wenn man a) das Layout aus dem PDF extrahiert, b) den Text extrahiert, c) den Text fortlaufend zusammensetzt, d) den Text wie gewünscht trennt und e) das PDF neu baut. Wie einfach oder kompliziert das dann wirklich ist hängt davon ab, wie komplex das PDF wirklich ist.
Und das ganze setzt natürlich voraus, dass der Text auch wirklich als Text im PDF vorliegt und nicht als eingebettete Grafik.

Gruß, noisefloor
ninaebez
User
Beiträge: 7
Registriert: Montag 16. November 2020, 10:07

Hallo zusammen,

ich habe das lösen können, vielleicht auch nicht besonders galant. Für die interessierte Nachwelt hier noch der Code:

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter#process_pdf
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from io import StringIO
import PyPDF2

def get_page_content(page):
rsrcmgr = PDFResourceManager()
output = StringIO()
device = TextConverter(rsrcmgr, output, laparams=LAParams())
interpreter = PDFPageInterpreter(rsrcmgr, device)
interpreter.process_page(page)
return output.getvalue()

def search_pdf(pdfname, search_string):
pages_found = []
with open(pdfname, 'rb') as fp:
for page_num, page in enumerate(PDFPage.get_pages(fp), 1):
page_text = get_page_content(page)
if search_string in page_text:
pages_found.append(page_num)
return pages_found


liste = search_pdf(dateiname, suchbegriff)

x = 0 # Anzahl der Dokumente
i = 0 # Anzahl der Seiten im neuen Dokument

reader = PyPDF2.PdfFileReader(dateiname)

for page in liste:
writer = PyPDF2.PdfFileWriter()
z = liste[x] - liste[x-1]
y = 0
if z == 1 or z < 0:
sollseite = reader.getPage(page - 1)
writer.addPage(sollseite)
i = i + 1
else:
while y < z:
sollseite = reader.getPage(liste[x-1] + y)
writer.addPage(sollseite)
y = y + 1
i = i + 1
outputFilename = "NEU" + str(x) + "_" + datei + ".pdf".format(page)
with open(outputFilename, "wb") as out:
writer.write(out)
print("created", outputFilename)
x = x + 1

print("Das Dokument hatte ursprünglich " + str(liste[-1]) + "Seiten. Es wurde zerteilt in " + str(x) + " Dokumente mit insgesamt " + str(i) + " Seiten.")

Beste Grüße
ninaebez
Benutzeravatar
noisefloor
User
Beiträge: 4194
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok, dann war aber IMHO deine Beschreibung im Eingangspost leicht missverständlich - da klang das so, als sollte der Text an sich nach der Zeichenfolge getrennt werden und nicht "nur" nach der Seite.
Den Code bitte nächstes Mal in eine Codeblock setzen, zu erreichen über die </> Schaltfläche im vollständigen Editor. So ohne Einrückungen ist der Code schlecht bis nicht zu lesen.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ninaebez: Das ist nicht lauffähig weil `dateiname`, `suchbegriff`, und `datei` nicht definiert sind. Und bei `outputFilename` wird versucht etwas mit `format()` in eine Zeichenkette zu formatieren, die gar keinen Platzhalter dafür hat.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Es gibt so einige Schlechte Namen. `liste` — der Leser will wissen was die Bedeutung der Werte ist, nicht das es eine Liste ist. Zudem muss es das nicht, es könnte auch eine beliebige andere Sequenz sein. Darum schreibt man keine Grunddatentypen in Namen.

`x`, `i`, `z`, `y` sind schlechte Namen weil einbuchstabige Namen sehr selten gut sind, weil die einfach nichts über die Bedeutung sagen. Wenn man zu `x` den Kommentar ``# Anzahl der Dokumente`` schreiben muss, dann sollte der Kommentar weg und `x` sollte `document_count` heissen. Schon versteht man nicht nur wo der Kommentar steht was sich hinter dem Namen verbirgt, sondern überall wo der Name steht/verwendet wird. Konkret bei diesen Namen ist auch schlecht das die manchmal sogar sinnvoll sind, weil sie zu den wenigen Ausnahmen gehören die eine erkennbare Bedeutung haben, hier aber für etwas anderes verwendet werden. `i` ist ein typischer Laufindex, und `x`, `y`, und `z` sind gute Namen für Koordinaten.

Wobei `document_count` beziehungsweise Anzahl der Dokumente nicht stimmt. Das ist ja gar nicht die Anzahl der Dokumente sondern die Nummer des aktuellen Dokuments. Und statt das manuell hoch zu zählen, wo der Leser erst einmal schauen und verstehen muss, dass das tatsächlich bedingungslos hochgezählt wird, würde man das mit `enumerate()` erzeugen.

`i` verstehe ich nicht. Wozu ist das gut? Das ist auch die Gesamtzahl der verarbeiteten Seiten. Also sollte den gleichen Wert haben wie ``liste[-1]``, warum wird das noch mal zusätzlich gezählt wenn man den Wert einfacher haben kann.

Es wird anscheinend auch nicht am Suchbegriff getrennt, in dem Sinne das am Ende auch alle Seiten in den neuen PDFs sind, sondern es werden neue PDFs erzeugt die immer bis zu einer Seite mit Suchbegriff gehen. Das sind dann nur in dem Fall alle Seiten wenn der Suchbegriff auch auf der letzten Seite vorkommt.

Wobei ich da sowieso Probleme habe die Logik nachzuvollziehen. Es wird beispielsweise die Seitennummer der letzten Fundstelle mit der Seitennummer der ersten Fundstelle verrechnet was keinen Sinn micht. Dir Irrsinn würde Auffallen wenn man `z` sinnvoll benennt und dann die Bedingung ``page_count < 0`` liest. Eine Anzahl kann nicht negativ sein. Diese ganze rumidexierei sollte man verständlicher schreiben, ohne das man da so rumrechnen muss. Beispielsweise in dem man sich was passendes aus `more_itertools` sucht.

`sollseite` sollte wohl eigentlich `page` heissen. Und `page` sollte `page_number` heissen.

`y` wird zu früh definiert. Das wird ja nur im ``else``-Zweig überhaupt benötigt. Und dort ist die ``while``-Schleife eigentlich eine ``for``-Schleife, so dass man diese Zuweisung an `y` überhaupt nicht braucht.

`str()` und ``+`` um Zeichenketten und Werte zusammenzustückeln ist eher BASIC denn Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und f-Zeichenkettenliterale.

Der Code fällt auf die Nase falls der Suchbegriff auf keiner Seite gefunden wird, und ich bin mir nicht sicher, dass er fehlerfrei funktioniert falls nur *eine* Seite gefunden wird.

Was soll der Name `fp` bedeuten?

In `search_pdf()` kann man das Erstellen der Liste als „list comprehension“ ausdrücken.

`rsrcmgr`? Srsly?

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from io import StringIO

import PyPDF2
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
from pdfminer.pdfpage import PDFPage


def get_page_content(page):
    resource_manager = PDFResourceManager()
    out_file = StringIO()
    PDFPageInterpreter(
        resource_manager,
        TextConverter(resource_manager, out_file, laparams=LAParams()),
    ).process_page(page)
    return out_file.getvalue()


def search_pdf(pdf_filename, search_string):
    with open(pdf_filename, "rb") as file:
        return [
            page_number
            for page_number, page in enumerate(PDFPage.get_pages(file), 1)
            if search_string in get_page_content(page)
        ]


def main():
    dateiname = "..."
    suchbegriff = "..."

    page_numbers = search_pdf(dateiname, suchbegriff)
    written_page_count = 0
    if page_numbers:
        reader = PyPDF2.PdfFileReader(dateiname)
        #
        # TODO This iterating over `page_numbers` and mixing index access in the
        #   loop is confusing and most likely can be written simpler and easier
        #   to understand.
        #
        for document_index, page_number in enumerate(page_numbers):
            writer = PyPDF2.PdfFileWriter()
            page_count = (
                page_numbers[document_index] - page_numbers[document_index - 1]
            )
            if page_count == 1 or page_count < 0:
                writer.addPage(reader.getPage(page_number - 1))
                written_page_count += 1
            else:
                for offset in range(page_count):
                    writer.addPage(
                        reader.getPage(
                            page_numbers[document_index - 1] + offset
                        )
                    )
                written_page_count += page_count

            output_filename = f"NEU{document_index}_{dateiname}"
            with open(output_filename, "wb") as file:
                writer.write(file)
                print("created", output_filename)

        print(
            "Das Dokument hatte ursprünglich",
            page_numbers[-1],
            "Seiten. Es wurde zerteilt in",
            document_index + 1,
            "Dokumente mit insgesamt",
            written_page_count,
            "Seiten.",
        )
    else:
        print(f"Keine Fundstellen von {suchbegriff!r} gefunden.")


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten