使用Python在PDF文档中自动创建与填写表单

 更新时间:2026年08月31日 08:24:22   作者:大丸子  
本文将教你使用Python自动完成表单创建、批量填写、字段提取和扁平化,实现电子合同全流程自动化,特别适合供应商协议、人事合同等场景,文中的示例代码讲解详细,有需要的小伙伴可以了解下

在企业数字化进程中,电子合同、申请表、报销单、问卷等场景大量依赖 PDF 表单(AcroForm)进行数据采集。传统做法是先用 Adobe Acrobat 或在线编辑器手工绘制表单字段,再由业务人员逐份打开、逐字段录入,面对批量合同时不仅耗时,还容易出现字段填错、漏填、格式不一致等问题。当表单需要对接审批系统、CRM 或归档数据库时,纯人工方式更难以满足合规与效率要求。

将表单的"创建—填写—提取—固化"全流程交给代码自动完成,可以让一份合同模板在数秒内生成数十份差异化合同,并在签署后自动回收关键字段进入业务系统。这种方式特别适用于人力资源合同、采购订单、供应商协议、用户调研问卷等高频、强结构化的场景。

本文将以一份"供应商合作协议"为线索,演示如何使用 Python 完成 PDF 表单的创建、批量自动填写、字段值提取以及表单固化(Flatten)等关键操作,构建一条可复用的电子合同自动化流水线。

本文使用的方法需要用到 Free Spire.PDF for Python,可通过 pip 安装:

pip install spire.pdf.free

1. 创建 PDF 电子合同表单模板

电子合同的第一步是生成带有可填写字段的 PDF 模板。我们先用代码在空白页面上绘制合同标题、说明文字,并添加文本框字段与列表框字段,搭建出一份可分发的空白合同模板。

from spire.pdf.common import *
from spire.pdf import *

outputFile = "ContractTemplate.pdf"

# 创建 PDF 文档
doc = PdfDocument()
# 允许在已有文档上创建表单(新建文档时也建议显式设置)
doc.AllowCreateForm = True

# 添加一个页面
page = doc.Pages.Add()

# 创建字体与画刷
titleFont = PdfTrueTypeFont("微软雅黑", 18.0, PdfFontStyle.Bold, True)
labelFont = PdfTrueTypeFont("微软雅黑", 11.0, PdfFontStyle.Regular, True)
fieldFont = PdfTrueTypeFont("宋体", 11.0, PdfFontStyle.Regular, True)
brush = PdfBrushes.get_Black()
hintBrush = PdfBrushes.get_DodgerBlue()

# 绘制合同标题
titleFormat = PdfStringFormat(PdfTextAlignment.Center)
page.Canvas.DrawString("供应商合作协议", titleFont, brush,
                        page.Canvas.ClientSize.Width / float(2), 40.0, titleFormat)

# 绘制说明文字
page.Canvas.DrawString("请填写以下字段后签署,带 * 字段为必填项。", labelFont, hintBrush, 50.0, 80.0)

# 字段起始坐标
x = 50.0
y = 120.0
labelGap = 15.0
fieldWidth = 200.0
fieldHeight = 18.0

# 1) 添加"供应商名称"文本框字段
label1 = "供应商名称 *:"
page.Canvas.DrawString(label1, labelFont, brush, x, y)
textX = x + labelFont.MeasureString(label1).Width + labelGap
textBoxName = PdfTextBoxField(page, "vendor_name")
textBoxName.Bounds = RectangleF(textX, y, fieldWidth, fieldHeight)
textBoxName.BorderWidth = 0.75
textBoxName.BorderStyle = PdfBorderStyle.Solid
textBoxName.Font = fieldFont
doc.Form.Fields.Add(textBoxName)

# 2) 添加"统一社会信用代码"文本框字段
y = y + 40.0
label2 = "信用代码 *:"
page.Canvas.DrawString(label2, labelFont, brush, x, y)
textX = x + labelFont.MeasureString(label2).Width + labelGap
textBoxCode = PdfTextBoxField(page, "credit_code")
textBoxCode.Bounds = RectangleF(textX, y, fieldWidth, fieldHeight)
textBoxCode.BorderWidth = 0.75
textBoxCode.BorderStyle = PdfBorderStyle.Solid
textBoxCode.Font = fieldFont
doc.Form.Fields.Add(textBoxCode)

