it-swarm.com.de

Parsing PDF Dateien (besonders bei Tabellen) mit PDFBox

Ich muss eine PDF -Datei analysieren, die Tabellendaten enthält. Ich benutze PDFBox , um den Dateitext zu extrahieren, um das Ergebnis (String) später zu analysieren. Das Problem ist, dass die Textextraktion für Tabellendaten nicht wie erwartet funktioniert. Zum Beispiel habe ich eine Datei, die eine Tabelle wie diese enthält (7 Spalten: Die ersten beiden haben immer Daten, nur eine Spalte für Komplexität enthält Daten, nur eine Spalte für Finanzierung enthält Daten):

+----------------------------------------------------------------+
| AIH | Value | Complexity                     | Financing       |
|     |       | Medium | High | Not applicable | MAC/Other | FAE |
+----------------------------------------------------------------+
| xyz | 12.43 | 12.34  |      |                | 12.34     |     |
+----------------------------------------------------------------+
| abc | 1.56  |        | 1.56 |                |           | 1.56|
+----------------------------------------------------------------+

Dann benutze ich PDFBox:

PDDocument document = PDDocument.load(pathToFile);
PDFTextStripper s = new PDFTextStripper();
String content = s.getText(document);

Diese zwei Datenzeilen würden folgendermaßen extrahiert:

xyz 12.43 12.4312.43
abc 1.56 1.561.56

Zwischen den letzten beiden Zahlen gibt es keine Leerzeichen, dies ist jedoch nicht das größte Problem. Das Problem ist, dass ich nicht weiß, was die letzten beiden Zahlen bedeuten: Mittel, Hoch, Nicht zutreffend? MAC/Andere, FAE? Ich habe keine Beziehung zwischen den Zahlen und ihren Spalten.

Es ist für mich nicht erforderlich, die PDFBox-Bibliothek zu verwenden, daher ist eine Lösung, die eine andere Bibliothek verwendet, in Ordnung. Ich möchte in der Lage sein, die Datei zu analysieren und zu wissen, was jede analysierte Nummer bedeutet.

61
matheus.emm

Sie müssen einen Algorithmus entwickeln, um die Daten in einem verwendbaren Format zu extrahieren. Unabhängig davon, welche PDF Bibliothek Sie verwenden, müssen Sie dies tun. Zeichen und Grafiken werden durch eine Reihe zustandsorientierter Zeichenoperationen gezeichnet, d. H. Sie bewegen sich zu dieser Position auf dem Bildschirm und zeichnen die Glyphe für das Zeichen 'c'.

Ich schlage vor, dass Sie org.Apache.pdfbox.pdfviewer.PDFPageDrawer erweitern und die strokePath-Methode überschreiben. Von dort aus können Sie die Zeichenoperationen für horizontale und vertikale Liniensegmente abfangen und anhand dieser Informationen die Spalten- und Zeilenpositionen Ihrer Tabelle ermitteln. Dann müssen Sie einfach Textbereiche einrichten und bestimmen, welche Zahlen/Buchstaben/Zeichen in welchem ​​Bereich gezeichnet werden. Da Sie das Layout der Regionen kennen, können Sie feststellen, zu welcher Spalte der extrahierte Text gehört.

Möglicherweise haben Sie keine Leerzeichen zwischen visuell getrenntem Text, weil häufig ein Leerzeichen von der PDF-Datei nicht gezeichnet wird. Stattdessen wird die Textmatrix aktualisiert, und ein Zeichenbefehl für "Verschieben" wird ausgegeben, um das nächste Zeichen und eine "Leerzeichenbreite" neben dem letzten Zeichen zu zeichnen.

Viel Glück.

18
purecharger

Ich hatte viele Werkzeuge zum Extrahieren von Tabellen aus PDF-Dateien verwendet, aber es funktionierte nicht für mich.

Also habe ich meinen eigenen Algorithmus (dessen Name traprange ist) implementiert, um Tabellendaten in PDF-Dateien zu analysieren. 

Nachfolgend einige PDF-Beispieldateien und Ergebnisse: 

  1. Eingabedatei: sample-1.pdf , Ergebnis: sample-1.html
  2. Eingabedatei: sample-4.pdf , Ergebnis: sample-4.html

