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

问题代码展示
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
相关产品推荐
相关产品推荐