# 3) 添加"合同金额(元)"文本框字段
y = y + 40.0
label3 = "合同金额 *:"
page.Canvas.DrawString(label3, labelFont, brush, x, y)
textX = x + labelFont.MeasureString(label3).Width + labelGap
textBoxAmount = PdfTextBoxField(page, "amount")
textBoxAmount.Bounds = RectangleF(textX, y, fieldWidth, fieldHeight)
textBoxAmount.BorderWidth = 0.75
textBoxAmount.BorderStyle = PdfBorderStyle.Solid
textBoxAmount.Font = fieldFont
doc.Form.Fields.Add(textBoxAmount)

# 4) 添加"签署日期"文本框字段
y = y + 40.0
label4 = "签署日期 *:"
page.Canvas.DrawString(label4, labelFont, brush, x, y)
textX = x + labelFont.MeasureString(label4).Width + labelGap
textBoxDate = PdfTextBoxField(page, "sign_date")
textBoxDate.Bounds = RectangleF(textX, y, fieldWidth, fieldHeight)
textBoxDate.BorderWidth = 0.75
textBoxDate.BorderStyle = PdfBorderStyle.Solid
textBoxDate.Font = fieldFont
doc.Form.Fields.Add(textBoxDate)

# 5) 添加"付款方式"列表框字段
y = y + 40.0
label5 = "付款方式 *:"
page.Canvas.DrawString(label5, labelFont, brush, x, y)
textX = x + labelFont.MeasureString(label5).Width + labelGap
listBoxPayment = PdfListBoxField(page, "payment_method")
listBoxPayment.Items.Add(PdfListFieldItem("一次性付清", "pay_once"))
listBoxPayment.Items.Add(PdfListFieldItem("分期付款(按月)", "pay_monthly"))
listBoxPayment.Items.Add(PdfListFieldItem("分期付款(按季)", "pay_quarterly"))
listBoxPayment.Items.Add(PdfListFieldItem("验收后付款", "pay_after_accept"))
listBoxPayment.Bounds = RectangleF(textX, y, fieldWidth, 60.0)
listBoxPayment.Font = fieldFont
listBoxPayment.SelectedIndex = 0
doc.Form.Fields.Add(listBoxPayment)

# 保存模板
doc.SaveToFile(outputFile, FileFormat.PDF)
doc.Close()

说明:

  • PdfDocument() 创建一份新的 PDF 文档,AllowCreateForm = True 显式开启表单创建能力。
  • PdfTextBoxField(page, "name") 在指定页面上生成一个文本框字段,字段名是后续填写与提取的索引依据。
  • PdfListBoxField(page, "name") 生成列表框字段,通过 Items.Add(PdfListFieldItem(...)) 逐项添加可选项,SelectedIndex 设置默认选中项。
  • Bounds = RectangleF(x, y, w, h) 精确控制字段在页面上的位置与尺寸,配合 MeasureString 计算标签宽度,保证标签与字段对齐。
  • 所有字段最终通过 doc.Form.Fields.Add(...) 注册到文档表单集合中。

此步骤的作用是生成一份可分发的空白合同模板,为后续批量自动填写奠定基础。

创建PDF表单模板

2. 自动填写电子合同表单

模板生成后,实际业务中往往需要根据数据库或 Excel 中的供应商信息批量填写合同。下面演示如何加载模板,遍历表单字段并按业务数据自动填充。

from spire.pdf.common import *
from spire.pdf import *

inputFile = "ContractTemplate.pdf"
outputFile = "FilledContract.pdf"

# 加载合同模板
doc = PdfDocument()
doc.LoadFromFile(inputFile)

# 获取表单并转换为 FormWidget
pdfform = doc.Form
formWidget = PdfFormWidget(pdfform)

# 业务数据:来自数据库或 Excel 的供应商信息
vendorData = {
    "vendor_name": "成都**科技有限公司",
    "credit_code": "91510100MA6XXXXXX9X",
    "amount": "128000.00",
    "sign_date": "2026-08-28"
}

# 遍历所有字段并按名称填充
if formWidget.FieldsWidget.Count > 0:
    for i in range(formWidget.FieldsWidget.Count):
        field = formWidget.FieldsWidget.get_Item(i)

        # 处理文本框字段
        if isinstance(field, PdfTextBoxFieldWidget):
            textBoxField = field if isinstance(field, PdfTextBoxFieldWidget) else None
            if textBoxField.Name in vendorData:
                textBoxField.Text = vendorData[textBoxField.Name]

        # 处理列表框字段
        if isinstance(field, PdfListBoxWidgetFieldWidget):
            listBoxField = field if isinstance(field, PdfListBoxWidgetFieldWidget) else None
            if listBoxField.Name == "payment_method":
                # 选择"分期付款(按月)"
                listBoxField.SelectedIndex = [1]