Besuchen Sie meine Projektseite unter traprange .

12
Tho

Sie können Text in PDFBox nach Bereichen extrahieren. Siehe die ExtractByArea.Java-Beispieldatei im Artefakt pdfbox-examples, wenn Sie Maven verwenden. Ein Ausschnitt sieht aus wie

   PDFTextStripperByArea stripper = new PDFTextStripperByArea();
   stripper.setSortByPosition( true );
   Rectangle rect = new Rectangle( 464, 59, 55, 5);
   stripper.addRegion( "class1", rect );
   stripper.extractRegions( page );
   String string = stripper.getTextForRegion( "class1" );

Das Problem ist, die Koordinaten an erster Stelle zu bekommen. Ich hatte Erfolg beim Erweitern der normalen TextStripper, der Überschreitung von processTextPosition(TextPosition text) und dem Ausdrucken der Koordinaten für jedes Zeichen und der Ermittlung der Position im Dokument.

Es gibt jedoch einen viel einfacheren Weg, zumindest wenn Sie mit einem Mac arbeiten. Öffnen Sie PDF in der Vorschau. ToIhne der Inspektor zu sehen, wählen Sie die Registerkarte Zuschneiden und vergewissern Sie sich, dass sich die Einheiten in Punkten befinden. Wählen Sie im Menü Extras die Option Rechteckige Auswahl und wählen Sie den gewünschten Bereich aus. Wenn Sie einen Bereich auswählen, zeigt der Inspektor die Koordinaten an, die Sie runden und in die Konstruktorargumente Rectangle eingeben können. Sie müssen lediglich mit der ersten Methode den Ursprung des Ursprungs bestätigen.

10

Es ist vielleicht zu spät für meine Antwort, aber ich denke, das ist nicht so schwer. Sie können die PDFTextStripper-Klasse erweitern und die Methoden writePage () und processTextPosition (...) überschreiben. In Ihrem Fall gehe ich davon aus, dass die Spaltenüberschriften immer gleich sind. Das bedeutet, dass Sie die x-Koordinate jeder Spaltenüberschrift kennen und Sie können die x-Koordinate der Zahlen mit denen der Spaltenüberschriften vergleichen. Wenn sie nahe genug sind (Sie müssen testen, wie nahe sie sind), können Sie sagen, dass diese Nummer zu dieser Spalte gehört.

Ein anderer Ansatz wäre, den Vector "zeichenByArticle" abzufangen, nachdem jede Seite geschrieben wurde:

@Override
public void writePage() throws IOException {
    super.writePage();
    final Vector<List<TextPosition>> pageText = getCharactersByArticle();
    //now you have all the characters on that page
    //to do what you want with them
}

Wenn Sie Ihre Spalten kennen, können Sie den Vergleich der X-Koordinaten durchführen, um zu entscheiden, zu welcher Spalte jede Zahl gehört.

Der Grund, warum Sie keine Leerzeichen zwischen den Zahlen haben, ist, dass Sie die Word-Trennzeichenfolge festlegen müssen.

Ich hoffe, das ist nützlich für Sie oder für andere, die vielleicht ähnliche Dinge versuchen.

10
impeto

Es gibt PDFLayoutTextStripper , das das Format der Daten beibehalten soll.

Aus der README:

import Java.io.FileInputStream;
import Java.io.FileNotFoundException;
import Java.io.IOException;

import org.Apache.pdfbox.pdfparser.PDFParser;
import org.Apache.pdfbox.pdmodel.PDDocument;
import org.Apache.pdfbox.util.PDFTextStripper;

public class Test {

    public static void main(String[] args) {
        String string = null;
        try {
            PDFParser pdfParser = new PDFParser(new FileInputStream("sample.pdf"));
            pdfParser.parse();
            PDDocument pdDocument = new PDDocument(pdfParser.getDocument());
            PDFTextStripper pdfTextStripper = new PDFLayoutTextStripper();
            string = pdfTextStripper.getText(pdDocument);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        };
        System.out.println(string);
    }
}
7
Matthias Braun

