查看: 121|回复: 1

Python导出PDF表格到CSV:Spire与pdfplumber多方案实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
PDF格式凭借版式固定性广泛应用于文档交换,但其结构化数据(如表格)难以直接抽取。针对带有明确边框的表格,专用提取器能提供更高准确度。本文介绍使用免费库Spire.PDF.Free和备选库pdfplumber、camelot、tabula实现PDF表格导出为CSV的完整方案,包含核心类说明、参数含义、文本清洗逻辑及跨页合并技巧。

一、环境准备

核心依赖为Spire.PDF.Free,执行以下命令安装:
  1. pip install Spire.Pdf.Free
复制代码
此外,导出CSV时使用Python内置csv、os标准库,无需额外依赖。

二、基于Spire.PDF.Free的核心实现

完整代码示例:
  1. from spire.pdf import PdfDocument, PdfTableExtractor
  2. import csv
  3. import os
  4. pdf = PdfDocument()
  5. pdf.LoadFromFile("Sample.pdf")
  6. extractor = PdfTableExtractor(pdf)
  7. output_root = "output/Tables"
  8. os.makedirs(output_root, exist_ok=True)
  9. for page_index in range(pdf.Pages.Count):
  10.     tables = extractor.ExtractTable(page_index)
  11.     for table_index, table in enumerate(tables):
  12.         table_data = []
  13.         row_total = table.GetRowCount()
  14.         for row in range(row_total):
  15.             row_data = []
  16.             col_total = table.GetColumnCount()
  17.             for col in range(col_total):
  18.                 cell_text = table.GetText(row, col).replace("\n", "").strip()
  19.                 row_data.append(cell_text)
  20.             table_data.append(row_data)
  21.         csv_name = f"Page{page_index + 1}-Table{table_index + 1}.csv"
  22.         csv_path = os.path.join(output_root, csv_name)
  23.         with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
  24.             writer = csv.writer(csvfile)
  25.             writer.writerows(table_data)
  26.         print(f"已导出:{csv_path}")
  27. pdf.Dispose()
  28. print("所有表格导出完成")
复制代码

代码解析:
- PdfDocument:主操作类,LoadFromFile()加载本地PDF,Pages.Count获取总页数,Dispose()释放资源。
- PdfTableExtractor:专用表格提取器,绑定已加载的PdfDocument对象;ExtractTable(page_index)返回当前页所有表格对象集合。
- 数据读取:GetRowCount()获取总行数,GetColumnCount()获取总列数,GetText(row,col)读取单元格原始文本。
- 文本清洗:replace("\n","")去除换行符,strip()清除首尾空白,避免CSV格式错乱。
- CSV写入关键配置:encoding="utf-8"防止中文乱码;newline=""避免多余空行;os.makedirs(...,exist_ok=True)自动创建目录。
- 文件命名:Page页码-Table表格序号.csv,便于溯源。

三、备选方案:pdfplumber(通用性最佳)

当表格无明确边框时,Spire可能无法识别,推荐使用pdfplumber。安装:
  1. pip install pdfplumber
复制代码

核心代码示例(自动识别表格并导出):
  1. import pdfplumber
  2. import csv
  3. import os
  4. def pdf_table_to_csv(pdf_path, csv_path, pages='all', table_settings=None):
  5.     base, ext = os.path.splitext(csv_path)
  6.     table_count = 0
  7.     with pdfplumber.open(pdf_path) as pdf:
  8.         if pages == 'all':
  9.             pages_to_process = pdf.pages
  10.         else:
  11.             pages_to_process = [pdf.pages[i] for i in range(pdf.pages) if i+1 in parse_pages(pages)]
  12.         for page_num, page in enumerate(pages_to_process, start=1):
  13.             tables = page.extract_tables(table_settings)
  14.             if not tables:
  15.                 print(f"第 {page_num} 页未找到表格")
  16.                 continue
  17.             for i, table in enumerate(tables):
  18.                 if not table:
  19.                     continue
  20.                 table_count += 1
  21.                 out_path = csv_path if (len(tables)==1 and len(pages_to_process)==1) else f"{base}_{table_count}{ext}"
  22.                 with open(out_path, 'w', newline='', encoding='utf-8') as f:
  23.                     writer = csv.writer(f)
  24.                     for row in table:
  25.                         cleaned_row = [cell if cell is not None else '' for cell in row]
  26.                         while cleaned_row and cleaned_row[-1] == '':
  27.                             cleaned_row.pop()
  28.                         if cleaned_row:
  29.                             writer.writerow(cleaned_row)
  30.                 print(f"已保存表格 {table_count} -> {out_path}")
  31.     print(f"完成!共导出 {table_count} 个表格。")
  32. def parse_pages(page_spec):
  33.     pages = set()
  34.     for part in page_spec.split(','):
  35.         if '-' in part:
  36.             start, end = map(int, part.split('-'))
  37.             pages.update(range(start, end+1))
  38.         else:
  39.             pages.add(int(part))
  40.     return pages
