使用 PDFBox 解析 PDF 文件(尤其是表格)

Posted

技术标签:

【中文标题】使用 PDFBox 解析 PDF 文件(尤其是表格)【英文标题】:Parsing PDF files (especially with tables) with PDFBox 【发布时间】:2011-03-13 08:08:57 【问题描述】:

我需要解析一个包含表格数据的 PDF 文件。我正在使用PDFBox 提取文件文本以稍后解析结果(字符串)。问题是文本提取不像我对表格数据的预期那样工作。例如,我有一个文件,其中包含这样的表(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 库,所以使用另一个库的解决方案就可以了。我想要的是能够解析文件并知道每个解析的数字意味着什么。

【问题讨论】:

祝你好运......我自己也陷入了 PDF 的地狱,并且完全厌恶这种格式。 PDF 被设计为一种输出显示格式,而不是用于提取。责怪用户,而不是格式。 如果 PDF 是固定布局,那么还有其他方法可以从列中提取数据。我刚刚编写了一个工具来从表单上的固定字段位置提取 PDF 文本。看看 Matheus 所指的 PDF 文件会很有趣。 很遗憾,我无法显示 PDF 文件。它包含项目的客户数据,不能泄露。 我在使用不同的库时遇到了同样的问题 (pdfparser.org)。这似乎不是图书馆的问题。我经过大量搜索得出的解决方案是将过程分为两个:1)PDFtohtml&2)HTMLtoTXT。 【参考方案1】:

您需要设计一种算法来提取可用格式的数据。无论您使用哪个 PDF 库,都需要这样做。字符和图形是通过一系列有状态的绘制操作来绘制的,即移动到屏幕上的这个位置并绘制字符'c'的字形。

我建议你扩展org.apache.pdfbox.pdfviewer.PDFPageDrawer 并覆盖strokePath 方法。从那里您可以截取水平和垂直线段的绘图操作,并使用该信息来确定表格的列和行位置。然后设置文本区域并确定在哪个区域中绘制哪些数字/字母/字符是一件简单的事情。由于您知道区域的布局,因此您将能够判断提取的文本属于哪一列。

此外,在视觉上分隔的文本之间可能没有空格的原因是,PDF 通常不会绘制空格字符。而是更新文本矩阵并发出“移动”的绘图命令以绘制下一个字符以及与最后一个字符分开的“空间宽度”。

祝你好运。

【讨论】:

这个工具似乎做了类似上面的事情,并且有一些可用的源代码jpedal.org/support_egTZ.php 我最近做了类似的事情,除了我必须处理多行文本。还请查看 ExtractText 类集,以了解在拥有列和行后如何提取实际文本。另一方面,我在正确获取行时遇到了问题,但是当我返回列时可以通过假设新行来进行调整。 @deterb 你是如何处理多行文本的? @purecharger 有什么方法可以识别桌子位置吗? @GouravSaklecha 您需要对任何写出 PDF 文档所使用的格式进行逆向工程,以找出其处理的确切方式 - 不同的 PDF 生成器处理此问题的方式不同。通常,您可以通过查看绘制文本的位置来辨别多行。【参考方案2】:

我使用了很多工具从 pdf 文件中提取表格,但它对我不起作用。

所以我实现了自己的算法(它的名字是traprange)来解析pdf文件中的表格数据。

以下是一些示例 pdf 文件和结果:

    输入文件:sample-1.pdf,结果:sample-1.html 输入文件:sample-4.pdf,结果:sample-4.html

访问我的项目页面traprange。

【讨论】:

您好,能否给您的 lib 提供 maven 依赖项? @VaheHarutyunyan 请签入此文件github.com/thoqbk/traprange/blob/master/pom.xml @ThomQ 似乎我们在 maven central 中没有它? search.maven.org @VaheHarutyunyan 不,我们没有 @Tho 如果表格在第一页中完成一半,在第二页中完成一半,您的工具是否可以工作?我的意思是表格的一部分在前一页中可用,在当前页中剩余。有什么帮助吗?【参考方案3】:

