最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot+pdfbox實現(xiàn)解析pdf中的段落和表格數(shù)據(jù)

 更新時間:2025年06月11日 09:51:31   作者:程序員Meteor  
在日常業(yè)務需求中,往往會遇到解析pdf文件中的段落或者表格數(shù)據(jù)的需求,這篇文章主要和大家介紹了SpringBoot結(jié)合tabula和pdfbox解析pdf中的段落和表格數(shù)據(jù)的詳細方法,需要的可以了解下

一、前言

在日常業(yè)務需求中,往往會遇到解析pdf文件中的段落或者表格數(shù)據(jù)的需求。

常見的做法是使用 pdfbox 來做,但是它只能提取文本數(shù)據(jù),沒有我們在文件頁面上面的那種結(jié)構(gòu)化組織,文本通常是散亂的包含各種換行回車空格等格式,因而它適合做一些段落文本提取。

而 tabula 在 pdfbox 的基礎上做了表格的特殊處理,能夠直接讀取到單元格中的內(nèi)容,但是它處理的前提是表格必須常規(guī)完整邊框的表格,只有部分邊框或者無邊框的這種結(jié)構(gòu)化數(shù)據(jù)還是束手無策。

針對上述情況,筆者實現(xiàn)了有邊框和無邊框表格的數(shù)據(jù)讀取并結(jié)構(gòu)化,也支持段落文本提取。

二、功能實現(xiàn)

2.1 引入依賴

<!-- PDF解析,內(nèi)含pdfbox -->
<dependency>
    <groupId>technology.tabula</groupId>
    <artifactId>tabula</artifactId>
    <version>1.0.5</version>
</dependency>

2.2 完整邊框表格

  • 支持多表格
  • 支持分頁
  • 支持跳過標題行
  • 支持跳過標題前無關行
  • 支持生成字段
  • 返回完整集合數(shù)據(jù)

代碼實現(xiàn)

package com.qiangesoft.pdf.util;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import technology.tabula.*;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * pdf工具類
 * ps:適合解析純文本、解析表格數(shù)據(jù)
 *
 * @author qiangesoft
 * @date 2025-05-28
 */
@Slf4j
public class PdfUtil {

    public static void main(String[] args) throws FileNotFoundException {
        String txt = readTxtFromPdf("C:\\Users\\admin\\Desktop\\微信流水.pdf", null);
        System.out.println(txt);

        List<List<Map<String, String>>> dataGroupList = readTableDataFromPdf("C:\\Users\\admin\\Desktop\\微信流水.pdf", null, true);
        for (List<Map<String, String>> list : dataGroupList) {
            for (Map<String, String> map : list) {
                System.out.println(JSON.toJSONString(map));
            }
        }
    }

    /**
     * 解析pdf的文本數(shù)據(jù)
     *
     * @param filePath 文件路徑
     * @param password 文件密碼
     * @return
     */
    public static String readTxtFromPdf(String filePath, String password) throws FileNotFoundException {
        return readTxtFromPdf(new FileInputStream(filePath), password);
    }

