首页 > 编程开发 > Python    日期:2026-06-10 / 浏览

PDF 格式因其版式固定性而广泛用于文档交换,但也导致结构化数据(如表格)难以直接抽取。尽管存在多种解析方案(如基于文本坐标启发式或 OCR),但对于具有明确边框和单元格边界的表格,专用表格提取器能提供更高的准确度。本文将介绍如何使用免费库 Free Spire.PDF for Python 解析 PDF 表格,并结合 Python 内置 csv 标准库完成数据导出。

一、环境准备

1. 依赖库安装

本次实现依赖 PDF 解析组件与 Python 原生库,执行以下命令安装免费版 PDF 解析组件:

pip install Spire.Pdf.Free

2. 技术说明

  • 核心依赖:spire.pdf.free 负责识别、提取 PDF 中的表格结构与单元格数据;
  • 文件导出:使用 Python 内置 csvos 标准库完成目录创建、CSV 写入,无需额外第三方数据处理库;
  • 适用场景:支持单页/多页 PDF、单页多表格场景,可自动按页面+表格序号拆分输出文件;
  • 兼容性:导出 UTF-8 编码 CSV,可直接用 Excel、WPS、Pandas 等工具读取。

二、核心实现思路

  • 初始化 PDF 文档对象,加载本地 PDF 文件;
  • 创建表格提取器,绑定已加载的 PDF 文档;
  • 遍历 PDF 所有页面,逐页提取当前页面内的全部表格;
  • 针对每个表格,逐行、逐列读取单元格文本,并做基础文本清洗;
  • 自动创建输出目录,将单张表格数据写入独立 CSV 文件,文件按页面、表格编号命名;
  • 执行资源释放操作,释放 PDF 文档占用的内存与文件流。

三、完整代码实现

该示例完整还原逐页、逐表提取逻辑,自动创建输出目录,每个表格单独生成 CSV 文件,附带文本清洗逻辑:

from spire.pdf import PdfDocument, PdfTableExtractor
import csv
import os

# 初始化PDF文档对象并加载文件
pdf = PdfDocument()
pdf.LoadFromFile("Sample.pdf")

# 创建PDF表格提取器
extractor = PdfTableExtractor(pdf)

# 定义输出目录,目录不存在则自动创建
output_root = "output/Tables"
os.makedirs(output_root, exist_ok=True)

# 遍历PDF每一页
for page_index in range(pdf.Pages.Count):
    # 提取当前页面下所有表格
    tables = extractor.ExtractTable(page_index)
    
    # 遍历当前页面中的每一个表格
    for table_index, table in enumerate(tables):
        table_data = []
        
        # 读取表格所有行
        row_total = table.GetRowCount()
        for row in range(row_total):
            row_data = []
            # 读取当前行所有列
            col_total = table.GetColumnCount()
            for col in range(col_total):
                # 提取单元格文本,去除换行符、首尾空白字符
                cell_text = table.GetText(row, col).replace("\n", "").strip()
                row_data.append(cell_text)
            table_data.append(row_data)
        
        # 拼接CSV文件路径,按 页面-表格 编号命名
        csv_name = f"Page{page_index + 1}-Table{table_index + 1}.csv"
        csv_path = os.path.join(output_root, csv_name)
        
        # 写入CSV文件,指定UTF-8编码,newline避免多余空行
        with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerows(table_data)
        print(f"已导出:{csv_path}")

# 释放PDF文档资源
pdf.Dispose()
print("所有表格导出完成")

四、代码解析

4.1 核心类说明

  • PdfDocumentPDF 文档操作主类,LoadFromFile() 用于加载本地 PDF 文件,Pages.Count 获取文档总页数,Dispose() 用于释放文件流、内存等资源,批量处理文件时必须调用,防止内存堆积。
  • PdfTableExtractor专用表格提取器,依赖已加载的 PdfDocument 对象初始化;ExtractTable(page_index) 接收页码索引,返回当前页面内所有表格对象集合。

4.2 表格数据读取方法

  • GetRowCount():获取当前表格的总行数;
  • GetColumnCount():获取当前行的总列数;
  • GetText(row, col):根据行、列索引读取单元格原始文本。

4.3 文本清洗逻辑

replace("\n", "").strip() 是实用的预处理逻辑:

  • 去除单元格内的换行符,避免 CSV 格式错乱;
  • 清除文本首尾空格、制表符,保证数据整洁。

4.4 CSV 写入关键配置

  • encoding="utf-8":统一使用 UTF-8 编码,从根源避免中文乱码;
  • newline="":Python csv 库专属配置,禁止自动插入空行,保证 CSV 格式标准;
  • os.makedirs(..., exist_ok=True):自动创建多级目录,exist_ok=True 表示目录已存在时不抛出异常。

