Java实战:高效提取PDF文件指定坐标的文本内容

使用java获取PDF文档指定坐标的文本内容

前言

临时接到一个紧急需要处理的事项。业务侧一个同事有几千个PDF文件需要整理:需要从文件中的指定位置获取对应的编号和地址。
要的急,工作量大。所以就问到技术部有没有好的解决方案。
问技术的话就只能写个demo跑下了。

解决办法

1. 研究下PDF文档,找出解决方案

PDF的文档看起来比较简单,因为只是需要读取两个坐标位置的文本内容,而且位置相对固定。所以就直接用java的第三方库pdfbox来操作PDF文档。

2. 找个能操作PDF的第三方库pdfbox。

  1. 先下载pdfbox的jar包。
    官网介绍
  2. pdfbox能干啥:
    • pdfbox是Apache软件基金会的一个开源项目,它提供API和工具来处理PDF文档。

    • pdfbox是Apache PDFBox的Java版本,它提供了一个类库,用于读取,写入,转换和创建PDF文档。

    • pdfbox支持处理各种PDF特性,如文本,字体,图像,表单字段,注释,书签,页面布局等。

    • pdfbox还提供了对加密和数字签名PDF文档的支持,以及对PDF文档的提取和合并。

    • pdfbox还提供了对PDF文档的验证,签名验证,加密验证和数字签名的支持。

    • PDFBox是一个用于处理PDF文档的Java库。它提供了一组功能强大的API,可以用于创建、修改和提取PDF文档的内容。PDFBox可以用于各种用途,包括生成PDF文档、提取文本和图像、合并和拆分PDF文件、添加水印和书签等。

    • PDFBox支持处理各种PDF特性,如文本、字体、图像、表单字段、注释、书签、页面布局等。它还提供了对加密和数字签名PDF文档的支持,以及对PDF文档的高级操作,如提取文本位置信息、提取图像和字体等。

