一、案例简介
有些表格活,真不是难,就是烦。
尤其是政务网站年报 PDF 这类文件,人工处理的时候基本就是重复受罪:先打开 PDF,放大缩小找字段,再盯着“网站名称、主办单位、信息发布总数”等内容一点点抄到 Excel 里。抄完还得检查有没有看串行、抄错数、漏掉列。
如果只是三五份文件还好,一旦 PDF 数量多起来,效率就会明显下降,而且人工录入错误也很难完全避免。
这个案例要解决的就是这个问题:批量读取省级政务网站年报 PDF,从文件名中自动拆出省份、行政区代码和年份,再从 PDF 表格里提取核心字段,最后统一汇总成一份 CSV 表格。
需要处理的文件名一般类似:
河北省-130000-2022.pdf
安徽省-340000-2024.pdf
代码会自动提取:
这个项目的特点是非常实用:不做复杂 OCR,也不调用大模型,只针对结构化 PDF 表格做稳定提取。 能提取的字段自动提取,提不到的字段保留为空,保证整个批处理流程不会因为个别文件卡住。
案例代码获取:
https://ppmandata.net/codeBase/2067079056599289857
或者扫码成为超级课程会员免费获取。
[图片]
二、代码思路
整个项目的处理流程可以概括为 4 步。
第一步,锁定输入目录。程序只扫描项目下的 PDFs 文件夹,避免误处理无关文件。
第二步,解析 PDF 文件名。由于文件名中已经包含省份、行政区代码和年份,所以可以用正则表达式直接拆出来,不需要人工录入。
第三步,读取 PDF 表格。代码使用 pdfplumber 提取 PDF 中的表格结构,然后根据字段名称定位目标内容。
第四步,汇总导出结果。每份 PDF 提取完成后,统一整理成 DataFrame,最后导出为 政务网站报表汇总.csv。
整体逻辑不是追求“识别所有 PDF”,而是围绕政务网站年报这一类固定场景做批量化处理。这样好处很明显:流程更轻、依赖更少、运行更稳定,也更适合日常数据整理工作。
三、核心代码展示
首先,从文件名中提取省份、行政区代码和年份。
import re
def parse_filename(filename):
# 文件名格式:河北省-130000-2022.pdf
match = re.match(r"^(.+?)-(\d+)-(\d+)\.pdf$", filename)
if match:
return match.group(1), match.group(2), match.group(3)
return filename, "", ""
这一步的作用是把每份 PDF 自带的元信息自动整理出来。人工最容易忽略的小字段,比如年份、行政区代码,交给程序处理会更稳。
接着,对 PDF 表格中的单元格进行清洗。因为 PDF 表格提取出来后,经常会有换行、多余空格或者数字逗号,所以需要先统一格式。
import re
def clean_cell(cell):
if cell is None:
return ""
return re.sub(r"\s+", " ", str(cell)).strip()
def extract_value_after_label(row_text_list, label):
for index, cell_value in enumerate(row_text_list):
if label not in cell_value:
continue
for candidate in row_text_list[index + 1:]:
normalized = candidate.replace(",", "")
if re.search(r"\d", normalized):
return normalized
return ""
这里的思路是:先找到字段标签,再从标签后面寻找数字内容。相比固定按某一列取值,这种方式对 PDF 表格轻微错位的容忍度更高。
然后,是 PDF 表格提取的核心部分。代码会先识别“网站名称”“主办单位”等直接字段,再进入“信息发布”区块,继续提取下面的子字段。
import pdfplumber
DIRECT_FIELDS = {
"网站名称": "网站名称",
"主办单位": "主办单位",
}
INFO_PUBLISH_FIELDS = {
"总数": "信息发布总数",
"概况类信息更新量": "概况类信息更新量",
"政务动态信息更新量": "政务动态信息更新量",
"信息公开目录信息更新量": "信息公开目录信息更新量",
}
def extract_from_pdfplumber(filepath):
result = {}
found_any_table = False
in_info_publish_section = False
with pdfplumber.open(filepath) as pdf:
for page in pdf.pages:
tables = page.extract_tables() or []
if tables:
found_any_table = True
for table in tables:
for row in table:
if not row or len(row) < 2:
continue
row_text_list = [clean_cell(cell) for cell in row if clean_cell(cell)]
if not row_text_list:
continue
cell0 = clean_cell(row[0])
cell1 = clean_cell(row[1])
for key, col_name in DIRECT_FIELDS.items():
if key in cell0 and cell1:
result[col_name] = cell1
break
if "信息发布" in cell0 and "单位" in cell0:
in_info_publish_section = True
total_value = extract_value_after_label(row_text_list, "总数")
if total_value:
result["信息发布总数"] = total_value
continue
if in_info_publish_section and cell0 and any(
keyword in cell0 for keyword in ["专栏", "解读", "办事", "互动", "安全"]
):
in_info_publish_section = False
continue
if not in_info_publish_section:
continue
for sub_key, col_name in INFO_PUBLISH_FIELDS.items():
if not any(sub_key in cell for cell in row_text_list):
continue
value = extract_value_after_label(row_text_list, sub_key)
if value:
result[col_name] = value
break
if not found_any_table:
return None
return result
这段代码的重点是“区块识别”。它不是简单地在整份 PDF 里搜索关键词,而是先判断是否进入“信息发布”区域,再提取这个区域下的字段。这样可以减少误匹配,避免把其他表格区域里的数字误当成目标结果。
最后,把单份 PDF 的结果补齐文件名信息,并统一导出 CSV。
import os
import glob
import pandas as pd
OUTPUT_COLUMNS = [
"省份", "行政区代码", "年份", "网站名称", "主办单位",
"信息发布总数", "概况类信息更新量", "政务动态信息更新量",
"信息公开目录信息更新量", "源文件"
]
def extract_single_pdf(filepath):
filename = os.path.basename(filepath)
province, code, year = parse_filename(filename)
result = extract_from_pdfplumber(filepath)
if result is None:
result = {}
result["省份"] = province
result["行政区代码"] = code
result["年份"] = year
result["源文件"] = filename
return result
def main():
pdf_files = sorted(glob.glob(os.path.join("PDFs", "*.pdf")))
all_records = [extract_single_pdf(filepath) for filepath in pdf_files]
df = pd.DataFrame(all_records)
for column in OUTPUT_COLUMNS:
if column not in df.columns:
df[column] = ""
df = df[OUTPUT_COLUMNS]
df.to_csv("政务网站报表汇总.csv", index=False, encoding="utf-8-sig")
这一步保证了最终结果结构固定。即使某些 PDF 没有成功提取出完整字段,也会保留省份、代码、年份和源文件名,方便后续人工复核或补充。
四、结果展示
代码运行后,会在项目目录下生成一份汇总表:
政务网站报表汇总.csv
输出结果示例如下:
从结果可以看到,原本需要人工逐份打开 PDF、复制粘贴、整理格式的工作,现在可以直接批量完成。最终表格已经整理成适合后续分析的结构化数据,可以继续用于描述统计、年度对比、省份对比,或者导入 Stata、R、Python 继续做进一步分析。
这个案例适合用在各类政务 PDF、年报表格、统计公报整理等场景。只要文件命名比较规范、PDF 内部存在可识别的表格结构,就可以在这个代码基础上扩展字段,改成自己的批量提取工具。