    /**
     * 解析pdf的文本數(shù)據(jù)
     *
     * @param inputStream 文件流
     * @param password 文件密碼
     * @return
     */
    public static String readTxtFromPdf(InputStream inputStream, String password) {
        String textContent = "";
        try (PDDocument document = PDDocument.load(inputStream, password)) {
            PDFTextStripper stripper = new PDFTextStripper();
            textContent = stripper.getText(document);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return textContent;
    }

    /**
     * 解析pdf的表格數(shù)據(jù)
     *
     * @param filePath 文件路徑
     * @param password 文件密碼
     * @param skipFirstRow 是否跳過表頭行 【連續(xù)分頁表格可能每頁有表頭】
     * @return
     */
    public static List<List<Map<String, String>>> readTableDataFromPdf(String filePath, String password, boolean skipFirstRow) throws FileNotFoundException {
        return readTableDataFromPdf(new FileInputStream(filePath), password, skipFirstRow);
    }

    /**
     * 解析pdf的表格數(shù)據(jù)
     *
     * @param inputStream 文件流
     * @param password 文件密碼
     * @param skipFirstRow 是否跳過表頭行
     * @return
     */
    public static List<List<Map<String, String>>> readTableDataFromPdf(InputStream inputStream, String password, boolean skipFirstRow) {
        // 按照同一個表格分組
        List<List<Map<String, String>>> dataGroupList = new ArrayList<>();

        // 表格提取算法
        SpreadsheetExtractionAlgorithm algorithm = new SpreadsheetExtractionAlgorithm();

        try (PDDocument document = PDDocument.load(inputStream, password)) {
            ObjectExtractor extractor = new ObjectExtractor(document);
            PageIterator pi = extractor.extract();
            // 遍歷頁
            double x = 0;
            int tableIndex = 0;
            int tableHeadRowNum = 0;
            List<Table> tables = new ArrayList<>();
            List<String> fieldList = new ArrayList<>();
            while (pi.hasNext()) {
                Page page = pi.next();
                List<Table> tableList = algorithm.extract(page);

                // 遍歷表格
                for (Table table : tableList) {
                    if (tableIndex == 0) {
                        tableHeadRowNum = getTableHeadRowNum(table, fieldList);
                        tables.add(table);
                        tableIndex++;
                    } else {
                        // 第一個 or x軸且列數(shù)相同為同一個表格
                        if (new BigDecimal(table.getX()).subtract(new BigDecimal(x)).abs().compareTo(new BigDecimal("0.001")) <= 0
                                && fieldList.size() == table.getRows().get(0).size()) {
                            tables.add(table);
                        } else {
                            List<Map<String, String>> dataList = convertTableToMap(tables, fieldList, tableHeadRowNum, skipFirstRow);
                            dataGroupList.add(dataList);

                            tables = new ArrayList<>();
                            tables.add(table);
                            tableIndex = 0;
                        }
                    }
                    x = table.getX();
                }
            }

            // 最后一個特殊處理
            if (!tables.isEmpty()) {
                List<Map<String, String>> dataList = convertTableToMap(tables, fieldList, tableHeadRowNum, skipFirstRow);
                dataGroupList.add(dataList);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return dataGroupList;
    }

    /**
     * 獲取字段并返回表格頭的行
     *
     * @param table 表格
     * @param fieldList 字段列表
     * @return
     */
    private static int getTableHeadRowNum(Table table, List<String> fieldList) {
        // 獲取表格頭
        int headRowNum = 0;
        List<List<RectangularTextContainer>> rowList = table.getRows();
        for (int i = 0; i < rowList.size(); i++) {
            fieldList.clear();
            List<RectangularTextContainer> cellList = rowList.get(i);
            int k = 0;
            for (int j = 0; j < cellList.size(); j++) {
                RectangularTextContainer cell = cellList.get(j);
                if (cell instanceof Cell) {
                    k++;
                    fieldList.add("k" + k);
                }
            }

            if (fieldList.size() == cellList.size()) {
                headRowNum = i;
                break;
            }
        }

        return headRowNum;
    }

    /**
     * 將表格數(shù)據(jù)轉(zhuǎn)為映射數(shù)據(jù)
     *
     * @param tableList 表格列表
     * @param fieldList 字段列表
     * @param tableHeadRowNum 表格頭行
     * @param skipFirstRow 是否跳過表頭行
     * @return
     */
    private static List<Map<String, String>> convertTableToMap(List<Table> tableList, List<String> fieldList, int tableHeadRowNum, boolean skipFirstRow) {
        List<Map<String, String>> dataList = new ArrayList<>();

        for (int i = 0; i < tableList.size(); i++) {
            // 表格所有行
            Table table = tableList.get(i);
            List<List<RectangularTextContainer>> rowList = table.getRows();

            // 遍歷行
            for (int j = (i == 0 ? tableHeadRowNum + 1 : skipFirstRow ? 1 : 0); j < rowList.size(); j++) {
                List<RectangularTextContainer> cellList = rowList.get(j);
                Map<String, String> data = new HashMap<>();
                // 遍歷列
                for (int m = 0; m < cellList.size(); m++) {
                    RectangularTextContainer cell = cellList.get(m);
                    // 去除換行符后設置值
                    String text = cell.getText().replace("\r", "");
                    data.put(fieldList.get(m), text);
                }
                dataList.add(data);
            }
        }
        return dataList;
    }

    /**
     * 讀取指定文字中間的文本
     *
     * @param txt 文本
     * @param startStr 開始字符串
     * @param endStr 結(jié)束字符串
     * @return
     */
    public static String readTxtFormTxt(String txt, String startStr, String endStr) {
        int index1 = txt.indexOf(startStr);
        if (index1 == -1) {
            return null;
        }
        int index2 = txt.length();
        if (endStr != null) {
            index2 = txt.indexOf(endStr);
            if (index2 == -1) {
                index2 = txt.length();
            }
        }

        return txt.substring(index1 + startStr.length(), index2);
    }

}

解析結(jié)果

2.3 無邊框表格

  • 支持單表格
  • 支持分頁
  • 支持跳過標題行
  • 支持生成字段
  • 返回完整集合數(shù)據(jù)

代碼實現(xiàn)

package com.qiangesoft.pdf.util;

import com.alibaba.fastjson.JSONObject;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;

/**
 * pdf規(guī)則數(shù)據(jù)分析工具類
 * ps:分析處理PdfUtil解決不了的表格,沒有格子
 *
 * @author qiangesoft
 * @date 2025-05-28
 */
public class PdfRuleDataUtil {

    public static void main(String[] args) throws IOException {
        String fileTxt = PdfUtil.readTxtFromPdf("C:\\Users\\admin\\Desktop\\流水文件\\中國建設銀行.pdf", null);
        System.out.println(readTxt(fileTxt, "卡號/賬號:", "客戶名稱:").trim());
        System.out.println(readTxt(fileTxt, "客戶名稱:", "起始日期:").trim());
        System.out.println(readTxt(fileTxt, "起始日期:", "結(jié)束日期:").trim());
        System.out.println(readTxt(fileTxt, "結(jié)束日期:", "序號").trim());
        List<Map<String, String>> dataList = readTableData(fileTxt, "序號 摘要 幣別 鈔匯 交易日期 交易金額 賬戶余額 交易地點/附言 對方賬號與戶名", "生成時間:");
        for (Map<String, String> map : dataList) {
            System.out.println(JSONObject.toJSONString(map));
        }
    }

    /**
     * 解析文本
     *
     * @param fileTxt
     * @param startStr
     * @param endStr
     * @return
     */
    public static String readTxt(String fileTxt, String startStr, String endStr) {
        return PdfUtil.readTxtFormTxt(fileTxt, startStr, endStr);
    }

    /**
     * 解析表格數(shù)據(jù)
     *
     * @param fileTxt 文本數(shù)據(jù)
     * @param startStr 開始字符串 【一般為標題行,字段根據(jù)標題行定,***很重要***】
     * @param endStr 結(jié)束字符串 【結(jié)束標志,如果表格連續(xù)中間沒有重復的標題行則直接使用表格末尾的結(jié)束標志即可,如果表格不連續(xù)每頁都有標題行則使用每頁的結(jié)束標志】
     * @return
     */
    public static List<Map<String, String>> readTableData(String fileTxt, String startStr, String endStr) {
        int length = startStr.trim().split(" ").length;
        List<String> fieldList = new ArrayList<>();
        for (int i = 1; i <= length; i++) {
            fieldList.add("k" + i);
        }

        List<Map<String, String>> lists = new ArrayList<>();
        while (true) {
            String dataStr = readTxt(fileTxt, startStr, endStr);
            if (dataStr == null) {
                break;
            }
            List<Map<String, String>> pageLists = readDataFromTxt(dataStr, startStr, fieldList);
            fileTxt = fileTxt.substring(fileTxt.indexOf(endStr) + endStr.length());
            if (CollectionUtils.isEmpty(pageLists)) {
                break;
            } else {
                lists.addAll(pageLists);
            }
        }

        return lists;
    }

    /**
     * 解析pdf的文本數(shù)據(jù)
     * ps:通過換行符進行分割行,然后根據(jù)空格分割列【如果列中數(shù)據(jù)存在空格則無法解決】
     *
     * @param dataStr 待解析的文本
     * @param tableHeadTxt 標題行文本
     * @param fieldList 字段列表
     * @return
     */
    private static List<Map<String, String>> readDataFromTxt(String dataStr, String tableHeadTxt, List<String> fieldList) {
        List<Map<String, String>> dataList = new ArrayList<>();

        int cellNum = fieldList.size();
        // "\r\n" or "\n"
        String[] split = dataStr.split(System.lineSeparator());

        StringBuilder chargeStr = new StringBuilder();
        for (int a = 0; a < split.length; a++) {
            String itemStr = split[a];
            // 標題行跳過
            if (itemStr.contains(tableHeadTxt)) {
                continue;
            }

            String[] split1;
            if (!chargeStr.toString().isEmpty()) {
                // 上一行未處理【加上本行一起處理】
                chargeStr.append(itemStr);
                split1 = chargeStr.toString().split(" ");
            } else {
                split1 = itemStr.split(" ");
            }

            if (split1.length < cellNum) { // 不足列數(shù)
                // 拼接本行
                if (chargeStr.toString().isEmpty()) {
                    chargeStr.append(itemStr);
                }
                // 最后一行特殊處理
                if (a == split.length - 1) {
                    Map<String, String> dataMap = new HashMap<>();
                    for (int i = 0; i < cellNum; i++) {
                        if (i > split1.length - 1) {
                            dataMap.put(fieldList.get(i), null);
                        } else {
                            dataMap.put(fieldList.get(i), split1[i]);
                        }
                    }
                    dataList.add(dataMap);
                }
            } else if (split1.length > cellNum) { // 超過列數(shù)
                if (!chargeStr.toString().isEmpty()) {
                    // 處理上一行
                    String[] split2 = chargeStr.toString().replace(itemStr, "").split(" ");
                    Map<String, String> dataMap = new HashMap<>();
                    for (int i = 0; i < cellNum; i++) {
                        if (i > split2.length - 1) {
                            dataMap.put(fieldList.get(i), null);
                        } else {
                            dataMap.put(fieldList.get(i), split2[i]);
                        }
                    }
                    dataList.add(dataMap);
                }

                // 處理本行
                chargeStr = new StringBuilder();
                String[] split3 = itemStr.split(" ");
                if (split3.length < cellNum) { // 本行不足列數(shù)
                    // 拼接本行
                    if (chargeStr.toString().isEmpty()) {
                        chargeStr.append(itemStr);
                    }
                    // 最后一行特殊處理
                    if (a == split.length - 1) {
                        Map<String, String> dataMap = new HashMap<>();
                        for (int i = 0; i < cellNum; i++) {
                            if (i > split3.length - 1) {
                                dataMap.put(fieldList.get(i), null);
                            } else {
                                dataMap.put(fieldList.get(i), split3[i]);
                            }
                        }
                        dataList.add(dataMap);
                    }
                } else { // 本行大于等于列數(shù)
                    Map<String, String> dataMap = new HashMap<>();
                    for (int i = 0; i < cellNum; i++) {
                        if (i > split3.length - 1) {
                            dataMap.put(fieldList.get(i), null);
                        } else {
                            dataMap.put(fieldList.get(i), split3[i]);
                        }
                    }
                    dataList.add(dataMap);
                }
            } else { // 等于列數(shù)
                Map<String, String> dataMap = new HashMap<>();
                for (int i = 0; i < cellNum; i++) {
                    dataMap.put(fieldList.get(i), split1[i]);
                }
                dataList.add(dataMap);
                chargeStr = new StringBuilder();
            }
        }

        return dataList;
    }


}

解析結(jié)果

2.4 解析段落

代碼實現(xiàn)

/**
     * 讀取指定文字中間的文本
     *
     * @param txt 文本
     * @param startStr 開始字符串
     * @param endStr 結(jié)束字符串
     * @return
     */
    public static String readTxtFormTxt(String txt, String startStr, String endStr) {
        int index1 = txt.indexOf(startStr);
        if (index1 == -1) {
            return null;
        }
        int index2 = txt.length();
        if (endStr != null) {
            index2 = txt.indexOf(endStr);
            if (index2 == -1) {
                index2 = txt.length();
            }
        }

        return txt.substring(index1 + startStr.length(), index2);
    }

解析結(jié)果

到此這篇關于SpringBoot+pdfbox實現(xiàn)解析pdf中的段落和表格數(shù)據(jù)的文章就介紹到這了,更多相關SpringBoot解析pdf數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 從lombok的val和var到JDK的var關鍵字方式

    從lombok的val和var到JDK的var關鍵字方式

    這篇文章主要介紹了從lombok的val和var到JDK的var關鍵字方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • 詳細盤點Java中鎖的分類

    詳細盤點Java中鎖的分類

    這篇文章主要介紹了詳細盤點Java中鎖的分類,Java中的鎖是一種多線程編程中的同步機制,用于控制線程對共享資源的訪問,防止并發(fā)訪問時的數(shù)據(jù)競爭和死鎖問題,需要的朋友可以參考下
    2023-08-08
  • RabbitMQ消息隊列之持久化機制詳解

    RabbitMQ消息隊列之持久化機制詳解

    這篇文章主要介紹了RabbitMQ消息隊列之持久化機制詳解,持久化,即將原本存在于內(nèi)存中的數(shù)據(jù)寫入到磁盤上永久保存數(shù)據(jù),防止服務宕機時內(nèi)存數(shù)據(jù)的丟失,Rabbitmq 的持久化分為隊列持久化、消息持久化和交換器持久化,需要的朋友可以參考下
    2023-08-08
  • java代碼如何實現(xiàn)存取數(shù)據(jù)庫的blob字段

    java代碼如何實現(xiàn)存取數(shù)據(jù)庫的blob字段

    這篇文章主要介紹了java代碼如何實現(xiàn)存取數(shù)據(jù)庫的blob字段問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java實現(xiàn)的文本字符串操作工具類實例【數(shù)據(jù)替換,加密解密操作】

    Java實現(xiàn)的文本字符串操作工具類實例【數(shù)據(jù)替換,加密解密操作】

    這篇文章主要介紹了Java實現(xiàn)的文本字符串操作工具類,可實現(xiàn)數(shù)據(jù)替換、加密解密等操作,涉及java字符串遍歷、編碼轉(zhuǎn)換、替換等相關操作技巧,需要的朋友可以參考下
    2017-10-10
  • skywalking分布式服務調(diào)用鏈路追蹤APM應用監(jiān)控

    skywalking分布式服務調(diào)用鏈路追蹤APM應用監(jiān)控

    這篇文章主要為大家介紹了skywalking分布式服務調(diào)用鏈路追蹤APM應用監(jiān)控的功能使用說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • SpringBoot過濾器與攔截器深入分析實現(xiàn)方法

    SpringBoot過濾器與攔截器深入分析實現(xiàn)方法

    大家應該都曉得實現(xiàn)過濾器需要實現(xiàn) javax.servlet.Filter 接口,而攔截器會在處理指定請求之前和之后進行相關操作,配置攔截器需要兩步,本文通過實例代碼給大家介紹SpringBoot 過濾器和攔截器的相關知識,感興趣的朋友一起看看吧
    2022-11-11
  • Java動態(tài)線程池插件dynamic-tp集成zookeeper

    Java動態(tài)線程池插件dynamic-tp集成zookeeper

    ZooKeeper是一個分布式的,開放源碼的分布式應用程序協(xié)調(diào)服務,是Google的Chubby一個開源的實現(xiàn),是Hadoop和Hbase的重要組件。它是一個為分布式應用提供一致性的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等
    2023-03-03
  • Java實現(xiàn)MinIO文件上傳的加解密操作

    Java實現(xiàn)MinIO文件上傳的加解密操作

    在云存儲場景中,數(shù)據(jù)安全是核心需求之一,MinIO作為高性能對象存儲服務,支持通過客戶端加密(CSE)在數(shù)據(jù)上傳前完成加密,下面我們來看看如何通過Java實現(xiàn)MinIO文件的加密上傳與解密下載吧
    2025-05-05
  • 使用Java提取和刪除PDF文檔附件的完整指南

    使用Java提取和刪除PDF文檔附件的完整指南

    在日常開發(fā)和文檔處理工作中,我們經(jīng)常會遇到需要操作?PDF?文檔的情況,除了讀取文本和圖片,有時還需要處理?PDF?附件,本文將圍繞?Java?語言,詳細講解如何使用示例代碼來操作?PDF?附件,需要的朋友可以參考下
    2026-04-04

最新評論

松桃| 琼结县| 宜黄县| 临朐县| 绍兴县| 肇庆市| 元谋县| 邯郸县| 平乡县| 清徐县| 新建县| 铁力市| 唐河县| 开平市| 辉县市| 东阳市| 涞水县| 台南县| 基隆市| 怀安县| 泸水县| 博白县| 宁国市| 河间市| 隆林| 什邡市| 岳阳市| 枞阳县| 肃宁县| 白朗县| 徐闻县| 清苑县| 宜州市| 信阳市| 会昌县| 泸水县| 临高县| 法库县| 雷山县| 鹰潭市| 威信县|