您可以在 PDFBox 中按区域提取文本。如果您使用 Maven,请参阅 pdfbox-examples 工件中的 ExtractByArea.java 示例文件。一个 sn-p 看起来像

   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,⌘I 以显示检查器,选择裁剪选项卡并确保单位为点,从工具菜单中选择矩形选择,然后选择感兴趣的区域。如果您选择一个区域,检查器将向您显示坐标,您可以将其四舍五入并输入Rectangle 构造函数参数。您只需要使用第一种方法确认原点。

【讨论】:

当 PDF 具有固定布局时,这是一个不错的简单解决方案!对于在 macOS 中使用 Preview 的技巧,我会再次投赞成票(如果可以的话!)。让提取变得非常容易。【参考方案4】:

我的回答可能为时已晚,但我认为这并不难。您可以扩展 PDFTextStripper 类并覆盖 writePage() 和 processTextPosition(...) 方法。在您的情况下,我假设列标题始终相同。这意味着您知道每个列标题的 x 坐标,并且可以将数字的 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 坐标以确定每个数字属于哪一列。

数字之间没有空格的原因是你必须设置单词分隔字符串。

我希望这对您或可能正在尝试类似事情的其他人有用。

【讨论】:

【参考方案5】:

PDFLayoutTextStripper 旨在保持数据的格式。

来自自述文件:

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

【讨论】:

使用当前版本的 pdfbox ,PDFParser 实例创建需要更改为 - PDFParser pdfParser = new PDFParser( new RandomAccessBufferedFileInputStream( "sample.pdf")); PDFLayoutTextStripper 不可用【参考方案6】:

我在解析 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);

【讨论】:

对于那些 Windows 用户,从以下位置下载您的 exe 文件:foolabs.com/xpdf/download.html 将变量 proc 指向 pdftotext.exe 文件。如果在主函数中执行,删除函数的返回类型和返回关键字。【参考方案7】:

尝试使用 TabulaPDF (https://github.com/tabulapdf/tabula) 。这是从 PDF 文件中提取表格内容的非常好的库。非常符合预期。

祝你好运。 :)

【讨论】:

提供一个在这种情况下您将如何使用该库的示例将大大有助于提高此答案的质量以及它被投票的机会。【参考方案8】:

从 PDF 中提取数据必然会遇到很多问题。文档是通过某种自动过程创建的吗?如果是这样,您可以考虑将 PDF 转换为未压缩的 PostScript(尝试 pdf2ps)并查看 PostScript 是否包含某种可以利用的常规模式。

【讨论】:

【参考方案9】:

我在读取数据为表格格式的 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 行,依此类推...

【讨论】:

【参考方案10】:

您可以使用 PDFBox 的PDFTextStripperByArea 类从文档的特定区域提取文本。您可以通过识别表格的每个单元格的区域来构建此基础。这不是开箱即用的,但示例 DrawPrintTextLocations 类演示了如何解析文档中单个字符的边界框(解析字符串或段落的边界框会很棒,但我没有在 PDFBox 中看到了对此的支持 - 参见 question)。您可以使用这种方法对所有接触的边界框进行分组,以识别表格的不同单元格。一种方法是维护一组Rectangle2D 区域的boxes,然后为每个解析的字符找到DrawPrintTextLocations.writeString(String string, List&lt;TextPosition&gt; 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

您还可以更进一步,将这些区域的水平和垂直分量分开,从而推断所有表格单元格的区域,无论是否包含任何内容。

我有理由执行这些步骤,并最终使用PDFBox 编写了我自己的PDFTableStripper 类。我已将我的代码共享为gist on GitHub。 main method 给出了如何使用该类的示例:

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

【讨论】:

PDFTableStripperByArea 不存在,你的意思是 PDFTextStripperByArea。【参考方案11】:

我不需要使用 PDFBox 库,所以使用另一个库的解决方案就可以了

卡米洛特和神剑

您可能想尝试 Python 库 Camelot,这是一个 Python 的开源库。如果您不喜欢编写代码,您可以使用围绕 Camelot 创建的 Web 界面 Excalibur。您将文档“上传”到 localhost Web 服务器,然后从该 localhost 服务器“下载”结果。

以下是使用此 python 代码的示例:

import camelot
tables = camelot.read_pdf('foo.pdf', flavor="stream")
tables[0].to_csv('foo.csv')

输入是包含此表的 pdf:

来自PDF-TREX set的示例表

没有为 camelot 提供帮助,它通过查看文本相对对齐方式自行工作。结果以 csv 文件的形式返回:

camelot从样本中提取的PDF表格

可以添加“规则”以帮助 camelot 识别复杂表格中的圆角:

Excalibur 中添加了规则。 Source

GitHub:

卡米洛特:https://github.com/camelot-dev/camelot 神剑:https://github.com/camelot-dev/excalibur

这两个项目都在进行中。

Here是与其他软件的比较(以实际文档为准),Tabula,pdfplumber,pdftables,pdf-table-extract。


我想要的是能够解析文件并知道每个解析出来的数字是什么意思

您无法自动执行此操作,因为 pdf 没有语义结构。

书籍与文档

从语义的角度来看,PDF“文档”是非结构化的(就像记事本文件),pdf文档给出了在哪里打印文本片段的说明,与同一部分的其他片段无关,内容之间没有分隔(要打印什么,以及这是标题的片段、表格还是脚注)和视觉表示(字体、位置等)。 Pdf 是PostScript 的扩展,它描述了一个Hello world!这样页面:

!PS
 /Courier             % font
 20 selectfont        % size
 72 500 moveto        % current location to print at
 (Hello world!) show  % add text fragment
 showpage             % print all on the page

(***)。 可以想象使用相同指令的表格会是什么样子。

我们可以说 html 不是更清晰,但是有一个很大的区别:Html 在语义上描述了内容(标题、段落、列表、表格标题、表格单元格……)并将 css 关联以产生视觉形式,因此内容是完全可访问的。从这个意义上说,html 是 sgml 的简化后代,它设置了约束以允许数据处理:

标记应该描述文档的结构和其他属性 而不是指定需要执行的处理,因为 它不太可能与未来的发展发生冲突。

与 PostScript/Pdf 完全相反。 SGML 用于出版。 Pdf 没有嵌入这种语义结构,它只携带与普通字符串相关的 css 等效项,这些字符串可能不是完整的单词或句子。 Pdf 用于封闭文档,现在用于所谓的workflow management。

在尝试从 pdf 中提取数据的不确定性和困难之后,很明显 pdf 根本不是为将来保留文档内容的解决方案(尽管 Adob​​e 从他们的配对中获得了 pdf standard)。

实际上保存完好的是印刷版,因为 pdf 在创建时完全致力于这一方面。 Pdf 几乎和印刷书籍一样死气沉沉。

当重用内容很重要时,必须再次依靠手动重新输入数据,例如从印刷书籍(可能尝试对其进行一些 OCR)。这越来越真实,因为许多 pdf 甚至阻止使用复制粘贴、在单词之间引入多个空格或在为 Web 使用进行一些“优化”时产生无序字符乱码。

当文档的内容(而不是其印刷形式)很有价值时,那么 pdf 就不是正确的格式。甚至 Adob​​e 也无法从其 pdf 渲染中完美地重新创建文档的源代码。

因此,开放数据永远不应该以 pdf 格式发布,这限制了它们的使用(在允许的情况下),并且使得重用变得更加困难或不可能。

【讨论】:

【参考方案12】:

打印到图像并在其上进行 OCR 怎么样?

听起来非常无效,但实际上 PDF 的目的就是让文本无法访问,你必须做你必须做的事情。

【讨论】:

不要认为您可以详细说明 OCR 可以读取哪些表格? @markdigi:我对 OCR 软件的经验很少。我的惠普打印机免费提供了一个非常笨拙的叫做 ReadIris 的东西,还有一个功能惊人但价格合理的产品 aabby FineReader(我认为)。如果我没记错的话,两者都能够将带有表格的文档读取为 MS Word 格式,并且包括表格。请将此信息作为进一步探索的提示,而不是具体建议。 OCRing 将毫无意义,因为您至少可以在任何 PDF API 中访问实际字符和 X、Y 位置 我用你的方法有序,虽然劳动密集,从PDF文档中的简单表格中提取,我使用PDF X-Change查看器的OCR,然后使用它的选择工具,然后剪切,最后粘贴到电子表格中。这是一个多步骤的过程。选择工具使用 [alt] 键按列选择,尽管不能完全预测(列、行边界并不总是保持不变)。这比转录要好。然而,对于在许多页上拆分的带有标题的长表,这种方法很乏味。我的幼稚理解是 PDF 缺乏可利用的表格形式。【参考方案13】:

http://swftools.org/ 这些家伙有一个 pdf2swf 组件。他们还能够显示表格。 他们也给出了消息来源。所以你可以检查一下。

【讨论】:

【参考方案14】:

如果使用 pdfbox 2.0.6 的 PDF 文件具有“仅矩形表”,则此方法可以正常工作。不适用于任何其他仅矩形表。

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;
    

【讨论】:

如果单元格值有空格,此解决方案将不起作用。【参考方案15】:

对于任何想要和 OP 做同样事情的人(和我一样),经过几天的研究,Amazon Textract 是最好的选择(如果你的数量很少,免费套餐可能就足够了)。

【讨论】:

【参考方案16】:
ObjectExtractor oe = new ObjectExtractor(document);

SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); // Tabula algo.