3. maven加载包

      pdfbox有三个大的版本,每个版本差异较大,这个时候如果要引入的时候,要主要版本了,否则demo就有可能跑不起来。
      ![pdfbox三个版本官方说明](https://img-blog.csdnimg.cn/3a822ec1571f4e088431d58704756781.png)
      作为新时代的青年,肯定要与时俱进。3.0肯定是要用上的。

3. 先验证下第三方库是否可行

下载jar包后,直接用java代码跑下demo。 demo读取pdf文档内容并输出文本数据到控制台

    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.text.PDFTextStripper;

    import java.io.File;
    import java.io.IOException;
    public class PDFBoxDemo {
        public static void main(String[] args) throws IOException {
            PDDocument document = PDDocument.load(new File("D:\\pdf\\test.pdf"));
            PDFTextStripper stripper = new PDFTextStripper();
            String text = stripper.getText(document);
            System.out.println(text);
            document.close();
        }
    }

发现demo跑起来后,报错。
原因是因为demo是2.0的版本,而当前的jar包是3.0的版本。PDDocument.load这个修改为Loader.load就OK了。

接下来,就是如何获取到指定坐标位置的文本内容。

4. 确认文本在PDF文档中的坐标位置。

确认PDF文本坐标一般有两种方案。

1. 代码校验(最精准)

先用demo跑下,看下是否可以读取到指定坐标位置的文本内容。

 /**
    * 获取文档坐标
    * @param  file PDF文件对象
    * @param sourceTex 匹配的字符
    * @return 坐标
    */
   public static Point getPoint(File file,String sourceTex) {
       Point point = new Point();
       //获取文档坐标
      try {
        PDDocument document =  Loader.loadPDF(file);
        PDFTextStripper textStripper = new PDFTextStripper() {
            @Override
            protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
                if (text.contains(targetText)) {
                    TextPosition textPositionStart = textPositions.get(0);
                    TextPosition textPositionEnd = textPositions.get(textPositions.size()-1);
                    point.setX(textPositionStart.getX());
                    point.setY(textPositionStart.getY()); 
                }
            }
        };

        textStripper.setSortByPosition(true);
        textStripper.setStartPage(1);
        textStripper.setEndPage(document.getNumberOfPages());

        textStripper.getText(document);

        document.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return point;
    }

跑完demo后,发现可以读取到指定坐标位置的文本内容。
这里会有个小问题,就是返回的坐标点有的会有小数。因为当前返回类型float,所以需要转换成int。

2. 最直接粗暴的方法。

  1. 福昕PDF文档工具。
  2. 直接用福昕PDF文档定位工具定位坐标。
  说实话,开发比较少用这种方式,因为感觉有点lower(其实是自己不太会用)

5. 整个demo先验证第三方库是否可行。

拿1个文件试试水

 public static void main(String[] args) {
        String filePath = "D:\\test\\test.pdf";
         try {
            PDDocument document = Loader.loadPDF(file);
            PDFTextStripperByArea  textStripper = new PDFTextStripperByArea ();
            Rectangle rectangle = new Rectangle(80,120, 250,10);
            String regionName = "regionName";
            textStripper.addRegion(regionName, rectangle);
            PDPage page = document.getPage(0);
            textStripper.extractRegions(page);
            String text = textStripper.getTextForRegion(regionName);
  
            System.out.println(text);
          
            textStripper.setSortByPosition(true);
            textStripper.setStartPage(1);
            textStripper.setEndPage(document.getNumberOfPages());
            textStripper.getText(document);
            document.close();
        }catch (IOException e) {
            e.printStackTrace();
        }
      
    }

结果能够正常输出对应的文本内容。

6. 整活上代码。

奉上全部demo代码

package com.example.demo;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson2.JSON;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.PDFTextStripperByArea;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;


import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Desc: 验证pdfbox的可行性
 *
 * @author admin
 * @date since 2023/8/8 18:44
 */

public class PdfDemo {
	//要匹配的位置内容点
    private  static final String[] target= {"name", "address"};
    public static void main(String[] args) {
       ExcelWriter excelWriter= ExcelUtil.getWriter("D:\\test\\pdf\\test.xls");
       String folderPath = "D:\\test\\pdf";
       File folder = new File(folderPath);
       if (folder.exists() && folder.isDirectory()) {
           List<Map<String,Object>>  mps =  listPdfFiles(folder);
           excelWriter.write(mps, true);
       } else {
           System.out.println("Invalid folder path.");
       }
       excelWriter.close();
    }
	/**
     * 获取pdf文件列表
     *
     * @param folder 文件夹
     * @return {@code List<Map<String,Object>>}
     */
    private static  List<Map<String,Object>>  listPdfFiles(File folder) {
        List<Map<String,Object>> mps = new ArrayList<>();
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    listPdfFiles(file); // 递归调用,处理子文件夹
                } else {
                    String fileName = file.getName();
                    if (fileName.toLowerCase().endsWith(".pdf")) {
                        mps.add(getLineData(file));
                    }
                }
            }
        }
        return mps;
    }
    /**
     * 行数据
     *
     * @param file 文件
     * @return {@code Map<String,Object>}
     */
    public static Map<String,Object> getLineData(File file){
        Map<String,Object> lineData = new HashMap<>(target.length+2);
        List<Point> pointList =  getPoint(file);
        String[]  arr=  getPointValue(file, pointList.stream().map(s -> new Rectangle(s.getX(), s.getY(), 260, 10)).toArray(Rectangle[]::new));
        if(arr.length>=target.length) {
        for(int i=0;i<target.length;i++)
        {
            lineData.put(target[i], arr[i]);
        }
            lineData.put("fileName", file.getName().toLowerCase().replace(".pdf", ""));
        }
      return lineData;
    }
 	/**
     * 获得PDF指定坐标点文本值
     *
     * @param file       文件
     * @param rectangles 矩形坐标
     * @return {@code String[]}
     */
    public  static String[] getPointValue( File file,Rectangle... rectangles){
        String[] textArr = new String[rectangles.length];
       // String text="";
        try {
            PDDocument document = Loader.loadPDF(file);
            PDFTextStripperByArea  textStripper = new PDFTextStripperByArea ();

            for(int i = 0; i < rectangles.length;i++   ) {
                Rectangle rectangle =rectangles[i];
                String regionName = "regionName"+rectangle.getX()+rectangle.getY();
                textStripper.addRegion(regionName, rectangle);
                PDPage page = document.getPage(0);
                textStripper.extractRegions(page);
                // 获取区域的text
                String text = textStripper.getTextForRegion(regionName);
                text = text.replace("\u0000","-").replace(" ","");
                System.out.println(">>text"+text);
                textArr[i]=text;
            }

            textStripper.setSortByPosition(true);
            textStripper.setStartPage(1);
            textStripper.setEndPage(document.getNumberOfPages());

            textStripper.getText(document);

            document.close();
        }catch (IOException e) {
            e.printStackTrace();
        }

        return  textArr;
    }

    public  static List<Point> getPoint( File file){
        List<Point> pointList=new ArrayList<>();
        try {
        PDDocument document =  Loader.loadPDF(file);
        PDFTextStripper textStripper = new PDFTextStripper() {
            @Override
            protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
                for(String target:target){
                    if (text.contains(target)) {
                        Point point = new Point();
                        TextPosition textPositionEnd = textPositions.get(textPositions.size() - 1);
                        point.setX((int) textPositionEnd.getEndX());
                        point.setY((int) textPositionEnd.getY());
                        pointList.add(point);
                    }
                }
            }
        };

        textStripper.setSortByPosition(true);
        textStripper.setStartPage(1);
        textStripper.setEndPage(document.getNumberOfPages());
        textStripper.getText(document);
        document.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(">>>>>pointList" + JSON.toJSONString(pointList));
        return pointList;
    }
} 

