Разбор PDF-файлов (особенно с таблицами) с PDFBox - PullRequest
63 голосов
/ 08 июля 2010

Мне нужно проанализировать файл PDF, который содержит табличные данные. Я использую PDFBox , чтобы извлечь текст файла для последующего анализа результата (String). Проблема в том, что извлечение текста не работает, как я ожидал для табличных данных. Например, у меня есть файл, который содержит такую ​​таблицу (7 столбцов: первые два всегда имеют данные, только один столбец сложности содержит данные, только один столбец Финансирование содержит данные):

+----------------------------------------------------------------+
| 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|
+----------------------------------------------------------------+

Тогда я использую PDFBox:

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

Эти две строки данных будут извлечены следующим образом:

xyz 12.43 12.4312.43
abc 1.56 1.561.56

Между двумя последними числами нет пробелов, но это не самая большая проблема. Проблема в том, что я не знаю, что означают последние два числа: Среднее, Высокое, Не применимо? MAC / Другое, FAE? У меня нет связи между числами и их столбцами.

Мне не нужно использовать библиотеку PDFBox, поэтому хорошо подходит решение, использующее другую библиотеку. Я хочу иметь возможность анализировать файл и знать, что означает каждый проанализированный номер.

Ответы [ 15 ]

18 голосов
/ 13 августа 2010

Вам нужно будет разработать алгоритм для извлечения данных в пригодном для использования формате.Независимо от того, какую библиотеку PDF вы используете, вам нужно будет это сделать.Символы и графика рисуются с помощью ряда операций рисования с сохранением состояния, т. Е. Перемещаются в эту позицию на экране и рисуют глиф для символа 'c'.

Я предлагаю вам расширить org.apache.pdfbox.pdfviewer.PDFPageDrawer и переопределить strokePath метод.Оттуда вы можете перехватывать операции рисования для горизонтальных и вертикальных отрезков и использовать эту информацию для определения позиций столбцов и строк для вашей таблицы.Тогда просто установить текстовые области и определить, какие цифры / буквы / символы нарисованы в какой области.Так как вы знаете расположение регионов, вы сможете определить, к какому столбцу относится извлеченный текст.

Кроме того, причина, по которой у вас может не быть пробелов между визуально разделенным текстом, заключается в том, что очень часто,символ пробела не отображается в PDF.Вместо этого текстовая матрица обновляется и выдается команда рисования для перемещения, чтобы нарисовать следующий символ и «ширину пробела» отдельно от последнего.

Удачи.

12 голосов
/ 12 апреля 2015

Я использовал много инструментов для извлечения таблицы из pdf файла, но у меня это не сработало.

Итак, я реализовал свой собственный алгоритм (его имя traprange) для анализа табличных данных в pdf-файлах.

Ниже приведены примеры файлов PDF и результаты:

  1. Входной файл: sample-1.pdf , результат: sample-1.html
  2. Входной файл: sample-4.pdf , результат: sample-4.html

Посетите страницу моего проекта на traprange .

11 голосов
/ 01 июля 2013

Вы можете извлекать текст по областям в PDFBox. См. Файл примера ExtractByArea.java в артефакте pdfbox-examples, если вы используете Maven. Фрагмент выглядит как

   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" );

Проблема в том, чтобы получить координаты в первую очередь. Я добился успеха, расширив нормальное TextStripper, переопределив processTextPosition(TextPosition text) и распечатав координаты для каждого символа и выяснив, где в документе они находятся.

Но есть гораздо более простой способ, по крайней мере, если вы работаете на Mac. Откройте PDF в Preview, PreviewI, чтобы показать Инспектора, выберите вкладку «Кадрирование» и убедитесь, что единицы измерения находятся в точках, в меню «Инструменты» выберите «Прямоугольный выбор» и выберите интересующую область. Если вы выберете область, инспектор покажет вам координаты, которые вы можете округлить и передать в аргументы конструктора Rectangle. Вам просто нужно подтвердить, где находится источник, используя первый метод.