Ich hatte einen anständigen Erfolg beim Analysieren von Textdateien, die mit dem Dienstprogramm pdftotext (Sudo apt-get install poppler-utils) generiert wurden.

File convertPdf() throws Exception {
    File pdf = new File("mypdf.pdf");
    String outfile = "mytxt.txt";
    String proc = "/usr/bin/pdftotext";
    ProcessBuilder pb = new ProcessBuilder(proc,"-layout",pdf.getAbsolutePath(),outfile); 
    Process p = pb.start();

    p.waitFor();

    return new File(outfile);
}
4
scott

Das gleiche Problem hatte ich beim Lesen der PDF-Datei, in der die Daten in Tabellenform vorliegen. Nach der regulären Analyse mit PDFBox wurde jede Zeile mit Komma als Trennzeichen extrahiert ... die Spaltenposition wurde verloren .. Um dieses Problem zu beheben, habe ich PDFTextStripperByArea verwendet, und mithilfe von Koordinaten habe ich die Daten Spalte für Spalte für jede Zeile extrahiert. Dies setzt voraus, dass Sie ein festes PDF-Format haben.

        File file = new File("fileName.pdf");
        PDDocument document = PDDocument.load(file);
        PDFTextStripperByArea stripper = new PDFTextStripperByArea();
        stripper.setSortByPosition( true );
        Rectangle rect1 = new Rectangle( 50, 140, 60, 20 );
        Rectangle rect2 = new Rectangle( 110, 140, 20, 20 );
        stripper.addRegion( "row1column1", rect1 );
        stripper.addRegion( "row1column2", rect2 );
        List allPages = document.getDocumentCatalog().getAllPages();
        PDPage firstPage = (PDPage)allPages.get( 2 );
        stripper.extractRegions( firstPage );
        System.out.println(stripper.getTextForRegion( "row1column1" ));
        System.out.println(stripper.getTextForRegion( "row1column2" ));

Dann Reihe 2 und so weiter ...

2
manu

Das Extrahieren von Daten aus PDF ist mit Problemen verbunden. Werden die Dokumente durch einen automatischen Prozess erstellt? Wenn dies der Fall ist, können Sie die PDF-Dateien in unkomprimiertes PostScript konvertieren (versuchen Sie pdf2ps) und prüfen, ob das PostScript ein regelmäßiges Muster enthält, das Sie nutzen können.

2
Todd Owen

Versuchen Sie es mit TabulaPDF ( https://github.com/tabulapdf/tabula ). Dies ist eine sehr gute Bibliothek zum Extrahieren von Tabelleninhalten aus der Datei PDF. Es ist sehr wie erwartet.

Viel Glück. :)

1
SURESH KUMAR S

Sie können die Klasse PDFTextStripperByArea von PDFBox verwenden, um Text aus einem bestimmten Bereich eines Dokuments zu extrahieren. Sie können darauf aufbauen, indem Sie den Bereich jeder Zelle der Tabelle identifizieren. Dies ist nicht standardmäßig vorhanden, aber die Beispielklasse DrawPrintTextLocations zeigt, wie Sie die Begrenzungsrahmen einzelner Zeichen in einem Dokument analysieren können In PDFBox wurde dafür keine Unterstützung gesehen - siehe frage ). Sie können diesen Ansatz verwenden, um alle berührenden Begrenzungsrahmen zu gruppieren, um unterschiedliche Zellen einer Tabelle zu identifizieren. Eine Möglichkeit, dies zu tun, besteht darin, eine Menge boxes von Rectangle2D-Regionen beizubehalten und dann für jedes analysierte Zeichen den Begrenzungsrahmen des Zeichens wie in DrawPrintTextLocations.writeString(String string, List<TextPosition> textPositions) zu finden und es mit dem vorhandenen Inhalt zusammenzuführen.

Rectangle2D bounds = s.getBounds2D();
// Pad sides to detect almost touching boxes
Rectangle2D hitbox = bounds.getBounds2D();
final double dx = 1.0; // This value works for me, feel free to Tweak (or add setter)
final double dy = 0.000; // Rows of text tend to overlap, so no need to extend
hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy);
hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy);

