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() {@Overrideprotected 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);// 获取区域的textString 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() {@Overrideprotected 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. 文件比较多的情况下,建议增加多线程处理。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

想使用cpolar内网穿透,如何下载安装?

如何下载安装并使用cpolar内网穿透 在不算久远的过去&#xff0c;哪位同学家中能有一台电脑&#xff0c;一定能收获其他同学羡慕的目光。随着科技和经济的发展&#xff0c;电脑在个人用户及商业群体中快速普及&#xff0c;也让电脑成为各类工作的中心。但想要让电脑能够发挥效…

TartanVO: A Generalizable Learning-based VO 论文阅读

论文信息 题目:TartanVO: A Generalizable Learning-based VO 作者&#xff1a;Wenshan Wang&#xff0c; Yaoyu Hu 来源&#xff1a;ICRL 时间&#xff1a;2021 代码地址&#xff1a;https://github.com/castacks/tartanvo Abstract 我们提出了第一个基于学习的视觉里程计&…

数组对象去重的几种方法

场景&#xff1a; let arrObj [{ name: "小红", id: 1 },{ name: "小橙", id: 1 },{ name: "小黄", id: 4 },{ name: "小绿", id: 3 },{ name: "小青", id: 1 },{ name: "小蓝", id: 4 } ]; 方法一&#xff1a;…

《Python入门到精通》函数详解

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 函数 1、函数的调用2、函数的参数2.1、变量的就近原则2.2、传递参数2.3、形参和实…

SAP使用函数NUMBER_GET_NEXT创建流水号

1. 系统中设定流水号&#xff1b;使用T-Code&#xff1a;SNRO来创建一个流 输入Object&#xff1a;ZLC_001&#xff0c;然后单击创建。 然后输入Shorttext, Long text, Number length domain在写程序的时候应该会另外创建&#xff0c;这里测试就使用料号的Domain MATNR来做,其他…

http、https笔记

目录 HTTP 基本概念状态码&#xff1a;get和post的区别&#xff1a;http 常⻅字段&#xff1a;http的缺点&#xff1a; HTTP/1.1HTTP/3HTTPSHTTPS和HTTP区别对称加密和⾮对称加密⾮对称加密 HTTP 基本概念 状态码&#xff1a; 1xx 中间状态&#xff0c;比如post的continue 20…

接口数据处理

调取接口 增删改查 增 对接口的数据添加字段 url: urlData.filter((urlItem) > urlItem.id item.id),url: urlData.find((urlItem) > urlItem.id item.id)//理想&#xff0c;不能实现 url: urlData.find((urlItem) > {if (urlItem.id item.id) {return urlItem.u…

ubuntu 安装 nvidia 驱动

ubuntu 安装 nvidia 驱动 初环境与设备查询型号查询对应的驱动版本安装驱动验证驱动安装结果 本篇文章将介绍ubuntu 安装 nvidia 驱动 初 希望能写一些简单的教程和案例分享给需要的人 环境与设备 系统&#xff1a;ubuntu 设备&#xff1a;Nvidia GeForce RTX 4090 查询型…

史上最牛,接口自动化测试-yaml框架配置文件总结(超级详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 yaml文件是一种配…

TCPDF生成PDF文件,含jpjraph生成雷达图

TCPDF生成PDF文件&#xff0c;含jpjraph生成雷达图 依赖自行安装 "tecnickcom/tcpdf": "^6.6","amenadiel/jpgraph": "4"雷达图生成 中文字体添加安装 没有封装&#xff0c;只作为测试案例展示 // 创建新的PDF文档$pdf new \TCPD…

【代码】表格封装 + 高级查询 + 搜索 +分页器 (极简)

一、标题 查询条件按钮&#xff08;Header&#xff09; <!-- Header 标题搜索栏 --> <template><div><div class"header"><div class"h-left"><div class"title"><div class"desc-test">…

vscode 搭建STM32开发环境

1.需要软件 1.1 vscode 1.2 STM32CubeMX&#xff0c;这个不是必须的&#xff0c;我是为了方便生成STM32代码 2.vscode配置 2.1安装keil Assistant 2.2配置keil Assistant 3.STMCUBE生成个STM32代码 &#xff0c;如果有自己的代码可以忽略 4.代码添加到vscode&#xff0c;并…

简单程度与自负是否相关?探索STM32的学习价值

事实上&#xff0c;无论STM32是否简单并不重要&#xff0c;更重要的是我们能通过学习STM32获得什么。通过STM32&#xff0c;我们可以学习到许多知识&#xff1a;如果我们制作一个键盘或鼠标&#xff0c;我们可以学习USB协议。如果我们制作一个联网设备&#xff0c;我们需要学习…

完整版:TCP、UDP报文格式

目录 TCP报文格式 报文格式 报文示例 UDP报文格式 报文格式 报文示例 TCP报文格式 报文格式 图1 TCP首部格式 字段长度含义Source Port16比特源端口&#xff0c;标识哪个应用程序发送。Destination Port16比特目的端口&#xff0c;标识哪个应用程序接收。Sequence Numb…

Spring Boot读取yml或者properties配置信息

文章目录 Spring Boot读取yml或者properties配置信息方法一&#xff1a;Value获取基本信息&#xff0c;适用于少量信息方法二&#xff1a;通过注解ConfigurationProperties(prefix "spring.datasource")方法三&#xff1a;通过api Environment Spring Boot读取yml或…

第01天 什么是CSRF ?

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 每天一个知识点 ✨特色专栏&#xff1…

Spring 知识点

Spring 1.1 Spring 简介 1.1.1 Spring 概念 Spring是一个轻量级Java开发框架&#xff0c;最早有Rod Johnson创建为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。使现有的技术更加容易使…

统信UOS下eclipse使用lombok报错的问题

lombok不兼容问题 lombok不支持高版本jdk&#xff0c;本人在应用商店下载eclipse安装的&#xff0c;默认用的jdk17&#xff0c;不兼容lombok插件&#xff0c;需要调整eclipse.ini配置文件&#xff0c;如下&#xff1a; #/opt/apps/org.eclipse.java-ee/files/eclipse.ini -ja…

静态网页加速器:优化性能和交付速度的 Node.js 最佳实践

如何使用 Node.js 发布静态网页 在本文中&#xff0c;我们将介绍如何使用 Node.js 来发布静态网页。我们将创建一个简单的 Node.js 服务器&#xff0c;将 HTML 文件作为响应发送给客户端。这是一个简单而灵活的方法&#xff0c;适用于本地开发和轻量级应用。 1、创建静态网页…