欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java利用Tabula實現(xiàn)對PDF內(nèi)表格數(shù)據(jù)提取

 更新時間:2023年09月14日 08:28:25   作者:kida_yuan  
Tabula是一個開源工具,用于從PDF文檔中提取表格數(shù)據(jù),下面小編就來和大家詳細介紹一下java如何通過Tabula對PDF文件內(nèi)表格進行數(shù)據(jù)提取吧

某天項目組來了個需求說需要提取 PDF 文件中數(shù)據(jù)作為數(shù)據(jù)沉淀使用,這是因為第三方系統(tǒng)不提供數(shù)據(jù)接口所以只能夠出此下策。

就據(jù)我所知,PDF 文件內(nèi)數(shù)據(jù)提取目前有 3 種解決方案:

第一種,資金足夠的話可以直接通過人工智能對 PDF 內(nèi)容進行解析,按照你需要的規(guī)格數(shù)據(jù)進行輸出即可;

第二種,采用 OCR 識別技術(shù)對內(nèi)容進行提??;

第三種,通過工具實現(xiàn)(也是我將為您呈現(xiàn)的)。在開源社區(qū)中 PDFbox 人氣很高,文字的識別率也很不錯,但是對于表格支持不太友好,涉及到表格數(shù)據(jù)提取的我選用了 Tabula 來實現(xiàn);

Tabula 是什么

Tabula是一個開源工具,用于從PDF文檔中提取表格數(shù)據(jù)。它的主要技術(shù)包括:

  • PDF 解析:Tabula 使用 Java 的 PDFBox 庫來解析 PDF 文檔的內(nèi)容和布局。它可以定位到每個頁的文本塊和圖像的坐標(biāo);
  • 表格識別:Tabula 通過分析頁面上的線條和文本塊的布局來識別表格的結(jié)構(gòu)。它會查找垂直和水平的線條作為列和行的分隔符;
  • 單元格提取:在確定了表格的結(jié)構(gòu)后,Tabula 會分析每個單元格對應(yīng)的文本塊,并提取出單元格中的文本內(nèi)容;
  • 數(shù)據(jù)整理:Tabula 會嘗試自動整理從表格中提取的數(shù)據(jù),例如:縱向和橫向合并單元格,處理跨頁的表格等。它也會執(zhí)行一定的文本清理;
  • 導(dǎo)出格式:Tabula 支持將提取出來的數(shù)據(jù)導(dǎo)出為 CSV 和 JSON 格式。用戶可以導(dǎo)入到 Excel 等其他工具中進行后續(xù)分析。
  • 優(yōu)化算法:Tabula 在表格分析和數(shù)據(jù)提取方面使用了一些優(yōu)化的算法和啟發(fā)式規(guī)則,以提高正確率。同時它也提供了交互式的編輯接口供用戶校正結(jié)果。

怎么用 Tabula

首先肯定是引入 pom 文件依賴,如下圖:

<dependency>
  <groupId>technology.tabula</groupId>
  <artifactId>tabula</artifactId>
  <version>1.0.5</version>
</dependency>

接著就可以創(chuàng)建 PDF 工具類了(PdfUtil)

public class PdfUtil {
      ...
      private static final SpreadsheetExtractionAlgorithm SPREADSHEEET_EXTRACTION_ALGORITHM = new SpreadsheetExtractionAlgorithm();
      private static final ThreadLocal<List<String>> THREAD_LOCAL = new ThreadLocal<>();
      ...
      /**
       * @description: 解析pdf表格(私有方法)
       *               使用 tabula-java 的 sdk 基本上都是這樣來解析 pdf 中的表格的,所以可以將程序提取出來,直到 cell
       *               單元格為止
       * @param {*} String pdf 路徑
       * @param {*} int 自定義起始行
       * @param {*} PdfCellCallback 特殊回調(diào)處理
       * @return {*}
       */
      private static JSONArray parsePdfTable(String pdfPath, int customStart, PdfCellCustomProcess callback) {
            JSONArray reJsonArr = new JSONArray(); // 存儲解析后的JSON數(shù)組
            try (PDDocument document = PDDocument.load(new File(pdfPath))) {
                  PageIterator pi = new ObjectExtractor(document).extract(); // 獲取頁面迭代器
                  // 遍歷所有頁面
                  while (pi.hasNext()) {
                        Page page = pi.next(); // 獲取當(dāng)前頁
                        List<Table> tableList = SPREADSHEEET_EXTRACTION_ALGORITHM.extract(page); // 解析頁面上的所有表格
                        // 遍歷所有表格
                        for (Table table : tableList) {
                              List<List<RectangularTextContainer>> rowList = table.getRows(); // 獲取表格中的每一行
                              // 遍歷所有行并獲取每個單元格信息
                              for (int rowIndex = customStart; rowIndex < rowList.size(); rowIndex++) {
                                    List<RectangularTextContainer> cellList = rowList.get(rowIndex); // 獲取行中的每個單元格
                                    callback.handler(cellList, rowIndex, reJsonArr);
                              }
                        }
                  }
            } catch (IOException e) {
                  LOGGER.error(MARKER,
                              "function[PdfUtil.parsePdfTable] Exception [{} - {}] stackTrace[{}]",
                              e.getCause(), e.getMessage(), e.getStackTrace());
            } finally {
                  THREAD_LOCAL.remove();
            }
            return reJsonArr; // 返回解析后的JSON數(shù)組
      }
      ...
}