11 голосов
/ 22 сентября 2012

Может быть, уже слишком поздно для моего ответа, но я думаю, что это не так сложно. Вы можете расширить класс PDFTextStripper и переопределить методы writePage () и processTextPosition (...). В вашем случае я предполагаю, что заголовки столбцов всегда одинаковы. Это означает, что вы знаете x-координату каждого заголовка столбца и можете сравнить x-координаты чисел с заголовками столбцов. Если они достаточно близки (вам нужно проверить, насколько близко), то вы можете сказать, что это число принадлежит этому столбцу.

Другим подходом было бы перехватить вектор "charactersByArticle" после написания каждой страницы:

@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
}

Зная ваши столбцы, вы можете сравнить x-координаты, чтобы определить, к какому столбцу относится каждое число.

Причина, по которой у вас нет пробелов между числами, заключается в том, что вы должны задать строку-разделитель слов.

Я надеюсь, что это полезно для вас или для других, кто может попробовать похожие вещи.

7 голосов
/ 25 февраля 2017

Существует PDFLayoutTextStripper , который был разработан для сохранения формата данных.

от 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);
    }
}
4 голосов
/ 01 октября 2012

Я добился приличного успеха при разборе текстовых файлов, созданных утилитой pdftotext (sudo apt-get install poppler-utils).

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);
}
2 голосов
/ 15 октября 2017

Вы можете использовать класс PDFBox PDFTextStripperByArea для извлечения текста из определенной области документа. Вы можете опираться на это, указав регион каждой ячейки таблицы. Это не предусмотрено из коробки, но пример класса DrawPrintTextLocations демонстрирует, как вы можете анализировать ограничивающие блоки отдельных символов в документе (было бы здорово проанализировать ограничивающие блоки строк или абзацев , но я не видел поддержки в PDFBox для этого - см. этот вопрос ). Вы можете использовать этот подход, чтобы сгруппировать все соприкасающиеся ограничивающие рамки, чтобы идентифицировать отдельные ячейки таблицы. Один из способов сделать это - сохранить набор boxes из Rectangle2D областей, а затем для каждого анализируемого символа найти ограничивающую рамку символа, как в DrawPrintTextLocations.writeString(String string, List<TextPosition> textPositions), и объединить ее с существующим содержимым.

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);

Затем вы можете передать эти регионы PDFTextStripperByArea.

Вы также можете пойти еще дальше и отделить горизонтальные и вертикальные компоненты этих областей, и таким образом вывести области всех ячеек таблицы, независимо от того, удерживаете ли они какое-либо содержимое.

У меня была причина выполнить эти шаги, и в конце концов я написал свой собственный класс PDFTableStripper, используя PDFBox . Я поделился своим кодом в виде gist на GitHub . Метод main дает пример использования класса:

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));
            }
        }
    }
}
2 голосов
/ 14 ноября 2014

У меня была такая же проблема при чтении PDF-файла, в котором данные представлены в табличном формате.После регулярного анализа с использованием PDFBox каждая строка извлекалась с запятой в качестве разделителя ... теряя положение столбца.Чтобы решить эту проблему, я использовал PDFTextStripperByArea и, используя координаты, извлекал данные столбец за столбцом для каждой строки. Это при условии, что у вас есть фиксированный формат pdf.

        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" ));

Затем строка 2 и так далее ...

2 голосов
/ 09 июля 2010

Извлечение данных из PDF связано с проблемами. Документы создаются с помощью какого-то автоматического процесса? Если это так, вы можете подумать о преобразовании PDF-файлов в несжатый PostScript (попробуйте pdf2ps) и посмотреть, содержит ли PostScript какой-то регулярный шаблон, который вы можете использовать.

1 голос
/ 22 августа 2018

Попробуйте использовать TabulaPDF (https://github.com/tabulapdf/tabula). Это очень хорошая библиотека для извлечения содержимого таблицы из файла PDF. Это очень, как и ожидалось.

Удачи.:)

...