本文目录导读:

在Java中解析PDF文件,有多种成熟的第三方库可供选择,最主流的方案包括 Apache PDFBox、iText(注意版本和许可)以及 PDFjet。
以下是基于 Apache PDFBox(免费、开源、功能全面)的详细案例解析。
主流库对比
| 库 | 许可证 | 特点 | 适用场景 |
|---|---|---|---|
| Apache PDFBox | Apache 2.0 | 免费开源,功能全面(提取文本、元数据、图片、创建PDF、表单处理) | 大多数项目首选 |
| iText | AGPL (社区版) / 商业版 | 性能高,功能强大,但社区版有严格的版权限制(需开源或购买商业许可) | 需要生成复杂PDF的商用项目 |
| PDFjet | 商业版 | 轻量级,速度快 | 特殊商业场景 |
推荐:如果您是个人学习或企业内训(无商业分发),使用 PDFBox 最稳妥。
环境准备:Maven依赖
在你的 pom.xml 中添加 PDFBox 依赖:
<!-- Apache PDFBox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.3</version> <!-- 建议使用最新稳定版 -->
</dependency>
注意:如果只需要提取文本,上述依赖即可,如果需要处理图片,可能还需要添加 jai-imageio-core 等依赖。
核心案例实战
假设我们有一个 sample.pdf 文件,包含以下内容:员工薪资报告
- 表格数据:姓名、基本薪资、绩效
- 一个Logo图片
案例1:提取纯文本内容(最基础)
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.File;
import java.io.IOException;
public class PdfTextExtractor {
public static void main(String[] args) {
String filePath = "path/to/your/sample.pdf";
try (PDDocument document = Loader.loadPDF(new File(filePath))) {
// 判断文档是否加密
if (document.isEncrypted()) {
System.out.println("PDF文件已加密,需要密码才能打开。");
// 尝试解密:document.openProtection(new StandardDecryptionMaterial("password"));
return;
}
// 创建文本剥离器
PDFTextStripper stripper = new PDFTextStripper();
// 可选:设置页码范围(默认全部)
stripper.setStartPage(1);
stripper.setEndPage(document.getNumberOfPages());
// 提取文本
String text = stripper.getText(document);
System.out.println("=== 提取的内容 ===");
System.out.println(text);
} catch (IOException e) {
System.err.println("读取PDF时出错:" + e.getMessage());
e.printStackTrace();
}
}
}
常见问题:
- 如果提取出来的文本乱码,通常是PDF内嵌字体问题,PDFBox会尽量解析,但某些定制字体可能无法完美提取。
- 如果PDF是扫描件(图片),PDFBox无法提取文字,这种情况需要使用 OCR。
案例2:提取表格数据(重点难点)
PDF内部没有“表格”这个概念(只有文本和线条),要提取表格数据,需要结合坐标位置判断。
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class TableExtractor extends PDFTextStripper {
public TableExtractor() throws IOException {
super();
}
public static void main(String[] args) throws IOException {
String filePath = "path/to/your/sample.pdf";
try (PDDocument document = Loader.loadPDF(new File(filePath))) {
TableExtractor extractor = new TableExtractor();
extractor.setSortByPosition(true); // 必须按位置排序
extractor.document = document;
// 逐页解析
for (int page = 1; page <= document.getNumberOfPages(); page++) {
extractor.setStartPage(page);
extractor.setEndPage(page);
extractor.getText(document);
}
}
}
/**
* 重写该方法,按行处理每个字符的位置信息
*/
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
// textPositions 包含了该行中每个字符的坐标 (x, y, width, height)
// 通过判断 y 坐标变化来识别换行
for (TextPosition pos : textPositions) {
float x = pos.getX(); // 字符左上角X坐标
float y = pos.getY(); // 字符左上角Y坐标
String charUnicode = pos.getUnicode();
System.out.printf("坐标(%.2f, %.2f) -> 字符: %s%n", x, y, charUnicode);
}
}
}
解析表格的逻辑:
- 设定一个“列X坐标阈值”(50, 150, 300, 450)。
- 对每一行(相同y值),根据字符x坐标落入哪个区间,来判断属于哪一列。
- 组合成二维数据。
替代方案:使用专门的表格解析库 Tabula-java(基于PDFBox,专门优化表格提取)。
案例3:提取图片
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ImageExtractor {
public static void main(String[] args) throws IOException {
String filePath = "path/to/your/sample.pdf";
String outputDir = "extracted_images/";
new File(outputDir).mkdirs();
try (PDDocument document = Loader.loadPDF(new File(filePath))) {
int imageCounter = 1;
for (PDPage page : document.getPages()) {
PDResources resources = page.getResources();
// 遍历页面上的所有XObject(包括图片、表单等)
for (String cosName : resources.getXObjectNames()) {
PDXObject xobject = resources.getXObject(cosName);
if (xobject instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) xobject;
BufferedImage bufferedImage = image.getImage();
// 保存为PNG文件
String fileName = String.format("image_%03d.png", imageCounter);
ImageIO.write(bufferedImage, "png", new File(outputDir + fileName));
System.out.println("已保存: " + fileName);
imageCounter++;
}
}
}
}
}
}
注意:PDF中图片可能是分割的(如大图分块渲染),该代码会逐个保存小图片。
进阶技巧与注意事项
-
处理加密PDF:
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial; // 提供密码 document.openProtection(new StandardDecryptionMaterial("user_password")); -
保留排版:
- 使用
PDFTextStripper时,可以设置setAddEndMark(true)保留一些标记。 - 如果需要高度保真排版,考虑
PDFLayoutTextStripper(社区提供,非官方)。
- 使用
-
性能优化:
- 使用
Loader.loadPDF(file, password, memoryUsageSetting)控制内存使用。 - 处理超大PDF时,可以用
PDFParser配合RandomAccessReadBufferedFile流式读取。
- 使用
-
乱码问题:
- 检查PDF是否使用了 CID 字体(亚洲语言常见),PDFBox 3.x 对CJK支持较好,但特定字体仍需引入
pdfbox-fontbox。 - 如果字符被映射为自定义编码,可以考虑提取原始字符代码:
String unicode = pos.getUnicode(); // 可能为空 int code = pos.getCode(); // 原始字符代码
- 检查PDF是否使用了 CID 字体(亚洲语言常见),PDFBox 3.x 对CJK支持较好,但特定字体仍需引入
-
表单数据提取: PDFBox 有专门的
PDAcroForm类处理交互式表单(AcroForm)。PDDocumentCatalog catalog = document.getDocumentCatalog(); PDAcroForm acroForm = catalog.getAcroForm(); for (PDField field : acroForm.getFields()) { System.out.println(field.getFullyQualifiedName() + ": " + field.getValueAsString()); }
| 需求 | 推荐方法 |
|---|---|
| 提取纯文本 | PDFTextStripper(PDFBox) |
| 提取表格 | Tabula-java 或 手动坐标分析(PDFBox) |
| 提取图片 | PDImageXObject.getImage()(PDFBox) |
| 提取表单数据 | PDAcroForm(PDFBox) |
| 生成/修改PDF | iText(功能强大)或 PDFBox(基础功能) |
| OCR扫描件PDF | Tesseract + PDFBox(先提取图片再OCR) |
最佳实践:先下载 PDFBox 源码中的 examples 包(pdfbox-examples),里面有从简单到复杂的各种官方示例。