复制代码

调整解析策略(应对复杂表格):
- 有线表格(边框完整):vertical_strategy="lines", horizontal_strategy="lines"
- 无线表格(靠文本对齐):vertical_strategy="text", horizontal_strategy="text"
- 混合模式:vertical_strategy="lines", horizontal_strategy="text"
- 手动指定竖线:vertical_strategy="explicit", explicit_vertical_lines=[100,200,300]

可通过page.debug_tablefinder(table_settings)可视化识别效果。

四、备选方案:camelot-py(高精度)

适用于带清晰边框的表格,需安装OpenCV和Ghostscript。
  1. pip install camelot-py[cv]
复制代码

使用示例:
  1. import camelot
  2. def pdf_table_to_csv_camelot(pdf_path, csv_path, flavor='lattice', pages='all'):
  3.     tables = camelot.read_pdf(pdf_path, flavor=flavor, pages=pages)
  4.     if not tables:
  5.         print("未找到表格")
  6.         return
  7.     for i, table in enumerate(tables):
  8.         out_path = csv_path if i==0 else csv_path.replace('.csv', f'_{i+1}.csv')
  9.         table.to_csv(out_path)
  10.         print(f"已保存表格 {i+1} -> {out_path}")
复制代码

五、备选方案:tabula-py(需Java环境)

安装:pip install tabula-py,确保系统已安装Java 8+。
  1. import tabula
  2. def pdf_table_to_csv_tabula(pdf_path, csv_path, pages='all', area=None):
  3.     dfs = tabula.read_pdf(pdf_path, pages=pages, area=area, multiple_tables=True)
  4.     if not dfs:
  5.         print("未找到表格")
  6.         return
  7.     for i, df in enumerate(dfs):
  8.         out_path = csv_path if i==0 else csv_path.replace('.csv', f'_{i+1}.csv')
  9.         df.to_csv(out_path, index=False)
  10.         print(f"已保存表格 {i+1} -> {out_path}")
复制代码

area参数:[top,left,bottom,right](单位毫米),用于指定页面区域。

六、跨页表格合并

若表格跨页,需手动合并。使用pdfplumber的示例:
  1. def merge_multipage_tables(pdf_path, pages, table_settings=None):
  2.     all_rows = []
  3.     with pdfplumber.open(pdf_path) as pdf:
  4.         for page_num in pages:
  5.             page = pdf.pages[page_num-1]
  6.             tables = page.extract_tables(table_settings)
  7.             if tables:
  8.                 table = tables[0]
  9.                 if all_rows:
  10.                     all_rows.extend(table[1:])  # 跳过第一页表头
  11.                 else:
  12.                     all_rows.extend(table)
  13.     return all_rows
复制代码

七、自动选择最佳策略

可编写智能函数自动尝试多种策略:
  1. def auto_extract_tables(pdf_path, pages='all'):
  2.     strategies = [
  3.         {"vertical_strategy": "lines", "horizontal_strategy": "lines"},
  4.         {"vertical_strategy": "text", "horizontal_strategy": "text"},
  5.         {"vertical_strategy": "lines", "horizontal_strategy": "text"},
  6.         {"vertical_strategy": "text", "horizontal_strategy": "lines"},
  7.     ]
  8.     with pdfplumber.open(pdf_path) as pdf:
  9.         for page in pdf.pages:
  10.             for settings in strategies:
  11.                 tables = page.extract_tables(settings)
  12.                 if tables and len(tables[0]) > 1:
  13.                     return tables
  14.     return None
复制代码

八、拓展应用方向

- 批量处理:使用os.listdir()遍历文件夹,循环调用上述函数。
- 数据二次加工:导出CSV后结合pandas筛选、去重、统计。
- 表头单独处理:识别第一行为表头,写入时单独指定header。
- 过滤空表格:增加数据判空逻辑,跳过无内容表格。

九、总结

通过Spire.PDF.Free可快速提取带边框PDF表格,配合pdfplumber等备选方案可覆盖更复杂的表格类型。在实际集成中,需注意边框依赖、页面限制及资源释放,并根据文档特性调整文本清洗规则。该方法不依赖中间格式,代码结构清晰,适用于自动化数据处理管道。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python导出PDF表格到CSV:Spire与pdfplumber多方案实践

感谢楼主的详细分享!代码结构清晰,注释到位,尤其是Spire.PDF.Free和pdfplumber两个方案的对比很实用。我之前也遇到过无边框表格识别困难的问题,楼主提到跨页合并技巧,能否具体说说跨页表格的合并逻辑?比如当表格跨两页时,是手动拼接行数据还是靠库自动处理?另外,pdfplumber的table_settings参数对中文表格的适应性如何?期待后续更多实践分享!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-10 12:04 , Processed in 0.031191 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部