7. 验证代码可行性

整理出来的excel,检查里面有些空格没有处理,就让业务自己批量替换一下。
因为代码只是一次性用的,就没有怎么进行封装了。总体来讲业务同事比较满意。

结论

  1. 第三方库pdfbox可以操作PDF文档。3.0版本之后和历史版本相差比较大,最好先阅读下源码。
  2. 坐标定位的话,可以用第三方也可以代码定位
  3. 如果代码后续想复用的话,最好抽离出公共方法
  4. 文件比较多的情况下,建议增加多线程处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/68343.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嗅探抓包工具,解决线上偶现问题来不及抓包的情况阅读目录

目录 背景 实现思路 具体实现 Python 抓包 总结 资料获取方法 背景 测试群里经常看到客户端的同学反馈发现了偶现Bug&#xff0c;但是来不及抓包&#xff0c;最后不了了之&#xff0c;最近出现得比较频繁&#xff0c;所以写个小脚本解决这个问题。 实现思路 之前写过一个…

OPENCV C++(十)gramm矫正+直方图均衡化

两者都是只对单通道使用&#xff0c;对多通道的话 就需要分离通道处理再合并通道 两种方法&#xff0c;第一个要运算次数太多了&#xff0c;第二个只需要查表 伽马矫正函数&#xff0c;这里用第二种方法&#xff0c;且写法有点高级 int gammaCorrection(cv::Mat srcMat, cv::…

享元模式(C++)

定义 运用共享技术有效地支持大量细粒度的对象。 使用场景 在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中&#xff0c;从而带来很高的运行时代价——主要指内存需求方面的代价。如何在避免大量细粒度对象问题的同时&#xff0c;让外部客户程序仍…

爬虫如何应对网站的反爬机制?如何查找user-agent对应的值

import requestsurl https://movie.douban.com/top250 response requests.get(url) # 查看结果 print(response)在requests使用一文中我们有讲到&#xff0c;当状态码不是200时表示爬虫不可用&#xff0c;也就是说我们获取不到网页源代码。但是我们还是可以挣扎一下&#xff…

uniapp-原生地图截屏返回base64-进行画板编辑功能

一、场景 vue写uniapp打包安卓包&#xff0c;实现原生地图截屏&#xff08;andirod同事做的&#xff09;-画板编辑功能 实现效果&#xff1a; 二、逻辑步骤简略 1. 由 原生地图nvue部分&#xff0c;回调返回 地图截屏生成的base64 数据&#xff0c; 2. 通过 uni插件市场 im…

LeaferUI - 性能强悍、简洁轻量的 HTML5 Canvas 2D 图形 UI 绘图框架,用于 web 端在线图形设计、图表、白板、数据可视化等场景

最近想做一个轻巧的在线画册和海报设计工具&#xff0c;最近发布的 LeaferUI 特别适合这样的场景。 LeaferUI 是什么&#xff1f; Leafer UI 是基于 LeaferJS 开发的一套绚丽多彩的 UI 绘图框架&#xff0c;帮助开发者快速生成图形界面。LeaferJS 是一个基于 HTML5 Canvas 开…

Spring Cloud构建微服务断路器介绍

什么是断路器 断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置&#xff0c;用于在电路上保护线路过载&#xff0c;当线路中有电器发生短路时&#xff0c;“断路器”能够及时的切断故障电路&#xff0c;防止发生过载、发热、甚至起火等严重后果…

On Evaluation of Embodied Navigation Agents 论文阅读

论文信息 题目&#xff1a;On Evaluation of Embodied Navigation Agents 作者&#xff1a;Peter Anderson&#xff0c;Angel Chang 来源&#xff1a;arXiv 时间&#xff1a;2018 Abstract 过去两年&#xff0c;导航方面的创造性工作激增。这种创造性的输出产生了大量有时不…

Rust 原生支持龙架构指令集

导读近日&#xff0c;Rust 开源社区发布 1.71.0 版本&#xff0c;实现对龙架构&#xff08;LoongArch&#xff09;指令集的原生支持。 龙架构操作系统发行版和开发者可基于上游社区源代码构建或直接下载 Rust 开源社区发布的龙架构二进制版本。Rust 开发者将在龙架构平台上获得…

