You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用PDFBox按表头/表格提取PDF数据及元数据获取问题求助

问题:PDFBox精准提取费用表格数据遇阻
  • 目标需求:从PDF的「Summary Of Charges」表头下,提取费用项名称、数量、金额数据
  • 现存问题:
    • 无法明确区分目标数据属于表头还是表格范畴
    • 尝试获取PDF元数据时,metadata变量返回null
    • 现有代码仅能提取零散文本,无法精准定位表格数据

PDF示例截图:
费用表格示例1
费用表格示例2


问题代码展示

1. 获取元数据返回null的代码

PDDocument document = PDDocument.load(new File("invoice.pdf"));

if(!document.isEncrypted()) {
    PDFTextStripper stripper  = new PDFTextStripper();
    stripper.setSortByPosition(true);
    String text = stripper.getText(document);
    
    PDDocumentInformation info = document.getDocumentInformation();
    PDDocumentCatalog catalog = document.getDocumentCatalog();
    PDMetadata metadata = catalog.getMetadata();
    InputStream stream = metadata.createInputStream();
}
document.close();

2. 仅能提取零散文本的代码

PDDocument document = PDDocument.load(new File("invoice.pdf"));

if(!document.isEncrypted()) {
    PDFTextStripper stripper  = new PDFTextStripper();
    stripper.setSortByPosition(true);
    String text = stripper.getText(document);
    //InputStream stream = metadata.createInputStream();
    System.out.print("Text:" + text);
}
document.close();

解决方案

1. 元数据返回null的说明

PDF的结构化元数据(如XMP)并非所有文件都会嵌入,返回null属于正常情况,无需依赖元数据,直接从文本布局入手提取表格即可。

2. 精准提取表格数据的实现方法

PDFBox原生无表格识别功能,需结合文本位置信息判断行列:

方法一:用PDFTextStripperByArea定位区域

先定位表头位置,再提取下方表格内容(需根据实际PDF调整坐标):

PDDocument document = PDDocument.load(new File("invoice.pdf"));
PDFTextStripperByArea stripper = new PDFTextStripperByArea();
stripper.setSortByPosition(true);

// 定位表头区域(坐标需自行测量调整)
Rectangle2D headerArea = new Rectangle2D.Double(50, 600, 500, 50);
stripper.addRegion("header", headerArea);
stripper.extractRegions(document.getPage(0));
String headerText = stripper.getTextForRegion("header");

if (headerText.contains("Summary Of Charges")) {
    // 定位表头下方的表格区域
    Rectangle2D tableArea = new Rectangle2D.Double(50, 100, 500, 500);
    stripper.addRegion("table", tableArea);
    stripper.extractRegions(document.getPage(0));
    String tableText = stripper.getTextForRegion("table");
    
    // 按行拆分,再按多空格分割列
    String[] rows = tableText.split("\\r?\\n");
    for (String row : rows) {
        String[] columns = row.trim().split("\\s{2,}");
        if (columns.length >= 3) {
            String item = columns[0];
            String quantity = columns[1];
            String amount = columns[2];
            System.out.printf("项目:%s,数量:%s,金额:%s%n", item, quantity, amount);
        }
    }
}
document.close();

方法二:自定义PDFTextStripper获取位置信息

继承PDFTextStripper获取每个字符的坐标,按Y坐标分组行、X坐标排序列:

public class TableTextStripper extends PDFTextStripper {
    private List<TextPosition> textPositions = new ArrayList<>();

    public TableTextStripper() throws IOException {
        super();
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        this.textPositions.addAll(textPositions);
        super.writeString(text, textPositions);
    }

    public List<TextPosition> getTextPositions() {
        return textPositions;
    }
}

使用示例:

PDDocument document = PDDocument.load(new File("invoice.pdf"));
TableTextStripper stripper = new TableTextStripper();
stripper.setSortByPosition(true);
stripper.getText(document);
List<TextPosition> positions = stripper.getTextPositions();

// 按Y坐标分组(同一行Y值接近,取整避免精度问题)
Map<Float, List<TextPosition>> rowMap = new TreeMap<>(Collections.reverseOrder());
for (TextPosition pos : positions) {
    float yKey = Math.round(pos.getY() * 10) / 10.0f;
    rowMap.computeIfAbsent(yKey, k -> new ArrayList<>()).add(pos);
}

// 遍历每行,按X坐标排序拆分列
for (List<TextPosition> row : rowMap.values()) {
    row.sort(Comparator.comparing(TextPosition::getX));
    StringBuilder rowText = new StringBuilder();
    for (TextPosition pos : row) {
        rowText.append(pos.getUnicode());
    }
    String[] columns = rowText.toString().trim().split("\\s{2,}");
    if (columns.length >= 3) {
        System.out.printf("项目:%s,数量:%s,金额:%s%n", columns[0], columns[1], columns[2]);
    }
}
document.close();

3. 简化开发的辅助工具

若PDF结构复杂,可直接使用专门的表格提取库(如Tabula),或集成Apache Tika(基于PDFBox),能快速导出表格数据为CSV/JSON格式,减少自定义开发工作量。


内容的提问来源于stack exchange,提问作者poojay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:01:24