# 保存填写后的合同
doc.SaveToFile(outputFile)
doc.Close()

说明:

  • PdfFormWidget(pdfform) 将表单对象转换为可操作的 FormWidgetFieldsWidget 即字段集合。
  • get_Item(i) 按索引取出字段,再通过 isinstance 判断字段类型,避免类型转换异常。
  • PdfTextBoxFieldWidget 通过 Text 属性写入文本;PdfListBoxWidgetFieldWidget 通过 SelectedIndex(列表)设置选中项。
  • 业务数据用字典维护,字段名作为 key,与模板中字段名一一对应,便于从数据库或 Excel 批量灌入。

此步骤实现了"模板 + 数据 = 合同"的自动化填充,是批量出合同的核心环节。

自动填写表单结果

批量生成多份合同

将上述填充逻辑封装为函数,即可根据供应商列表批量生成合同:

from spire.pdf.common import *
from spire.pdf import *

def fillContract(templatePath, outputPath, vendorData, paymentIndex):
    doc = PdfDocument()
    doc.LoadFromFile(templatePath)
    formWidget = PdfFormWidget(doc.Form)

    if formWidget.FieldsWidget.Count > 0:
        for i in range(formWidget.FieldsWidget.Count):
            field = formWidget.FieldsWidget.get_Item(i)
            if isinstance(field, PdfTextBoxFieldWidget):
                textBoxField = field if isinstance(field, PdfTextBoxFieldWidget) else None
                if textBoxField.Name in vendorData:
                    textBoxField.Text = vendorData[textBoxField.Name]
            if isinstance(field, PdfListBoxWidgetFieldWidget):
                listBoxField = field if isinstance(field, PdfListBoxWidgetFieldWidget) else None
                if listBoxField.Name == "payment_method":
                    listBoxField.SelectedIndex = [paymentIndex]
    doc.SaveToFile(outputPath)
    doc.Close()

# 供应商列表(实际来自数据库查询)
vendors = [
    {"vendor_name": "成都云启科技有限公司", "credit_code": "91510100MA6XXXXXX9X",
     "amount": "128000.00", "sign_date": "2026-08-28"},
    {"vendor_name": "深圳前海数据服务有限公司", "credit_code": "91440300MA5XXXXXX3X",
     "amount": "86500.00", "sign_date": "2026-08-28"},
    {"vendor_name": "杭州数澜科技股份有限公司", "credit_code": "91330106MA2XXXXXX1X",
     "amount": "210000.00", "sign_date": "2026-08-28"},
]

# 批量生成
for idx, vendor in enumerate(vendors):
    fillContract("ContractTemplate.pdf", f"Contracts/Contract_{idx + 1}.pdf", vendor, 0)

说明:

  • 将单份填写逻辑封装为 fillContract 函数,模板路径、输出路径、业务数据、付款方式索引均参数化。
  • 外层循环遍历供应商列表,依次生成 Contract_1.pdfContract_2.pdf……
  • 实际项目中,vendors 可直接来自数据库查询结果或 Excel 读取,实现真正的"数据驱动出合同"。

3. 提取表单字段数据用于归档与校验

合同签署回收后,业务系统通常需要把表单中的关键字段读出来归档、入库或与原始订单校验。下面演示如何遍历所有字段并提取其值。

from spire.pdf.common import *
from spire.pdf import *

inputFile = "FilledContract.pdf"
outputFile = "ExtractedFormData.txt"

# 加载已填写的合同
doc = PdfDocument()
doc.LoadFromFile(inputFile)
formWidget = PdfFormWidget(doc.Form)

result = []

if formWidget.FieldsWidget.Count > 0:
    for i in range(formWidget.FieldsWidget.Count):
        field = formWidget.FieldsWidget.get_Item(i)

        # 提取文本框字段值
        if isinstance(field, PdfTextBoxFieldWidget):
            textBoxField = field if isinstance(field, PdfTextBoxFieldWidget) else None
            result.append(f"字段名:{textBoxField.Name},值:{textBoxField.Text}")

        # 提取列表框字段值
        if isinstance(field, PdfListBoxWidgetFieldWidget):
            listBoxField = field if isinstance(field, PdfListBoxWidgetFieldWidget) else None
            selectedValue = listBoxField.SelectedValue
            result.append(f"字段名:{listBoxField.Name},选中值:{selectedValue}")

