使用Python有效識(shí)別和合并PDF的跨頁(yè)表格
前言
在處理大量包含表格數(shù)據(jù)的PDF文檔時(shí),一個(gè)常見(jiàn)的挑戰(zhàn)是這些表格可能跨越多頁(yè)。手動(dòng)合并這些表格不僅耗時(shí),而且容易出錯(cuò)。幸運(yùn)的是,通過(guò)使用Python和一些強(qiáng)大的庫(kù),我們可以自動(dòng)化這一過(guò)程,有效地識(shí)別和合并跨頁(yè)表格。
1.所需庫(kù)與安裝
為了實(shí)現(xiàn)這個(gè)功能,我們將使用以下Python庫(kù):
- pandas:用于數(shù)據(jù)處理和分析。
- camelot:用于從PDF文檔中提取表格。
- pdfplumber:用于讀取PDF文檔中的文本和布局信息。
- win32com.client:用于將Word文檔轉(zhuǎn)換為PDF(僅限Windows環(huán)境)。
確保你的環(huán)境中安裝了這些庫(kù)。可以通過(guò)運(yùn)行以下命令來(lái)安裝它們:
pip install pandas camelot-py pdfplumber pywin32
import pandas as pd import camelot # 基于文本的 PDF,而不適用于掃描文檔 import os from win32com.client import Dispatch from os import walk import pdfplumber # 文件夾路徑 path = 'E:\\data\\table_json-master' # 閾值,判斷表格是否位于頁(yè)面頂部或底部 topthreshold = 0.2 dthreshold = 0.8 # 將word文件轉(zhuǎn)化為PDF wdFormatPDF = 17
步驟詳解
- 識(shí)別表格邊界
利用pdfplumber,我們可以檢測(cè)每一頁(yè)上的表格邊界,這有助于判斷表格是否位于頁(yè)面頂部或底部,以及是否跨頁(yè)。 - 表格識(shí)別與跨頁(yè)檢查
camelot庫(kù)被用來(lái)識(shí)別PDF文檔中的表格。我們檢查每一表格是否在頁(yè)面的頂部或底部結(jié)束,以此判斷它是否可能是一個(gè)跨頁(yè)表格。 - 跨頁(yè)表格合并
如果確定表格跨頁(yè)并且列數(shù)匹配,我們使用camelot再次讀取這些頁(yè)面并將表格數(shù)據(jù)合并到一個(gè)DataFrame中,然后將結(jié)果保存為CSV或JSON文件。
2.提供 doc2pdf 函數(shù)用于將 Microsoft Word 文檔轉(zhuǎn)換為 PDF 格式
將文檔從 Word 轉(zhuǎn)換為 PDF 的方法非常適合批量轉(zhuǎn)換,即當(dāng)你有多個(gè)需要自動(dòng)轉(zhuǎn)換的 Word 文檔時(shí)。但是請(qǐng)注意,根據(jù)文檔的數(shù)量和大小,這個(gè)操作可能會(huì)比較慢,因?yàn)槊看无D(zhuǎn)換都涉及到打開(kāi) Word,加載文檔,將其保存為 PDF,然后再關(guān)閉文檔和 Word 應(yīng)用程序。
def doc2pdf(input_file):
word = Dispatch('Word.Application')
doc = word.Documents.Open(input_file)
doc.SaveAs(input_file.replace(".docx", ".pdf"), FileFormat=wdFormatPDF)
doc.Close()
word.Quit()
# 遍歷文件夾,將其中的word文件轉(zhuǎn)化為PDF
for root, dirs, filenames in walk(path):
for file in filenames:
if file.endswith(".doc") or file.endswith(".docx"):
doc2pdf(os.path.join(root, file))
win32com.client 庫(kù)是針對(duì) Windows 環(huán)境的,依賴(lài)于機(jī)器上安裝的 Microsoft Word。
3.定義函數(shù),用于識(shí)別PDF文檔中表格的位置,特別是跨頁(yè)表格
- get_table_bounds(page):
這個(gè)函數(shù)接收一個(gè)PDF頁(yè)面對(duì)象,使用pdfplumber庫(kù)的find_tables()方法找到頁(yè)面上的所有表格,并返回一個(gè)包含每個(gè)表格邊界坐標(biāo)的列表。每個(gè)表格的邊界由一個(gè)四元組表示,包含左、頂、右、底的坐標(biāo)。 - is_first_table_at_top_of_page(page):
這個(gè)函數(shù)接收一個(gè)PDF頁(yè)面對(duì)象,計(jì)算頁(yè)面高度,然后獲取第一個(gè)表格的邊界,判斷這個(gè)表格的頂部位置是否低于頁(yè)面頂部的某個(gè)閾值(topthreshold)。如果低于這個(gè)閾值,意味著表格可能從頁(yè)面的頂部開(kāi)始,返回一個(gè)字典,指示這個(gè)條件是否滿(mǎn)足。 - is_last_table_at_bottom_of_page(page):
類(lèi)似于is_first_table_at_top_of_page,但檢查的是最后一個(gè)表格的底部位置是否高于頁(yè)面底部的某個(gè)閾值(dthreshold),以判斷表格是否接近頁(yè)面底部。 - is_table_ending_on_page(pdf_path, page, npage):
這個(gè)函數(shù)接收PDF文件路徑、pdfplumber的頁(yè)面對(duì)象和頁(yè)碼,使用camelot庫(kù)讀取下一頁(yè)的表格。如果存在表格,它檢查最后一個(gè)表格是否接近當(dāng)前頁(yè)面的底部,并返回一個(gè)布爾值和最后一行的列數(shù)。 - is_table_starting_on_page(pdf_path, page, npage):
與is_table_ending_on_page類(lèi)似,但檢查的是下一個(gè)頁(yè)面的第一個(gè)表格是否從頁(yè)面頂部開(kāi)始,并返回一個(gè)布爾值和第一行的列數(shù)。
def is_first_table_at_top_of_page(page):
page_height = page.height
table_bounds_list = get_table_bounds(page)
if table_bounds_list:
first_table_bounds = table_bounds_list[0]
is_first_table_at_top = first_table_bounds[1] < topthreshold * page_height
return {
"is_first_table_at_top": is_first_table_at_top,
}
else:
return {
"is_first_table_at_top": False,
}
def is_last_table_at_bottom_of_page(page):
page_height = page.height
table_bounds_list = get_table_bounds(page)
if table_bounds_list:
last_table_bounds = table_bounds_list[-1]
is_last_table_at_bottom = last_table_bounds[3] > dthreshold * page_height
return {
"is_last_table_at_bottom": is_last_table_at_bottom,
}
else:
return {
"is_last_table_at_bottom": False,
}
def get_table_bounds(page):
table_bounds_list = []
tables = page.find_tables()
for table in tables:
x0, top, x1, bottom = table.bbox
table_bounds_list.append((x0, top, x1, bottom))
return table_bounds_list
def is_table_ending_on_page(pdf_path, page, npage):
tables = camelot.read_pdf(pdf_path, pages=str(npage + 1))
if not tables:
return False, 0
table = tables[-1]
last_row = table.df.iloc[-1]
last_table_result = is_last_table_at_bottom_of_page(page)
return last_table_result['is_last_table_at_bottom'], len(last_row)
def is_table_starting_on_page(pdf_path, page, npage):
tables = camelot.read_pdf(pdf_path, pages=str(npage + 1))
if not tables:
return False, 0
table = tables[0]
first_row = table.df.iloc[0]
first_table_result = is_first_table_at_top_of_page(page)
return first_table_result['is_first_table_at_top'], len(first_row)
4.識(shí)別PDF文件中跨頁(yè)的表格并將其合并
- 獲取PDF文件列表:
使用列表推導(dǎo)式和os.listdir(path)函數(shù),篩選出所有以.pdf結(jié)尾的文件,形成一個(gè)包含所有PDF文件名的列表。 - 遍歷PDF文件: 對(duì)于每一個(gè)PDF文件,首先打印一條信息表明正在處理哪個(gè)文件。
- 構(gòu)造完整的PDF文件路徑。
使用camelot.read_pdf函數(shù)讀取整個(gè)PDF文檔中的所有表格。 - 檢查跨頁(yè)表格: 使用pdfplumber庫(kù)打開(kāi)PDF文件,遍歷除了最后一頁(yè)的所有頁(yè)面。
對(duì)于每一頁(yè),使用之前定義的函數(shù)is_table_ending_on_page和is_table_starting_on_page來(lái)檢查表格是否在該頁(yè)結(jié)束并在下一頁(yè)開(kāi)始。 如果發(fā)現(xiàn)表格跨頁(yè),并且連續(xù)兩頁(yè)的表格具有相同的列數(shù)(意味著它們可能是同一表格的一部分),則記錄這對(duì)頁(yè)面號(hào)。 - 合并跨頁(yè)表格: 如果找到了跨頁(yè)的表格,代碼會(huì)嘗試合并這些表格。 使用一個(gè)列表current_merge來(lái)跟蹤需要合并的頁(yè)面范圍。
當(dāng)遇到新的跨頁(yè)表格序列時(shí),如果前一個(gè)序列尚未合并,先執(zhí)行合并操作。
# 獲取文件夾中所有PDF文件的列表
pdf_files = [f for f in os.listdir(path) if f.endswith('.pdf')]
print(pdf_files)
# 遍歷每個(gè)PDF文件并使用Camelot讀取
for pdf_file in pdf_files:
cross_page_tables = []
print(f'開(kāi)始對(duì){pdf_file}進(jìn)行表格識(shí)別')
pdf_path = os.path.join(path, pdf_file)
tables = camelot.read_pdf(pdf_path, pages='all')
# 導(dǎo)出pdf所有的表格為json文件
tables.export(f'{pdf_file}.json', f='json') # json, excel, html, sqlite, markdown
with pdfplumber.open(pdf_path) as pdf:
for i, page in enumerate(pdf.pages[:-1]):
table_ending, ending_col_count = is_table_ending_on_page(pdf_path, page, i)
if i+ 1 < len(pdf.pages):
table_starting, starting_col_count = is_table_starting_on_page(pdf_path, page, i + 1)
if table_ending and table_starting and ending_col_count == starting_col_count:
cross_page_tables.append((i, i + 1))
print(f" {pdf_file}的表格在第 {i + 1} 頁(yè)和第 {i + 2} 頁(yè)之間跨頁(yè),并且最后一行和下一頁(yè)的第一行列數(shù)相同")
# 處理所有跨頁(yè)表格
if cross_page_tables:
current_merge = []
for (start_page, end_page) in cross_page_tables:
if not current_merge or start_page == current_merge[-1]:
current_merge.append(end_page)
else:
# 合并當(dāng)前跨頁(yè)表格
merged_table = camelot.read_pdf(pdf_path, pages=",".join(map(str, [p + 1 for p in current_merge])))
full_df = pd.concat([table.df for table in merged_table])
full_df.to_csv(f'{pdf_file}_merged_{"_".join(map(str, [p + 1 for p in current_merge]))}.csv',
index=False)
full_df.to_json(f'{pdf_file}_merged_{"_".join(map(str, [p + 1 for p in current_merge]))}.json',
orient='records')
print(f'合并表格已保存為 {pdf_file}_merged_{"_".join(map(str, [p + 1 for p in current_merge]))}.csv 和 {pdf_file}_merged_{"_".join(map(str, [p + 1 for p in current_merge]))}.json')
# 重置合并列表
current_merge = [start_page, end_page]
5.總結(jié)
通過(guò)上述步驟,可以自動(dòng)化地處理大量的PDF文檔,識(shí)別和合并跨頁(yè)表格,大大提高了數(shù)據(jù)處理的效率和準(zhǔn)確性。這種方法特別適用于需要頻繁分析大量文檔的工作場(chǎng)景。
到此這篇關(guān)于使用Python有效識(shí)別和合并PDF的跨頁(yè)表格的文章就介紹到這了,更多相關(guān)Python識(shí)別和合并PDF跨頁(yè)表格內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?的矩陣傳播機(jī)制Broadcasting和矩陣運(yùn)算
這篇文章主要介紹了Python?的矩陣傳播機(jī)制Broadcasting和矩陣運(yùn)算,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06
淺談Python訪問(wèn)MySQL的正確姿勢(shì)
這篇文章主要介紹了淺談Python訪問(wèn)MySQL的正確姿勢(shì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Python結(jié)合多線程與協(xié)程實(shí)現(xiàn)高效異步請(qǐng)求處理
在現(xiàn)代Web開(kāi)發(fā)和數(shù)據(jù)處理中,高效處理HTTP請(qǐng)求是關(guān)鍵挑戰(zhàn)之一,本文將結(jié)合Python異步IO(asyncio)和多線程技術(shù),探討如何優(yōu)化請(qǐng)求處理邏輯,解決常見(jiàn)的線程事件循環(huán)問(wèn)題,有需要的小伙伴可以根據(jù)需求進(jìn)行選擇2025-04-04
python+mysql實(shí)現(xiàn)學(xué)生信息查詢(xún)系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python+mysql實(shí)現(xiàn)學(xué)生信息查詢(xún)系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02
Python利用Diagrams繪制漂亮的系統(tǒng)架構(gòu)圖
Diagrams 是一個(gè)基于Python繪制云系統(tǒng)架構(gòu)的模塊,它能夠通過(guò)非常簡(jiǎn)單的描述就能可視化架構(gòu)。本文將利用它繪制漂亮的系統(tǒng)架構(gòu)圖,感興趣的可以了解一下2023-01-01

