PDF表单(AcroForm)在办公自动化中常被用作合同模板或报表模板。通过Python可以批量填写这些表单字段,也可以从已填写的表单中提取数据。本文使用pdfplumber和PyPDF2两个库,给出可复用的批量处理代码。
## 环境准备
安装依赖:
- pip install pdfplumber PyPDF2 pandas openpyxl
复制代码
## 案例一:批量填写PDF表单
### 1. 查看表单字段结构
在填写前,需要知道模板中有哪些字段。使用pdfplumber可以遍历注释(annotations)获取字段名:
- import pdfplumber
- def inspect_form_fields(pdf_path):
- """查看PDF表单字段名称与类型"""
- with pdfplumber.open(pdf_path) as pdf:
- for i, page in enumerate(pdf.pages):
- annotations = page.annots
- if annotations:
- print(f"第 {i+1} 页表单字段:")
- for ann in annotations:
- print(f" - {ann.get('name', '未命名')}: 类型={ann.get('subtype', '')}")
复制代码
运行后输出字段名列表,后续填写时需要使用完全一致的字段名称。
### 2. 单份填写函数
使用PyPDF2的PdfWriter更新表单字段值:
- from PyPDF2 import PdfReader, PdfWriter
- def fill_pdf_form(template_path, output_path, data_dict):
- """
- 填写单个PDF表单
- :param template_path: 模板PDF路径
- :param output_path: 输出PDF路径
- :param data_dict: 字段名→值的字典
- """
- reader = PdfReader(template_path)
- writer = PdfWriter()
- writer.add_page(reader.pages[0])
- writer.update_page_form_field_values(writer.pages[0], data_dict)
- with open(output_path, "wb") as f:
- writer.write(f)
复制代码
### 3. 批量生成合同
假设有一份Excel数据源,包含“客户名称”、“金额”、“签订日期”、“合同编号”等列,每行对应一份合同。读取Excel并循环生成:
- import pandas as pd
- import os
- def batch_fill_forms(template_path, excel_path, output_dir):
- os.makedirs(output_dir, exist_ok=True)
- df = pd.read_excel(excel_path)
- for _, row in df.iterrows():
- data = {
- "name": row["客户名称"],
- "amount": str(row["金额"]),
- "date": str(row["签订日期"]),
- "contract_no": row["合同编号"]
- }
- output = os.path.join(output_dir, f"合同_{row['客户名称']}.pdf")
- fill_pdf_form(template_path, output, data)
- print(f"已生成: {output}")
复制代码
## 案例二:提取PDF表单数据
### 1. 提取已填写的表单字段
使用PyPDF2的get_form_text_fields()可以获取所有可填写字段的当前值:
- def extract_form_data(pdf_path):
- reader = PdfReader(pdf_path)
- fields = reader.get_form_text_fields()
- print("表单字段提取结果:")
- for field_name, value in fields.items():
- print(f" {field_name}: {value}")
- return fields
复制代码
批量处理多个PDF并将结果汇总为Excel:
- def batch_extract(pdf_dir, output_excel):
- all_data = []
- for f in os.listdir(pdf_dir):
- if f.endswith(".pdf"):
- fields = extract_form_data(os.path.join(pdf_dir, f))
- fields["来源文件"] = f
- all_data.append(fields)
- if all_data:
- df = pd.DataFrame(all_data)
- df.to_excel(output_excel, index=False)
- print(f"共提取 {len(all_data)} 份表单: {output_excel}")
复制代码
### 2. 提取不可填写表格(文字型PDF)
对于不含AcroForm的PDF(例如文字型报表),可以使用pdfplumber提取表格数据:
- def extract_table_from_scanned(pdf_path):
- with pdfplumber.open(pdf_path) as pdf:
- all_tables = []
- for i, page in enumerate(pdf.pages):
- tables = page.extract_tables()
- for table in tables:
- if table and len(table) > 1:
- df = pd.DataFrame(table[1:], columns=table[0])
- all_tables.append(df)
- print(f"第{i+1}页找到表格: {len(table)}行")
- if all_tables:
- return pd.concat(all_tables, ignore_index=True)
- return None
复制代码
### 3. 完整处理流水线
将上述步骤组合成一个每日自动执行的流程:
- def daily_pdf_pipeline():
- print("开始PDF处理...")
- form_data = extract_form_data("日报表.pdf")
- print(f"提取到 {len(form_data)} 个字段")
- table_df = extract_table_from_scanned("明细.pdf")
- if table_df is not None:
- table_df.to_excel("提取数据.xlsx", index=False)
- print(f"导出 {len(table_df)} 行数据")
- # 后续可加入汇总报告生成逻辑
- print("处理完成!")
复制代码
## 注意事项
1. PDF表单必须为AcroForm格式,非扫描件。如果模板是扫描图片,需先OCR识别。
2. 批量处理前务必用单个文件测试,确认字段名与Excel列名对应无误。
3. 中文字体需要嵌入到PDF中,否则填写后显示为方框。
4. PyPDF2的update_page_form_field_values对非ASCII字段名可能有限制,建议字段名使用英文。
通过以上代码,可以快速搭建批量PDF表单处理工具。在实际项目中,可根据需求扩充异常处理和日志记录。 |