4.5 文件命名规则

文件采用 Page页码-Table表格序号.csv 命名,可直观区分数据来源,方便后续文件管理与溯源。

五、拓展应用方向

  • 批量处理多文件结合 os.listdir() 遍历指定文件夹,批量读取目录下所有 PDF,循环调用封装函数实现全量表格导出。
  • 数据二次加工导出 CSV 后结合 pandas 库,实现数据筛选、去重、统计、格式转换等数据分析操作。
  • 表头单独处理识别表格第一行为表头,写入 CSV 时单独指定表头行,提升数据可读性。
  • 过滤空表格在代码中增加数据判空逻辑,跳过无有效内容的空白表格,减少无效文件生成。

六、知识扩展

将 PDF 中的表格导出为 CSV 是办公自动化中的常见需求。由于 PDF 的格式多样性,没有“万能”的解析工具,但可根据表格特点选择合适的 Python 库。下面介绍三种主流方案,并提供可直接运行的代码。

方案选择速览

方案 适用表格类型 安装难度 优点
pdfplumber 带边框、无边框皆可 低(纯 Python) 轻量、易用、支持中文
camelot-py 带清晰边框的表格 中(需 OpenCV) 精度高,支持复杂表格
tabula-py 各种规则表格 中(需 Java) 功能强大,可指定区域

若 PDF 为扫描件(无文本层),需先用 OCR 提取文字,再交给表格解析器。本指南不涉及 OCR,专注于文本型 PDF。

推荐方案:pdfplumber(通用性最好)

pdfplumber 能自动识别页面中的表格,并支持提取合并单元格、调整解析策略,适合大多数场景。

1. 安装

pip install pdfplumber

2. 代码示例:提取所有表格并保存为 CSV

import pdfplumber
import csv
import os

def pdf_table_to_csv(pdf_path, csv_path, pages='all', table_settings=None):
    """
    将 PDF 中的所有表格导出为 CSV(每个表格独立保存,若多个表格则生成多个文件)。
    
    参数:
        pdf_path: PDF 文件路径
        csv_path: CSV 输出路径(若多个表格,自动添加 _1, _2 后缀)
        pages: 要处理的页码,如 'all', '1-3', [1,2]
        table_settings: 解析表格的参数字典,例如:
            {
                "vertical_strategy": "lines",   # 识别竖向线条
                "horizontal_strategy": "lines", # 识别横向线条
                "explicit_vertical_lines": [],
                "explicit_horizontal_lines": [],
                "snap_tolerance": 3,
                "snap_x_tolerance": 3,
                "snap_y_tolerance": 3,
                "edge_min_length": 3,
                "min_words_vertical": 1,
                "min_words_horizontal": 1,
                "intersection_tolerance": 3,
                "text_tolerance": 3,
            }
    """
    base, ext = os.path.splitext(csv_path)
    table_count = 0

    with pdfplumber.open(pdf_path) as pdf:
        # 确定要处理的页面
        if pages == 'all':
            pages_to_process = pdf.pages
        else:
            pages_to_process = [pdf.pages[i] for i in range(pdf.pages) if i+1 in parse_pages(pages)]

        for page_num, page in enumerate(pages_to_process, start=1):
            # 提取表格
            tables = page.extract_tables(table_settings)
            if not tables:
                print(f"第 {page_num} 页未找到表格")
                continue

            for i, table in enumerate(tables):
                if not table:  # 空表格
                    continue
                table_count += 1
                # 生成输出文件名
                if len(tables) == 1 and len(pages_to_process) == 1:
                    out_path = csv_path
                else:
                    out_path = f"{base}_{table_count}{ext}"

                # 写入 CSV
                with open(out_path, 'w', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f)
                    for row in table:
                        # 清洗行:将 None 转为空字符串,去除行尾空单元格
                        cleaned_row = [cell if cell is not None else '' for cell in row]
                        # 去掉末尾连续的空值(可选)
                        while cleaned_row and cleaned_row[-1] == '':
                            cleaned_row.pop()
                        if cleaned_row:  # 只写非空行
                            writer.writerow(cleaned_row)

                print(f"已保存表格 {table_count} -> {out_path}")

    print(f"完成!共导出 {table_count} 个表格。")

def parse_pages(page_spec):
    """解析页码字符串 '1-3,5' -> [1,2,3,5]"""
    pages = set()
    for part in page_spec.split(','):
        if '-' in part:
            start, end = map(int, part.split('-'))
            pages.update(range(start, end+1))
        else:
            pages.add(int(part))
    return pages

# 使用示例
if __name__ == "__main__":
    pdf_table_to_csv("example.pdf", "output.csv", pages="1-2")

3. 调整解析策略(应对复杂表格)

若默认解析效果不佳,可自定义 table_settings。常用策略:

