Подтвердить что ты не робот

Разбор PDF файлов (особенно с таблицами) с помощью PDFBox

Мне нужно проанализировать 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, поэтому решение, использующее другую библиотеку, прекрасное. Я хочу, чтобы иметь возможность анализировать файл и знать, что означает каждый проанализированный номер.

4b9b3361

Ответ 1

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

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

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

Удачи.

Ответ 2

Возможно, слишком поздно для моего ответа, но я думаю, что это не так сложно. Вы можете расширить класс 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-координат, чтобы определить, к какому столбцу принадлежит каждый номер.

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

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

Ответ 3

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

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

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

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

Ответ 4

Вы можете извлечь текст по области в 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, ⌘I, чтобы показать Инспектор, выберите вкладку Crop и убедитесь, что единицы находятся в точках, из меню "Инструменты" выберите "Прямоугольный выбор" и выберите интересующую область. Если вы выберете область, инспектор покажет вам координаты, которые вы можете объединить и подать в аргументы конструктора Rectangle. Вам просто нужно подтвердить, где источник, используя первый метод.

Ответ 5

У меня был неплохой успех при анализе текстовых файлов, созданных с помощью утилиты 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);
}

Ответ 6

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

Ответ 7

У меня была такая же проблема при чтении 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 и т.д.

Ответ 8

Там 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);
    }
}

Ответ 9

http://swftools.org/ у этих парней есть компонент pdf2swf. Они также могут показывать таблицы. Они также дают источник. Таким образом, вы можете проверить это.

Ответ 10

Это отлично работает, если файл PDF имеет "Только прямоугольную таблицу", используя pdfbox 2.0.6. Не будет работать с любой другой таблицей только в прямоугольной таблице.

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

Ответ 11

Вы можете использовать класс PDFBox PDFTableStripperByArea для извлечения текста из определенного региона документа. Вы можете основываться на этом, идентифицируя область в каждой ячейке таблицы. Это не предусмотрено из коробки, но пример DrawPrintTextLocations класс демонстрирует, как вы можете анализировать ограничивающие поля отдельных символов в document (было бы прекрасно разобрать ограничивающие прямоугольники строк или абзацев, но я не видел поддержки в PDFBox для этого - см. этот question). Вы можете использовать этот подход для группировки всех касательных ограничивающих прямоугольников для идентификации отдельных ячеек таблицы. Один из способов сделать это - поддерживать набор 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);

Затем вы можете передать эти области в PDFTableStripperByArea.

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

У меня была причина выполнить эти шаги, и в итоге я написал свой собственный класс 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));
            }
        }
    }
}

Ответ 12

Я не знаком с PDFBox, но вы можете попробовать itext. Несмотря на то, что на главной странице говорится о создании PDF файлов, вы также можете делать манипуляции и извлечение PDF файлов. Посмотрите и посмотрите, подходит ли это вашему прецеденту.

Ответ 13

Как насчет печати на изображение и выполнения OCR?

Звучит ужасно неэффективно, но практически цель PDF - сделать текст недоступным, вы должны делать то, что должны делать.

Ответ 14

Для чтения содержимого таблицы из pdf файла вам нужно просто преобразовать файл pdf в текстовый файл с помощью любого API (у меня есть PdfTextExtracter.getTextFromPage() iText), а затем прочитайте этот файл txt ваша java-программа. Теперь, прочитав ее, основная задача будет выполнена. Вы должны отфильтровать данные своей потребности. вы можете сделать это, постоянно используя метод split для класса String, пока не найдете запись своего интереса. Вот мой код, по которому я извлекаю часть записи PDF файлом и записываю его в файл .CSV. Url of PDF файл.. http://www.cea.nic.in/reports/monthly/generation_rep/actual/jan13/opm_02.pdf

Код: -

