You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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表单的方案:

核心问题分析

  1. 第一次尝试错误:生成FDF后未正确读取FDF数据填充PDF,反而使用了模板本身的空字段值,且未处理单选按钮的FDF格式。
  2. 第二次尝试错误:直接修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:30:55