這里我們先按照官網(wǎng)樣例代碼來實現(xiàn) pdf 表格解析先。大致的思路就是:

  • 創(chuàng)建一個空的 JSONArray 對象 reJsonArr ,用于存儲解析后的表格數(shù)據(jù);
  • 使用 PDDocument.load 方法加載指定路徑的 PDF 文件,并使用 try-with-resources 語句創(chuàng)建一個 PDDocument 對象 document ;
  • 使用 ObjectExtractor 從 document 中提取頁面迭代器 pi ;
  • 使用 while 循環(huán)遍歷每個頁面,使用 pi.hasNext 方法判斷是否還有下一個頁面,如果有則進入循環(huán);
  • 使用 pi.next 方法獲取當(dāng)前頁面對象 page ;
  • 使用 SPREADSHEEET_EXTRACTION_ALGORITHM 解析 page 中的所有表格,并將結(jié)果存儲在 tableList 中;
  • 使用 for 循環(huán)遍歷 tableList 中的每個表格,對于每個表格執(zhí)行以下操作: a. 使用 table.getRows 方法獲取表格中的每一行,并將結(jié)果存儲在 rowList 中; b. 使用 for 循環(huán)遍歷 rowList 中的每一行,從 customStart 位置開始,對于每一行執(zhí)行以下操作: i. 使用 rowList.get 方法獲取行中的每個單元格,并將結(jié)果存儲在 cellList 中; ii. 將 cellList 、 rowIndex 和 reJsonArr 作為參數(shù)傳遞給回調(diào)函數(shù) callback 的 handler 方法進行處理;
  • 使用 try-catch 語句捕獲可能發(fā)生的 IOException 異常,并記錄錯誤信息;
  • 使用 finally 語句移除 THREAD_LOCAL 中的數(shù)據(jù);
  • 返回解析后的 JSONArray 對象 reJsonArr ;

這里要加上一個 callback.handler 回調(diào)函數(shù)主要的目的是為了將“單元格操作”跟 pdf 解析兩部分代碼進行解耦,那么這個回調(diào)接口的接口定義如下:

@FunctionalInterface
public interface PdfCellCustomProcess {
      /**
       * @description: 自定義單元格回調(diào)處理
       * @return {*}
       */
      void handler(List<RectangularTextContainer> cellList, int rowIndex, JSONArray reJsonArr);
}

其中 cellList 傳入的是這一行的所有單元格的集合,rowIndex 傳入的是當(dāng)前行碼,reJsonArr 是返回值。具體的實現(xiàn)代碼如下:

public class PdfUtil {
      ...
      /**
       * @description: 解析 pdf 中簡單的表格并返回 json 數(shù)組
       * @param {*} String PDF文件路徑
       * @param {*} int 自定義起始行
       * @return {*}
       */
      public static JSONArray parsePdfSimpleTable(String pdfPath, int customStart) {
            return parsePdfTable(pdfPath, customStart, (cellList, rowIndex, reArr) -> {
                  JSONObject jsonObj = new JSONObject();
                  // 遍歷單元格獲取每個單元格內(nèi)字段內(nèi)容
                  List<String> headList = ObjectUtil.isNullObj(THREAD_LOCAL.get()) ? new ArrayList<>()
                              : THREAD_LOCAL.get();
                  for (int colIndex = 0; colIndex < cellList.size(); colIndex++) {
                        String text = cellList.get(colIndex).getText().replace("\r", " ");
                        if (rowIndex == customStart) {
                              headList.add(text);
                        } else {
                              jsonObj.put(headList.get(colIndex), text);
                        }
                  }
                  if (rowIndex == customStart) {
                        THREAD_LOCAL.set(headList);
                  }
                  if (!jsonObj.isEmpty()) {
                        reArr.add(jsonObj);
                  }
            });
      }
     ...
}

代碼的主要部分是一個 Lambda 表達式,它作為參數(shù)傳遞給 parsePdfTable 方法。Lambda 表達式做了PdfCellCustomProcess 接口的實現(xiàn)。Lambda 表達式的代碼塊首先創(chuàng)建一個 JSONObject 對象,然后遍歷單元格列表,獲取每個單元格的文本內(nèi)容。

如果當(dāng)前行索引等于自定義起始行索引,將文本內(nèi)容添加到 headList 列表中;否則,將文本內(nèi)容作為鍵值對添加到j(luò)sonObj 對象中。最后,如果 jsonObj 對象不為空,則將其添加到 reArr 數(shù)組中。 代碼還包含了一些其他操作。如果當(dāng)前行索引等于自定義起始行索引,將 headList 列表設(shè)置為 THREAD_LOCAL 線程局部變量。最后,返回 reArr數(shù)組作為方法的結(jié)果。