public static void genrateCsvMonth_Region(String pdfpath, String csvpath) {
        try {
            String line = null;
            // Appending Header in CSV file...
            BufferedWriter writer1 = new BufferedWriter(new FileWriter(csvpath,
                    true));
            writer1.close();
            // Checking whether file is empty or not..
            BufferedReader br = new BufferedReader(new FileReader(csvpath));

            if ((line = br.readLine()) == null) {
                BufferedWriter writer = new BufferedWriter(new FileWriter(
                        csvpath, true));
                writer.append("REGION,");
                writer.append("YEAR,");
                writer.append("MONTH,");
                writer.append("THERMAL,");
                writer.append("NUCLEAR,");
                writer.append("HYDRO,");
                writer.append("TOTAL\n");
                writer.close();
            }
            // Reading the pdf file..
            PdfReader reader = new PdfReader(pdfpath);
            BufferedWriter writer = new BufferedWriter(new FileWriter(csvpath,
                    true));

            // Extracting records from page into String..
            String page = PdfTextExtractor.getTextFromPage(reader, 1);
            // Extracting month and Year from String..
            String period1[] = page.split("PEROID");
            String period2[] = period1[0].split(":");
            String month[] = period2[1].split("-");
            String period3[] = month[1].split("ENERGY");
            String year[] = period3[0].split("VIS");

            // Extracting Northen region
            String northen[] = page.split("NORTHEN REGION");
            String nthermal1[] = northen[0].split("THERMAL");
            String nthermal2[] = nthermal1[1].split(" ");

            String nnuclear1[] = northen[0].split("NUCLEAR");
            String nnuclear2[] = nnuclear1[1].split(" ");

            String nhydro1[] = northen[0].split("HYDRO");
            String nhydro2[] = nhydro1[1].split(" ");

            String ntotal1[] = northen[0].split("TOTAL");
            String ntotal2[] = ntotal1[1].split(" ");

            // Appending filtered data into CSV file..
            writer.append("NORTHEN" + ",");
            writer.append(year[0] + ",");
            writer.append(month[0] + ",");
            writer.append(nthermal2[4] + ",");
            writer.append(nnuclear2[4] + ",");
            writer.append(nhydro2[4] + ",");
            writer.append(ntotal2[4] + "\n");

            // Extracting Western region
            String western[] = page.split("WESTERN");

            String wthermal1[] = western[1].split("THERMAL");
            String wthermal2[] = wthermal1[1].split(" ");

            String wnuclear1[] = western[1].split("NUCLEAR");
            String wnuclear2[] = wnuclear1[1].split(" ");

            String whydro1[] = western[1].split("HYDRO");
            String whydro2[] = whydro1[1].split(" ");

            String wtotal1[] = western[1].split("TOTAL");
            String wtotal2[] = wtotal1[1].split(" ");

            // Appending filtered data into CSV file..
            writer.append("WESTERN" + ",");
            writer.append(year[0] + ",");
            writer.append(month[0] + ",");
            writer.append(wthermal2[4] + ",");
            writer.append(wnuclear2[4] + ",");
            writer.append(whydro2[4] + ",");
            writer.append(wtotal2[4] + "\n");

            // Extracting Southern Region
            String southern[] = page.split("SOUTHERN");

            String sthermal1[] = southern[1].split("THERMAL");
            String sthermal2[] = sthermal1[1].split(" ");

            String snuclear1[] = southern[1].split("NUCLEAR");
            String snuclear2[] = snuclear1[1].split(" ");

            String shydro1[] = southern[1].split("HYDRO");
            String shydro2[] = shydro1[1].split(" ");

            String stotal1[] = southern[1].split("TOTAL");
            String stotal2[] = stotal1[1].split(" ");

            // Appending filtered data into CSV file..
            writer.append("SOUTHERN" + ",");
            writer.append(year[0] + ",");
            writer.append(month[0] + ",");
            writer.append(sthermal2[4] + ",");
            writer.append(snuclear2[4] + ",");
            writer.append(shydro2[4] + ",");
            writer.append(stotal2[4] + "\n");

            // Extracting eastern region
            String eastern[] = page.split("EASTERN");

            String ethermal1[] = eastern[1].split("THERMAL");
            String ethermal2[] = ethermal1[1].split(" ");

            String ehydro1[] = eastern[1].split("HYDRO");
            String ehydro2[] = ehydro1[1].split(" ");

            String etotal1[] = eastern[1].split("TOTAL");
            String etotal2[] = etotal1[1].split(" ");
            // Appending filtered data into CSV file..
            writer.append("EASTERN" + ",");
            writer.append(year[0] + ",");
            writer.append(month[0] + ",");
            writer.append(ethermal2[4] + ",");
            writer.append(" " + ",");
            writer.append(ehydro2[4] + ",");
            writer.append(etotal2[4] + "\n");

            // Extracting northernEastern region
            String neestern[] = page.split("NORTH");

            String nethermal1[] = neestern[2].split("THERMAL");
            String nethermal2[] = nethermal1[1].split(" ");

            String nehydro1[] = neestern[2].split("HYDRO");
            String nehydro2[] = nehydro1[1].split(" ");

            String netotal1[] = neestern[2].split("TOTAL");
            String netotal2[] = netotal1[1].split(" ");

            writer.append("NORTH EASTERN" + ",");
            writer.append(year[0] + ",");
            writer.append(month[0] + ",");
            writer.append(nethermal2[4] + ",");
            writer.append(" " + ",");
            writer.append(nehydro2[4] + ",");
            writer.append(netotal2[4] + "\n");
            writer.close();

        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

    }