如何在Python中提取填写的PDF表单字段?

56 投票
7 回答
69738 浏览
提问于 2025-04-16 05:50

我正在尝试用Python处理一些用Adobe Acrobat Reader填写和签名的PDF表单。

我试过:

  • 使用pdfminer的演示:结果没有提取到任何填写的数据。
  • 尝试pyPdf:当我用PdfFileReader(f)加载文件时,程序占用了一个核心处理器长达2分钟,我实在等不下去了,只好强制结束了它。
  • 使用Jython和PDFBox:这个可以正常工作,但启动时间太长。如果这就是我唯一的选择,我宁愿用纯Java写一个外部工具。

我可以继续寻找库并尝试,但我希望有人已经有了一个有效的解决方案。


更新:根据Steven的回答,我查看了pdfminer,结果效果很好。

from argparse import ArgumentParser
import pickle
import pprint
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdftypes import resolve1, PDFObjRef

def load_form(filename):
    """Load pdf form contents into a nested list of name/value tuples"""
    with open(filename, 'rb') as file:
        parser = PDFParser(file)
        doc = PDFDocument(parser)
        return [load_fields(resolve1(f)) for f in
                   resolve1(doc.catalog['AcroForm'])['Fields']]

def load_fields(field):
    """Recursively load form fields"""
    form = field.get('Kids', None)
    if form:
        return [load_fields(resolve1(f)) for f in form]
    else:
        # Some field types, like signatures, need extra resolving
        return (field.get('T').decode('utf-16'), resolve1(field.get('V')))

def parse_cli():
    """Load command line arguments"""
    parser = ArgumentParser(description='Dump the form contents of a PDF.')
    parser.add_argument('file', metavar='pdf_form',
                    help='PDF Form to dump the contents of')
    parser.add_argument('-o', '--out', help='Write output to file',
                      default=None, metavar='FILE')
    parser.add_argument('-p', '--pickle', action='store_true', default=False,
                      help='Format output for python consumption')
    return parser.parse_args()

def main():
    args = parse_cli()
    form = load_form(args.file)
    if args.out:
        with open(args.out, 'w') as outfile:
            if args.pickle:
                pickle.dump(form, outfile)
            else:
                pp = pprint.PrettyPrinter(indent=2)
                file.write(pp.pformat(form))
    else:
        if args.pickle:
            print(pickle.dumps(form))
        else:
            pp = pprint.PrettyPrinter(indent=2)
            pp.pprint(form)

if __name__ == '__main__':
    main()

7 个回答

15

Python的PyPDF2库(是pyPdf的升级版)非常好用:

import PyPDF2
f = PyPDF2.PdfReader('form.pdf')
ff = f.get_fields()

然后,ff就是一个dict(字典),里面包含了所有相关的表单信息。

19

如果你使用的是Python 3.6或者更高版本,首先你需要安装一个叫做PyPDF2的库。这个库可以帮助你处理PDF文件。

安装的方法很简单,只需要在命令行中输入:

pip install PyPDF2

这样就可以把这个库安装到你的电脑上了。

# -*- coding: utf-8 -*-

from collections import OrderedDict
from PyPDF2 import PdfFileWriter, PdfFileReader


def _getFields(obj, tree=None, retval=None, fileobj=None):
    """
    Extracts field data if this PDF contains interactive form fields.
    The *tree* and *retval* parameters are for recursive use.

    :param fileobj: A file object (usually a text file) to write
        a report to on all interactive form fields found.
    :return: A dictionary where each key is a field name, and each
        value is a :class:`Field<PyPDF2.generic.Field>` object. By
        default, the mapping name is used for keys.
    :rtype: dict, or ``None`` if form data could not be located.
    """
    fieldAttributes = {'/FT': 'Field Type', '/Parent': 'Parent', '/T': 'Field Name', '/TU': 'Alternate Field Name',
                       '/TM': 'Mapping Name', '/Ff': 'Field Flags', '/V': 'Value', '/DV': 'Default Value'}
    if retval is None:
        retval = OrderedDict()
        catalog = obj.trailer["/Root"]
        # get the AcroForm tree
        if "/AcroForm" in catalog:
            tree = catalog["/AcroForm"]
        else:
            return None
    if tree is None:
        return retval

    obj._checkKids(tree, retval, fileobj)
    for attr in fieldAttributes:
        if attr in tree:
            # Tree is a field
            obj._buildField(tree, retval, fileobj, fieldAttributes)
            break

    if "/Fields" in tree:
        fields = tree["/Fields"]
        for f in fields:
            field = f.getObject()
            obj._buildField(field, retval, fileobj, fieldAttributes)

    return retval


def get_form_fields(infile):
    infile = PdfFileReader(open(infile, 'rb'))
    fields = _getFields(infile)
    return OrderedDict((k, v.get('/V', '')) for k, v in fields.items())



if __name__ == '__main__':
    from pprint import pprint

    pdf_file_name = 'FormExample.pdf'

    pprint(get_form_fields(pdf_file_name))
51

你可以使用 pdfminer 来实现这个功能,不过你需要对pdfminer的内部工作原理有一些了解,还要知道pdf格式的一些知识(当然是关于表单的,但也包括pdf内部结构,比如“字典”和“间接对象”等)。

这个例子可能会对你有所帮助(我觉得它只适用于简单的情况,比如没有嵌套字段之类的……)

import sys
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdftypes import resolve1

filename = sys.argv[1]
fp = open(filename, 'rb')

parser = PDFParser(fp)
doc = PDFDocument(parser)
fields = resolve1(doc.catalog['AcroForm'])['Fields']
for i in fields:
    field = resolve1(i)
    name, value = field.get('T'), field.get('V')
    print '{0}: {1}'.format(name, value)

编辑:我忘了提,如果你需要提供密码,可以把它传递给 doc.initialize()

撰写回答