使用Python在PDF文档中自动创建与填写表单
在企业数字化进程中,电子合同、申请表、报销单、问卷等场景大量依赖 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(...)注册到文档表单集合中。
此步骤的作用是生成一份可分发的空白合同模板,为后续批量自动填写奠定基础。

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)将表单对象转换为可操作的FormWidget,FieldsWidget即字段集合。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.pdf、Contract_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跳过索引递增,避免越界。 - 此操作适用于模板迭代、字段清理等场景。
关键类与方法说明表
为便于查阅,下面整理本文中主要使用到的类、属性与方法。
| 类 / 方法 | 说明 |
|---|---|
PdfDocument | PDF 文档对象,负责创建、加载与保存文档 |
PdfDocument.AllowCreateForm | 是否允许在文档上创建表单字段 |
PdfDocument.Form | 获取文档的表单对象 |
PdfFormWidget | 表单操作对象,提供 FieldsWidget 字段集合 |
FieldsWidget.get_Item(i) | 按索引获取字段 |
FieldsWidget.Remove(field) | 移除指定字段 |
PdfTextBoxField(page, name) | 创建文本框字段 |
PdfListBoxField(page, name) | 创建列表框字段 |
PdfListFieldItem(text, value) | 列表框/组合框的选项项 |
PdfTextBoxFieldWidget | 文本框字段操作对象,可读写 Text、Required、Font 等 |
PdfListBoxWidgetFieldWidget | 列表框字段操作对象,可读写 SelectedIndex、SelectedValue |
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创建与填写表单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论