// Find all overlapping boxes
List<Rectangle2D> intersectList = new ArrayList<Rectangle2D>();
for(Rectangle2D box: boxes) {
    if(box.intersects(hitbox)) {
        intersectList.add(box);
    }
}

// Combine all touching boxes and update
for(Rectangle2D box: intersectList) {
    bounds.add(box);
    boxes.remove(box);
}
boxes.add(bounds);

Sie können diese Regionen dann an PDFTextStripperByArea übergeben.

Sie können auch einen Schritt weitergehen, um die horizontalen und vertikalen Komponenten dieser Bereiche voneinander zu trennen, und so auf Bereiche aller Tabellenzellen schließen, unabhängig davon, ob sie dann Inhalt enthalten.

Ich hatte Grund, diese Schritte auszuführen, und schrieb schließlich meine eigene PDFTableStripper-Klasse mit PDFBox . Ich habe meinen Code als Gist auf GitHub freigegeben. Die main-Methode gibt ein Beispiel für die Verwendung der Klasse:

try (PDDocument document = PDDocument.load(new File(args[0])))
{
    final double res = 72; // PDF units are at 72 DPI
    PDFTableStripper stripper = new PDFTableStripper();
    stripper.setSortByPosition(true);

    // Choose a region in which to extract a table (here a 6"wide, 9" high rectangle offset 1" from top left of page)
    stripper.setRegion(new Rectangle(
        (int) Math.round(1.0*res), 
        (int) Math.round(1*res), 
        (int) Math.round(6*res), 
        (int) Math.round(9.0*res)));

    // Repeat for each page of PDF
    for (int page = 0; page < document.getNumberOfPages(); ++page)
    {
        System.out.println("Page " + page);
        PDPage pdPage = document.getPage(page);
        stripper.extractTable(pdPage);
        for(int c=0; c<stripper.getColumns(); ++c) {
            System.out.println("Column " + c);
            for(int r=0; r<stripper.getRows(); ++r) {
                System.out.println("Row " + r);
                System.out.println(stripper.getText(r, c));
            }
        }
    }
}
1
beldaz

http://swftools.org/ Diese Jungs haben eine pdf2swf-Komponente. Sie können auch Tabellen zeigen ..__ Sie geben auch die Quelle an. So könnten Sie es vielleicht überprüfen. 

0
kaushalc

Dies funktioniert gut, wenn die Datei PDF "Nur rechteckige Tabelle" mit pdfbox 2.0.6 enthält. Funktioniert nicht mit anderen Tabellen. Rechteckige Tabelle.

import Java.io.File;
import Java.io.IOException;
import Java.util.ArrayList;

import org.Apache.pdfbox.pdmodel.PDDocument;
import org.Apache.pdfbox.text.PDFTextStripper;
import org.Apache.pdfbox.text.PDFTextStripperByArea;
public class PDFTableExtractor {
    public static void main(String[] args) throws IOException {
        ArrayList<String[]> objTableList = readParaFromPDF("C:\\sample1.pdf", 1,1,6);
        //Enter Filepath, startPage, EndPage, Number of columns in Rectangular table
    }
    public static ArrayList<String[]> readParaFromPDF(String pdfPath, int pageNoStart, int pageNoEnd, int noOfColumnsInTable) {
        ArrayList<String[]> objArrayList = new ArrayList<>();
        try {
            PDDocument document = PDDocument.load(new File(pdfPath));
            document.getClass();
            if (!document.isEncrypted()) {
                PDFTextStripperByArea stripper = new PDFTextStripperByArea();
                stripper.setSortByPosition(true);
                PDFTextStripper tStripper = new PDFTextStripper();
                tStripper.setStartPage(pageNoStart);
                tStripper.setEndPage(pageNoEnd);
                String pdfFileInText = tStripper.getText(document);
                // split by whitespace
                String Documentlines[] = pdfFileInText.split("\\r?\\n");
                for (String line : Documentlines) {
                    String lineArr[] = line.split("\\s+");
                    if (lineArr.length == noOfColumnsInTable) {
                        for (String linedata : lineArr) {
                            System.out.print(linedata + "             ");
                        }
                        System.out.println("");
                        objArrayList.add(lineArr);
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("Exception " +e);
        }
            return objArrayList;
    }
}
0