有线表格(表格边框完整):

table_settings = {
    "vertical_strategy": "lines",
    "horizontal_strategy": "lines",
}

无线表格(靠文本对齐):

table_settings = {
    "vertical_strategy": "text",
    "horizontal_strategy": "text",
}

混合模式(既有线又有文本):

table_settings = {
    "vertical_strategy": "lines",
    "horizontal_strategy": "text",
}

手动指定竖线位置(例如表格只有外框,内部无竖线):

table_settings = {
    "vertical_strategy": "explicit",
    "explicit_vertical_lines": [100, 200, 300],  # 页面上的 x 坐标(点)
    "horizontal_strategy": "lines",
}

可通过 page.debug_tablefinder(table_settings) 查看识别到的表格区域。

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

对于带有清晰边框的表格,camelot 提供 lattice 模式,提取精度很高。

1. 安装

pip install camelot-py[cv]

注意camelot 依赖 OpenCV 和 Ghostscript,Windows 用户需手动安装 Ghostscript 并添加到 PATH。

2. 代码示例

import camelot

def pdf_table_to_csv_camelot(pdf_path, csv_path, flavor='lattice', pages='all'):
    """
    使用 camelot 提取表格并保存为 CSV。
    flavor: 'lattice'(有边框) 或 'stream'(无边框)
    """
    tables = camelot.read_pdf(pdf_path, flavor=flavor, pages=pages)
    if not tables:
        print("未找到表格")
        return

    for i, table in enumerate(tables):
        out_path = csv_path if i == 0 else csv_path.replace('.csv', f'_{i+1}.csv')
        table.to_csv(out_path)
        print(f"已保存表格 {i+1} -> {out_path}")

# 使用
pdf_table_to_csv_camelot("table.pdf", "output.csv", flavor="lattice")

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

tabula-py 是 Java 库 Tabula 的 Python 封装,功能强大,支持指定页面区域。

1. 安装与配置

pip install tabula-py

确保系统已安装 Java 8+ 并配置环境变量。

2. 代码示例

import tabula

def pdf_table_to_csv_tabula(pdf_path, csv_path, pages='all', area=None):
    """
    area: 列表 [top, left, bottom, right] 单位毫米(可选)
    """
    # 提取表格,返回 DataFrame 列表
    dfs = tabula.read_pdf(pdf_path, pages=pages, area=area, multiple_tables=True)
    if not dfs:
        print("未找到表格")
        return

    for i, df in enumerate(dfs):
        out_path = csv_path if i == 0 else csv_path.replace('.csv', f'_{i+1}.csv')
        df.to_csv(out_path, index=False)
        print(f"已保存表格 {i+1} -> {out_path}")

# 使用(自动检测区域)
pdf_table_to_csv_tabula("example.pdf", "output.csv", pages="all")

处理多页表格合并

若表格跨越多页,需要手动合并。以下是使用 pdfplumber 合并多页表格的示例:

def merge_multipage_tables(pdf_path, pages, table_settings=None):
    all_rows = []
    with pdfplumber.open(pdf_path) as pdf:
        for page_num in pages:
            page = pdf.pages[page_num-1]   # pages 是页码列表,如 [1,2,3]
            tables = page.extract_tables(table_settings)
            if tables:
                # 假设每页只有一个表格
                table = tables[0]
                if all_rows:
                    # 跳过表头(假设第一页已有表头),从第二行开始追加
                    all_rows.extend(table[1:])
                else:
                    all_rows.extend(table)
    return all_rows

完整示例:自动选择最佳策略

以下是一个智能函数,自动尝试多种策略提取表格:

import pdfplumber
def auto_extract_tables(pdf_path, pages='all'):
    """
    自动尝试不同策略提取表格,返回第一个非空结果。
    """
    strategies = [
        {"vertical_strategy": "lines", "horizontal_strategy": "lines"},
        {"vertical_strategy": "text", "horizontal_strategy": "text"},
        {"vertical_strategy": "lines", "horizontal_strategy": "text"},
        {"vertical_strategy": "text", "horizontal_strategy": "lines"},
    ]
    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            for settings in strategies:
                tables = page.extract_tables(settings)
                if tables and len(tables[0]) > 1:  # 至少有一行数据
                    return tables
    return None

七、总结

以上 Python 示例演示了如何轻松实现 PDF 表格提取并导出为 CSV 文件。通过逐页扫描、单元格清理和标准 CSV 写入,可构建稳定可靠的数据抽取流程。在实际集成中,开发者应当关注表格边框依赖、页面限制以及资源释放等关键因素,并根据文档特性调整文本清洗规则。该方法不依赖任何中间格式或额外解析库,代码结构清晰,适用于自动化数据处理管道。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章

1 2 3 4 5