Java案例怎么解析PDF文件?

wen java案例 73

本文目录导读:

Java案例怎么解析PDF文件?

  1. 主流库对比
  2. 环境准备:Maven依赖
  3. 核心案例实战
  4. 进阶技巧与注意事项

在Java中解析PDF文件,有多种成熟的第三方库可供选择,最主流的方案包括 Apache PDFBoxiText(注意版本和许可)以及 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);
        }
    }
}

解析表格的逻辑

  1. 设定一个“列X坐标阈值”(50, 150, 300, 450)。
  2. 对每一行(相同y值),根据字符x坐标落入哪个区间,来判断属于哪一列。
  3. 组合成二维数据。

替代方案:使用专门的表格解析库 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中图片可能是分割的(如大图分块渲染),该代码会逐个保存小图片。


进阶技巧与注意事项

  1. 处理加密PDF

    import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
    import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
    // 提供密码
    document.openProtection(new StandardDecryptionMaterial("user_password"));
  2. 保留排版

    • 使用 PDFTextStripper 时,可以设置 setAddEndMark(true) 保留一些标记。
    • 如果需要高度保真排版,考虑 PDFLayoutTextStripper(社区提供,非官方)。
  3. 性能优化

    • 使用 Loader.loadPDF(file, password, memoryUsageSetting) 控制内存使用。
    • 处理超大PDF时,可以用 PDFParser 配合 RandomAccessReadBufferedFile 流式读取。
  4. 乱码问题

    • 检查PDF是否使用了 CID 字体(亚洲语言常见),PDFBox 3.x 对CJK支持较好,但特定字体仍需引入 pdfbox-fontbox
    • 如果字符被映射为自定义编码,可以考虑提取原始字符代码
      String unicode = pos.getUnicode(); // 可能为空
      int code = pos.getCode();          // 原始字符代码
  5. 表单数据提取: 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),里面有从简单到复杂的各种官方示例。

抱歉,评论功能暂时关闭!