Java高效提取PDF文件指定坐標的文本內(nèi)容實戰(zhàn)代碼
前言
臨時接到一個緊急需要處理的事項。業(yè)務側(cè)一個同事有幾千個PDF文件需要整理:需要從文件中的指定位置獲取對應的編號和地址。
要的急,工作量大。所以就問到技術部有沒有好的解決方案。
問技術的話就只能寫個demo跑下了。
解決辦法
1. 研究下PDF文檔,找出解決方案
PDF的文檔看起來比較簡單,因為只是需要讀取兩個坐標位置的文本內(nèi)容,而且位置相對固定。所以就直接用java的第三方庫pdfbox來操作PDF文檔。
2. 找個能操作PDF的第三方庫pdfbox。
- 先下載pdfbox的jar包。
官網(wǎng)介紹 - pdfbox能干啥:
pdfbox是Apache軟件基金會的一個開源項目,它提供API和工具來處理PDF文檔。
pdfbox是Apache PDFBox的Java版本,它提供了一個類庫,用于讀取,寫入,轉(zhuǎn)換和創(chuàng)建PDF文檔。
pdfbox支持處理各種PDF特性,如文本,字體,圖像,表單字段,注釋,書簽,頁面布局等。
pdfbox還提供了對加密和數(shù)字簽名PDF文檔的支持,以及對PDF文檔的提取和合并。
pdfbox還提供了對PDF文檔的驗證,簽名驗證,加密驗證和數(shù)字簽名的支持。
PDFBox是一個用于處理PDF文檔的Java庫。它提供了一組功能強大的API,可以用于創(chuàng)建、修改和提取PDF文檔的內(nèi)容。PDFBox可以用于各種用途,包括生成PDF文檔、提取文本和圖像、合并和拆分PDF文件、添加水印和書簽等。
PDFBox支持處理各種PDF特性,如文本、字體、圖像、表單字段、注釋、書簽、頁面布局等。它還提供了對加密和數(shù)字簽名PDF文檔的支持,以及對PDF文檔的高級操作,如提取文本位置信息、提取圖像和字體等。
3. maven加載包
pdfbox有三個大的版本,每個版本差異較大,這個時候如果要引入的時候,要注意對應的版本了,否則demo就有可能跑不起來。
pdfbox最新的大版本是3.0。作為新時代的青年,肯定要與時俱進。3.0肯定是要用上的。
4. 先驗證下第三方庫是否可行
下載jar包后,直接用java代碼跑下demo。 demo讀取pdf文檔內(nèi)容并輸出文本數(shù)據(jù)到控制臺
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(); } }
發(fā)現(xiàn)demo跑起來后,報錯。
原因是因為demo是2.0的版本,而當前的jar包是3.0的版本。PDDocument.load這個修改為Loader.load就OK了。
接下來,就是如何獲取到指定坐標位置的文本內(nèi)容。
5. 確認文本在PDF文檔中的坐標位置。
確認PDF文本坐標一般有兩種方案。
1. 代碼校驗(最精準)
先用demo跑下,看下是否可以讀取到指定坐標位置的文本內(nèi)容。
/** * 獲取文檔坐標 * @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后,發(fā)現(xiàn)可以讀取到指定坐標位置的文本內(nèi)容。
這里會有個小問題,就是返回的坐標點有的會有小數(shù)。因為當前返回類型float,所以需要轉(zhuǎn)換成int。
2. 最直接粗暴的方法。
1. 福昕PDF文檔工具。
2. 直接用福昕PDF文檔定位工具定位坐標。
說實話,開發(fā)比較少用這種方式,因為感覺有點lower(其實是自己不太會用)
6. 整個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(); } }
結果能夠正常輸出對應的文本內(nèi)容。
7. 整活上代碼。
奉上全部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 { //要匹配的位置內(nèi)容點 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); // 遞歸調(diào)用,處理子文件夾 } else { String fileName = file.getName(); if (fileName.toLowerCase().endsWith(".pdf")) { mps.add(getLineData(file)); } } } } return mps; } /** * 行數(shù)據(jù) * * @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); // 獲取區(qū)域的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; } }
8. 驗證代碼可行性
整理出來的excel,檢查里面有些空格沒有處理,就讓業(yè)務自己批量替換一下。
因為代碼只是一次性用的,就沒有怎么進行封裝了??傮w來講業(yè)務同事比較滿意。
結論
- 第三方庫pdfbox可以操作PDF文檔。3.0版本之后和歷史版本相差比較大,最好先閱讀下源碼。
- 坐標定位的話,可以用第三方也可以代碼定位
- 如果代碼后續(xù)想復用的話,最好抽離出公共方法
- 文件比較多的情況下,建議增加多線程處理。
總結
到此這篇關于Java高效提取PDF文件指定坐標的文本內(nèi)容的文章就介紹到這了,更多相關Java提取PDF文件指定坐標文本內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中l(wèi)ogback?自動刷新不生效的問題解決
本文主要介紹了Java中l(wèi)ogback?自動刷新不生效的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-05-05一文了解SpringBoot是如何連接數(shù)據(jù)庫的
Spring Boot提供了一系列的開箱即用的功能和特性,使得開發(fā)人員可以快速構建和部署應用程序,下面這篇文章主要給大家介紹了關于SpringBoot是如何連接數(shù)據(jù)庫的相關資料,需要的朋友可以參考下2023-06-06Java異常java.lang.UnsatisfiedLinkError: no opencv_ja
這篇文章主要介紹了Java異常java.lang.UnsatisfiedLinkError: no opencv_java320 in java.library.path的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12jstack報錯Unable to open socket file解決
這篇文章主要為大家介紹了jstack報錯Unable to open socket file的解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-02-02