# 保存提取结果
with open(outputFile, "w", encoding="UTF-8") as f:
    for line in result:
        f.write(line + "\n")

doc.Close()

说明:

  • PdfTextBoxFieldWidget.Text 直接返回文本框当前值。
  • PdfListBoxWidgetFieldWidget.SelectedValue 返回列表框当前选中项的值。
  • 提取结果写入文本文件,实际项目中可改为写入数据库或 JSON,供审批系统消费。
  • 这一步是"签署回收 → 数据回流"的关键,让 PDF 表单不再是信息孤岛。

4. 表单字段管理:必填、修改、扁平化与删除

在合同流程的不同阶段,往往需要对表单字段做进一步管理:起草时标记必填、审批时修改个别字段、签署完成后扁平化防止篡改、归档前删除临时字段。

4.1 设置必填字段与修改字段值

from spire.pdf.common import *
from spire.pdf import *

inputFile = "ContractTemplate.pdf"
outputFile = "ManagedContract.pdf"

doc = PdfDocument()
doc.LoadFromFile(inputFile)
formWidget = PdfFormWidget(doc.Form)

if formWidget.FieldsWidget.Count > 0:
    for i in range(formWidget.FieldsWidget.Count):
        field = formWidget.FieldsWidget.get_Item(i)

        if isinstance(field, PdfTextBoxFieldWidget):
            textBoxField = field if isinstance(field, PdfTextBoxFieldWidget) else None
            # 将关键字段标记为必填
            if textBoxField.Name in ["vendor_name", "credit_code", "amount", "sign_date"]:
                textBoxField.Required = True
            # 为字段设置统一字体,提升填写后的视觉效果
            textBoxField.Font = PdfTrueTypeFont("宋体", 11.0, PdfFontStyle.Regular, True)

# 保存
doc.SaveToFile(outputFile)
doc.Close()

说明:

  • Required = True 将字段标记为必填,PDF 阅读器会以高亮边框提示填写人。
  • Font = PdfTrueTypeFont(...) 为字段统一设置字体,避免不同填写端字体差异导致排版错乱。

4.2 扁平化表单防止二次修改

合同签署完成后,通常需要将表单字段"烧录"成静态内容,防止后续被篡改:

from spire.pdf.common import *
from spire.pdf import *

inputFile = "FilledContract.pdf"
outputFile = "FlattenedContract.pdf"

doc = PdfDocument()
doc.LoadFromFile(inputFile)

# 扁平化所有表单字段:字段值转为普通文本,不再可编辑
doc.Form.IsFlatten = True

doc.SaveToFile(outputFile)
doc.Close()

说明:

  • doc.Form.IsFlatten = True 一行即可将整份文档的所有表单字段扁平化。
  • 扁平化后字段值变为页面上的普通文本,任何 PDF 阅读器都无法再修改,适合作为最终归档版本。
  • 这是电子合同"签署即固化"的关键操作,常与数字签名配合使用。

4.3 删除指定字段

在模板复用场景中,有时需要从已有表单中移除某些不再使用的字段:

from spire.pdf.common import *
from spire.pdf import *

inputFile = "ContractTemplate.pdf"
outputFile = "RemovedFieldContract.pdf"

doc = PdfDocument()
doc.LoadFromFile(inputFile)
formWidget = PdfFormWidget(doc.Form)

if formWidget.FieldsWidget.Count > 0:
    i = 0
    while i < formWidget.FieldsWidget.Count:
        field = formWidget.FieldsWidget.get_Item(i)
        if isinstance(field, PdfTextBoxFieldWidget):
            textBoxField = field if isinstance(field, PdfTextBoxFieldWidget) else None
            # 删除"签署日期"字段
            if textBoxField.Name == "sign_date":
                formWidget.FieldsWidget.Remove(textBoxField)
                continue
        i = i + 1

doc.SaveToFile(outputFile)
doc.Close()

说明:

  • formWidget.FieldsWidget.Remove(field) 从表单中移除指定字段。
  • 删除操作会改变字段集合长度,因此使用 while 循环配合索引手动递增,删除时 continue 跳过索引递增,避免越界。
  • 此操作适用于模板迭代、字段清理等场景。

关键类与方法说明表

为便于查阅,下面整理本文中主要使用到的类、属性与方法。