beego实现文件上传到七牛云详细教程

文章目录 安装获取凭证配置app.conf上代码调用示例ps 安装 执行命令&#xff1a; go get github.com/qiniu/go-sdk/v7获取凭证 Go SDK 的所有的功能&#xff0c;都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access Key和Secret Key&#xff0c;这对密钥可以…

系列七、RocketMQ如何保证顺序消费消息

一、概述 所谓顺序消费指的是可以按照消息的发送顺序来进行消费。例如一笔订单产生了3条消息&#xff0c;即下订单》减库存》增加订单&#xff0c;消费时要按照顺序消费才有意义&#xff0c;要不然就乱套了&#xff08;PS&#xff1a;你总不能订单还没下&#xff0c;就开始减库…

Vue-2.nodejs的介绍和安装

nodejs简介 ► 创建 Node.js 应用:package.json 首先&#xff0c;创建一个新文件夹以便于容纳需要的所有文件&#xff0c;并且在此其中创建一个 package.json 文件&#xff0c;描述你应用程序以及需要的依赖&#xff1a; 配合着你的 package.json 请运行 npm install。如果你…

基于ffmpeg与SDL的视频播放库

由于工作需要&#xff0c;自己封装的基于ffmpeg的视频编解码库&#xff0c;显示采用了SDL库。可以播放本地文件或网络流&#xff0c;支持多端口播放&#xff0c;支持文字叠加&#xff0c;截图、视频录制等等。 头文件代码&#xff1a; #pragma once #ifdef __DLLEXPORT #defin…

SpringCloud实用篇5——elasticsearch基础

目录 1.初识elasticsearch1.1 了解ES1.1.1 elasticsearch的作用1.1.2 ELK技术栈1.1.3 elasticsearch和lucene1.1.4 总结 1.2.倒排索引1.2.1.正向索引1.2.2.倒排索引1.2.3.正向和倒排 1.3 es的一些概念1.3.1 文档和字段1.3.2 索引和映射1.3.3 mysql与elasticsearch 1.4 部署单点…

flutter开发实战-TextPainter计算文本内容的宽度

flutter开发实战-TextPainter计算文本内容的宽度 最近开发过程中根据Text文本的大小判断是否需要进行显示跑马灯效果&#xff0c;获取文本的大小&#xff0c;需要TextPainter来获取Size 一、TextPainter TextPainter主要用于实现文本的绘制。TextPainter类可以将TextSpan渲染…

浏览器多管闲事之跨域

年少时的梦想就是买一台小霸王游戏机 当时的宣传语就是小霸王其乐无穷~。 大些了&#xff0c;攒够了零花钱&#xff0c;在家长的带领下终于买到了 那一刻我感觉就是最幸福的人 风都是甜的&#xff01; 哪成想... 刚到家就被家长扣下了 “”禁止未成年人玩游戏机 (问过卖家了&a…

Window下安装MinGW64

欢迎来到我的酒馆 介绍Windows下&#xff0c;安装MinGW64。 目录 欢迎来到我的酒馆二.MinGW64三.配置系统环境变量 二.MinGW64 从sourceforge下载mingw64&#xff0c; sourceforge下载MinGW https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 下…

香港站群服务器为什么适合seo优化?

​  香港站群为什么适合seo优化?本文主要从以下四点出发进行原因阐述。 1.香港站群服务器的优势 2.香港站群服务器与国内服务器的对比 3.多IP站群服务器的优势 4.香港站群服务器在SEO优化中的注意事项 1.香港站群服务器的优势 香港站群服务器是为了满足企业SEO优化需求而提供…

实践分享:Vue 项目如何迁移小程序

最近我们小组刚经历了将成熟的 HTML5 项目转换成小程序&#xff0c;并在app中运行的操作&#xff01;记录下来分享给各位。 项目&#xff1a;将已有的 Vue 项目转为小程序&#xff0c; 在集成了FinClip SDK 的 App 中运行。 技术&#xff1a;uni-app、FinClip 两个注意事项&…

[每周一更]-(第57期):用Docker、Docker-compose部署一个完整的前后端go+vue分离项目

文章目录 1.参考项目2.技能点3.GO的Dockerfile配置后端的结构如图Dockerfile先手动docker调试服务是否可以启动报错 4.Vue的Dockerfile配置前端的结构如图nginx_docker.confDockerfile构建 5.docker-compose 整合前后端docker-compose.yml错误记录&#xff08;1&#xff09;ip端…