最后只需要補上 main 方法調(diào)用即可獲取到解析后的 JsonArray 集合。但是直接輸出 JsonArray 數(shù)據(jù)并不直觀,于是我又寫了一個解析 JsonArray 數(shù)據(jù)的方法,并將里面的數(shù)據(jù)轉(zhuǎn)換為 Markdown 格式,如下圖:

private static String outputMdFormatForVerify(JSONArray jsonArr) {
        StringBuilder mdStrBld = new StringBuilder();
        StringBuilder headerStrBld = new StringBuilder("|");
        StringBuilder segmentStrBld = new StringBuilder("|");
        for (int row = 0; row < jsonArr.size(); row++) {
              StringBuilder bodyStrBld = new StringBuilder("|");
              JSONObject rowObj = (JSONObject) jsonArr.get(row);
              if (row == 0) {
                    rowObj.forEach((k, v) -> {
                          headerStrBld.append(" ").append(k).append(" |");
                          segmentStrBld.append(" ").append("---").append(" |");
                    });
                    headerStrBld.append("\n");
                    segmentStrBld.append("\n");
                    mdStrBld.append(headerStrBld).append(segmentStrBld);
              }
              rowObj.forEach((k, v) -> bodyStrBld.append("").append(v).append("|"));
              bodyStrBld.append("\n");
              mdStrBld.append(bodyStrBld);
        }
        return mdStrBld.toString();
}

這個應(yīng)該比較好理解吧,這里就不再詳述了。

以上的代碼對于一般的 PDF 表格解析是基本沒有問題的,但是對于帶有合并單元格的解析就不能滿足了。合并單元格需要考慮橫向合并、縱向合并和混合合并三種合并模式,不是說 tabula-java 的 sdk 不能做只是比較麻煩,在 tabula-java 方案中我們可以獲取到單元格的高和寬,那么先做一次全遍歷獲取二維數(shù)組對于單元格定位后,根據(jù)高和寬進行虛擬表格的建設(shè),最后根據(jù)二維數(shù)組對數(shù)據(jù)進行回填即可。這也是用回調(diào)將單元格操作分離的原因之一,為了后面做合并單元格解析做準(zhǔn)備的。

但其實上面說這么多,合并單元格解析的代碼我還沒寫呢(以上都是我吹的),等完成后再給大家分享。

到此這篇關(guān)于java利用Tabula實現(xiàn)對PDF內(nèi)表格數(shù)據(jù)提取的文章就介紹到這了,更多相關(guān)java PDF提取數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot實現(xiàn)Read Through模式的操作過程

    SpringBoot實現(xiàn)Read Through模式的操作過程

    Read Through模式通常是指一種緩存策略,其中當(dāng)應(yīng)用程序嘗試讀取數(shù)據(jù)時,緩存系統(tǒng)首先被檢查以查看數(shù)據(jù)是否已經(jīng)存在于緩存中,這篇文章主要介紹了SpringBoot實現(xiàn)Read Through模式,需要的朋友可以參考下
    2024-07-07
  • Java?RabbitMQ的持久化和發(fā)布確認(rèn)詳解

    Java?RabbitMQ的持久化和發(fā)布確認(rèn)詳解

    這篇文章主要為大家詳細介紹了RabbitMQ的持久化和發(fā)布確認(rèn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • SpringBoot mybatis 實現(xiàn)多級樹形菜單的示例代碼

    SpringBoot mybatis 實現(xiàn)多級樹形菜單的示例代碼

    這篇文章主要介紹了SpringBoot mybatis 實現(xiàn)多級樹形菜單的示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-05-05
  • mybatis中association和collection的使用與區(qū)別

    mybatis中association和collection的使用與區(qū)別

    在 MyBatis 中,<association>?和?<collection>?是用于配置結(jié)果映射中關(guān)聯(lián)關(guān)系的兩個元素,本文主要介紹了mybatis中<association>和<collection>的使用與區(qū)別,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • Java如何不解壓讀取.zip的文件內(nèi)容

    Java如何不解壓讀取.zip的文件內(nèi)容

    這篇文章主要給大家介紹了關(guān)于Java如何不解壓讀取.zip的文件內(nèi)容的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • SpringBoot使用自定義json解析器的使用方法

    SpringBoot使用自定義json解析器的使用方法

    本篇文章主要介紹了SpringBoot使用自定義json解析器的使用方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例

    java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例

    這篇文章主要介紹了java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例,需要的朋友可以參考下
    2014-05-05
  • SpringBoot3-yaml文件配置方式

    SpringBoot3-yaml文件配置方式

    這篇文章主要介紹了SpringBoot3-yaml文件配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Java實現(xiàn)接口限流方案

    Java實現(xiàn)接口限流方案

    這篇文章主要為大家詳細介紹了Java實現(xiàn)接口限流方案,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • spring cloud config 配置中心快速實現(xiàn)過程解析

    spring cloud config 配置中心快速實現(xiàn)過程解析

    這篇文章主要介紹了spring cloud config 配置中心快速實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-08-08

最新評論