类 / 方法说明
PdfDocumentPDF 文档对象,负责创建、加载与保存文档
PdfDocument.AllowCreateForm是否允许在文档上创建表单字段
PdfDocument.Form获取文档的表单对象
PdfFormWidget表单操作对象,提供 FieldsWidget 字段集合
FieldsWidget.get_Item(i)按索引获取字段
FieldsWidget.Remove(field)移除指定字段
PdfTextBoxField(page, name)创建文本框字段
PdfListBoxField(page, name)创建列表框字段
PdfListFieldItem(text, value)列表框/组合框的选项项
PdfTextBoxFieldWidget文本框字段操作对象,可读写 TextRequiredFont 等
PdfListBoxWidgetFieldWidget列表框字段操作对象,可读写 SelectedIndexSelectedValue
field.Bounds = RectangleF(x, y, w, h)设置字段位置与尺寸
field.BorderStyle / field.BorderWidth设置字段边框样式与宽度
PdfTrueTypeFont(name, size, style, true)创建 TrueType 字体,用于页面绘制与字段字体设置
PdfBrushes.get_Black()获取黑色画刷,用于绘制文本
page.Canvas.DrawString(...)在页面上绘制文本
doc.Form.IsFlatten = True扁平化所有表单字段,转为静态文本

总结

通过本文示例,你已经了解如何使用 Python 完成 PDF 电子合同表单的完整生命周期管理:从创建带字段的合同模板,到根据业务数据批量自动填写,再到签署后提取字段值回流业务系统,最后通过扁平化固化签署结果。整个过程高度自动化,特别适用于供应商协议、人事合同、采购订单等高频、强结构化场景。

相比手工在 Acrobat 中逐份填写,代码方式具有批量处理、字段校验、与业务系统无缝对接等优势。你可以在此基础上扩展更多能力,例如结合数字签名保障法律效力、对接数据库实现合同台账自动入库、用 OCR 识别纸质合同回填电子表单等,构建完整的电子合同管理闭环。

到此这篇关于使用Python在PDF文档中自动创建与填写表单的文章就介绍到这了,更多相关Python PDF创建与填写表单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python实现贪吃蛇小游戏

    python实现贪吃蛇小游戏

    这篇文章主要为大家详细介绍了python实现贪吃蛇小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • 浅谈django channels 路由误导

    浅谈django channels 路由误导

    这篇文章主要介绍了浅谈django channels 路由误导,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Python中os模块的简单使用及重命名操作

    Python中os模块的简单使用及重命名操作

    这篇文章主要给大家介绍了关于Python中os模块的简单使用及重命名操作的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • numpy.bincount用于复数权重的方法

    numpy.bincount用于复数权重的方法

    numpy.bincount是NumPy库中的一个函数,它用于计算整数数组中每个值的出现次数,numpy.bincount函数在统计整数数组中每个值的出现次数或权重和时非常有用,本文给大家介绍numpy.bincount如何用于复数权重,感兴趣的朋友跟随小编一起看看吧
    2023-11-11
  • pandas分别写入excel的不同sheet方法

    pandas分别写入excel的不同sheet方法

    今天小编就为大家分享一篇pandas分别写入excel的不同sheet方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-12-12
  • Python3实现个位数字和十位数字对调, 其乘积不变

    Python3实现个位数字和十位数字对调, 其乘积不变

    这篇文章主要介绍了Python3实现个位数字和十位数字对调, 其乘积不变,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-05-05
  • Python利用Matplotlib库实现绘制饼形图

    Python利用Matplotlib库实现绘制饼形图

    这篇文章主要为大家分享了基于python+matplotlib库的饼形图绘制,具体内容涉及一般的饼图、分裂饼图、以及环形图,感兴趣的小伙伴可以了解一下
    2022-04-04
  • 关于python实现requests接口测试的问题

    关于python实现requests接口测试的问题

    requests是一个很实用的Python HTTP客户端库,Requests是Python语言的第三方的库,专门用于发送HTTP请求,这篇文章主要介绍了python实现requests接口测试,需要的朋友可以参考下
    2021-10-10
  • python打开隐藏控制台方法详解

    python打开隐藏控制台方法详解

    这篇文章主要为大家介绍了python打开隐藏控制台方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Python利用socket实现多进程的端口扫描器

    Python利用socket实现多进程的端口扫描器

    作为开发人员经常需要查看服务的端口开启状态判断服务是否宕机。特别是部署的服务比较多的情况下,可能存在几个甚至几十个服务端口的占用。所以本文将利用socket实现多进程的端口扫描器,需要的可以参考一下
    2022-12-12

最新评论