如何用Python解析CSV/FDF数据并填充PDF模板表单?
问题描述
需要将CRM导出的客户数据自动填充到PDF模板表单中,已完成CSV导出、CSV转FDF步骤,但生成的PDF表单字段为空;尝试直接解析CSV填充时,出现Error: key must be PdfObject错误。当前使用pypdf 4.1.0库,PDF模板包含文本字段和单选按钮组,求高效实现CSV数据填充PDF表单的方案。
第一次尝试代码
import csv import os from pypdf import PdfWriter, PdfReader # 读取CSV文件 with open('acrobat_import.csv', 'r') as file: reader = csv.DictReader(file) data = list(reader) # 生成FDF内容的函数 def create_fdf_content(row): fdf_content = """ %FDF-1.2 %���� 1 0 obj << /FDF << /F(template_form.pdf)/Fields[ << /T(courseType)/V/{courseType} >> << /T(gender)/V/{gender} >> << /T(dateofbirth)/V({dateofbirth}) >> << /T(preferredLanguage)/V({preferredLanguage}) >> << /T(LastName)/V({lastName}) >> << /T(firstName)/V({firstName}) >> << /T(middleName)/V({middleName}) >> << /T(addressStreet)/V({addressStreet}) >> << /T(addressCity)/V({addressCity}) >> << /T(addressState)/V({addressState}) >> << /T(addressCountry)/V({addressCountry}) >> << /T(addressPostalCode)/V({addressPostalCode}) >> << /T(phoneNumber)/V({phoneNumber}) >> << /T(emailAddress)/V({emailAddress}) >> << /T(studentSigDate)/V({studentSigDate}) >> << /T(courseStartDate)/V({courseStartDate}) >> << /T(courseEndDate)/V({courseEndDate}) >> << /T(classHours)/V({classHours}) >> << /T(numberOfStudents)/V({numberOfStudents}) >> << /T(courseState)/V({courseState}) >> << /T(courseCity)/V({courseCity}) >> << /T(courseLanguage)/V({courseLanguage}) >> << /T(instructorNumber)/V({instructorNumber}) >> << /T(instructorLastName)/V({instructorLastName}) >> << /T(instructorFirstName)/V({instructorFirstName}) >> << /T(instructorSigDate)/V({instructorSigDate}) >> << /T(courseFee)/V({courseFee}) >> << /T(examinerNumber)/V({examinerNumber}) >> << /T(examinerLastName)/V({examinerLastName}) >> << /T(examinerFirstName)/V({examinerFirstName}) >> << /T(examinerSigDate)/V({examinerSigDate}) >>] >> >> endobj trailer << /Root 1 0 R >> %%EOF """.format(**row) return fdf_content # 循环生成每个行对应的FDF文件 for row in data: if row['lastName'] and row['firstName']: fdf_filename = f"{row['lastName']}_{row['firstName']} - {row['courseType']}.fdf" with open(fdf_filename, 'w', encoding='utf-8') as file: file.write(create_fdf_content(row)) print(f"已生成FDF文件: {fdf_filename}") # 打开PDF模板 template_pdf = PdfReader("template_form.pdf", "rb") # 解析FDF文件的函数 def parse_fdf_file(fdf_filename): fields = [] with open(fdf_filename, 'r', encoding='utf-8') as fdf_file: lines = fdf_file.readlines() for line in lines: # 只处理带/V()的字段,未处理单选按钮的/V/格式 if line.startswith('<< /T(') and '/V(' in line: field_name = line.split('<< /T(')[1].split(')')[0] field_value = line.split('/V(')[1].split(')')[0] fields.append({'field_name': field_name, 'field_value': field_value}) return fields # 将FDF数据导入PDF模板 pdf_writer = PdfWriter() page = template_pdf.pages[0] fields = template_pdf.get_fields() pdf_writer.add_page(page) for field in fields: pdf_writer.update_page_form_field_values(0, field['field_name'], field['field_value']) # 保存生成的PDF pdf_filename = f"{row['lastName']}_{row['firstName']} - {row['courseType']}.pdf" with open(pdf_filename, "wb") as pdf_file: pdf_writer.write(pdf_file) print(f"已生成PDF文件: {pdf_filename}")
第二次尝试代码
import csv from pypdf import PdfReader, PdfWriter # 定义文件路径 CSV_FILE = "acrobat_import.csv" PDF_TEMPLATE = "pdf_import_template.pdf" def process_csv_pdf(csv_file, pdf_template): try: # 读取CSV数据为字典列表 with open(csv_file, 'r', newline='') as file: reader = csv.DictReader(file) csv_data = list(reader) print("CSV数据:", csv_data) except FileNotFoundError as e: print(f"错误: CSV文件'{csv_file}'未找到。") return except Exception as e: print(f"读取CSV文件出错: {e}") return # 处理每一行CSV数据 for row in csv_data: try: # 获取文件名所需字段 last_name = row.get('lastName', '') first_name = row.get('firstName', '') course_type = row.get('courseType', '') if not all([last_name, first_name, course_type]): print(f"警告: 因数据缺失跳过该行: {row}") continue # 生成输出文件名 output_filename = f"{last_name}_{first_name} - {course_type}.pdf" print(f"正在处理: {output_filename}") # 填充PDF模板 reader = PdfReader(pdf_template) writer = PdfWriter() page = reader.pages[0] # 获取表单字段 fields = reader.get_fields(0) if fields: # 创建字段名到字段对象的映射 field_map = {field.get("/T"): field for field in fields.values()} for field_name in row: if field_name in field_map: pdf_field = field_map[field_name] # 调试信息 print(type(pdf_field)) print(pdf_field) # 尝试直接修改字段值 pdf_field["/V"] = row[field_name] # 更新表单字段 writer.update_page_form_field_values(writer.add_page(page), pdf_field.get("/T"), row[field_name]) # 保存输出PDF with open(output_filename, 'wb') as output_file: writer.write(output_file) print(f"PDF已生成: {output_filename}") except Exception as e: print(f"处理该行出错: {row}, 错误信息: {e}") if __name__ == "__main__": process_csv_pdf(CSV_FILE, PDF_TEMPLATE)
解决方案
针对pypdf 4.1.0的正确用法,以下是高效实现CSV填充PDF表单的方案:
核心问题分析
- 第一次尝试错误:生成FDF后未正确读取FDF数据填充PDF,反而使用了模板本身的空字段值,且未处理单选按钮的FDF格式。
- 第二次尝试错误:直接修改PdfObject的
/V属性不符合pypdf的API规范,update_page_form_field_values不需要操作字段对象,直接传字段名和值的字典即可,且重复添加页面会导致异常。
正确实现代码
import csv from pypdf import PdfReader, PdfWriter CSV_FILE = "acrobat_import.csv" PDF_TEMPLATE = "pdf_import_template.pdf" def fill_pdf_form(template_path, data_dict, output_path): """填充单个PDF表单""" reader = PdfReader(template_path) writer = PdfWriter() # 确认PDF表单字段,用于排查字段名匹配问题 form_fields = reader.get_fields() print("PDF表单字段列表:", [field["/T"] for field in form_fields.values()]) # 准备填充数据:仅保留PDF中存在的字段,且去除值的首尾空格 fill_data = {} for field_name, value in data_dict.items(): if field_name in form_fields: fill_data[field_name] = value.strip() if value else "" # 添加页面并填充表单 page = reader.pages[0] writer.add_page(page) # 使用update_page_form_field_values直接传入字段名-值字典 writer.update_page_form_field_values(page, fill_data) # 保存填充后的PDF with open(output_path, "wb") as out_file: writer.write(out_file) def process_csv_to_pdf(): """批量处理CSV数据填充PDF""" try: with open(CSV_FILE, 'r', newline='', encoding='utf-8') as csv_file: reader = csv.DictReader(csv_file) for row_num, row in enumerate(reader, 1): # 提取关键字段用于命名 last_name = row.get('lastName', '').strip() first_name = row.get('firstName', '').strip() course_type = row.get('courseType', '').strip() # 跳过数据不完整的行 if not (last_name and first_name and course_type): print(f"第{row_num}行数据缺失关键字段,跳过") continue output_filename = f"{last_name}_{first_name} - {course_type}.pdf" print(f"正在生成: {output_filename}") # 填充并保存PDF fill_pdf_form(PDF_TEMPLATE, row, output_filename) print(f"{output_filename} 生成成功") except FileNotFoundError: print(f"错误:未找到文件 {CSV_FILE} 或 {PDF_TEMPLATE}") except Exception as e: print(f"处理过程中出错: {str(e)}") if __name__ == "__main__": process_csv_to_pdf()
关键注意事项
- 字段名匹配:确保CSV的列名与PDF表单的字段名完全一致(区分大小写),可通过
reader.get_fields()查看PDF的字段名。 - 单选按钮处理:CSV中的值必须与PDF单选按钮的导出值匹配(不是按钮显示的文字),可使用Adobe Acrobat打开模板,查看每个单选按钮的导出值设置。
- 格式一致性:日期、数字等字段需保证CSV中的格式符合PDF模板的预期,避免填充后显示异常。
- API规范:pypdf 4.x版本的
update_page_form_field_values方法接收Page对象和字段名-值字典,无需直接操作PdfObject,这是解决Error: key must be PdfObject的核心。
内容的提问来源于stack exchange,提问作者TripitakaBC
相关产品推荐
相关产品推荐