Page page = oe.extract(1); // extract only the first page

for (int y = 0; y < sea.extract(page).size(); y++) 
  System.out.println("table: " + y);
  Table table = sea.extract(page).get(y);

  for (int i = 0; i < table.getColCount(); i++) 
    for (int x = 0; x < table.getRowCount(); x++) 
      System.out.println("col:" + i + "/lin:x" + x + " >>" + table.getCell(x, i).getText());
    
  

【讨论】:

您应该提及您使用的库。您的代码包含许多不是 PDFBox 或基本 Java 类的类... @mkl 这些类来自tabula-java 啊,谢谢。好吧,考虑到问题是关于“使用 PDFBox 解析 PDF 文件(尤其是表格),这并不清楚。【参考方案17】:

考虑使用 PDFTableStripper.class

该课程在 git 上可用: https://gist.github.com/beldaz/8ed6e7473bd228fcee8d4a3e4525be11#file-pdftablestripper-java-L1

【讨论】:

【参考方案18】:

我不熟悉 PDFBox,但您可以尝试查看 itext。即使主页说 PDF 生成,您也可以进行 PDF 操作和提取。看看它是否适合您的用例。

【讨论】:

有没有使用itext提取文件内容的例子? 我找到了一种使用 iText 阅读内容的简单方法,但它对我没有帮助。使用 PdfTextExtractor 我得到与使用 PDFBox 类似的结果。 :-( 已经有一段时间了,但不是PdfReader然后是.getContent()吗?【参考方案19】:

要从 pdf 文件中读取表格的内容,您只需使用任何 API 将 pdf 文件转换为文本文件(我使用了 iText 的 PdfTextExtracter.getTextFromPage()),然后通过读取该 txt 文件你的java程序..现在读完之后主要任务就完成了..你必须过滤你需要的数据。您可以通过连续使用 String 类的 split 方法来做到这一点,直到找到您的兴趣记录.. 这是我的代码,我通过 PDF 文件提取部分记录并将其写入 .CSV 文件.. PDF 的 URL文件是..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();
        

    

【讨论】:

以上是关于使用 PDFBox 解析 PDF 文件(尤其是表格)的主要内容,如果未能解决你的问题,请参考以下文章

Java 使用PDFBox提取PDF文件中的图片

java中使用pdfbox对pdf文件进行操作时,如何实现插入文本的自动换行操作?

使用 PDFBOX 填写 PDF 表单中的多个字段并在填写后锁定编辑 pdf 文档

Delphi提取PDF文本

如何使用 Apache PDFBox 创建表格

java读取pdf内容