2010-10-21 27 views
25

Estoy tratando de usar Python para procesar algunos formularios PDF que se completaron y firmaron utilizando Adobe Acrobat Reader.¿Cómo extraer campos PDF de un formulario completo en Python?

que he probado:

  • La demo pdfminer: no volcar cualquiera de los datos rellenados.
  • pyPdf: maxed un núcleo durante 2 minutos cuando traté de cargar el archivo con PdfFileReader (f) y me rendí y lo maté.
  • Jython y PDFBox: funciona bien, pero el tiempo de inicio es excesivo, solo escribiré una utilidad externa en Java directo si esa es mi única opción.

Puedo seguir buscando bibliotecas y probarlas, pero espero que alguien ya tenga una solución eficiente para esto.


Actualización: Sobre la base de la respuesta de Steven Miré en pdfminer y se hizo el truco muy bien.

from argparse import ArgumentParser 
import pickle 
import pprint 
from pdfminer.pdfparser import PDFParser, 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.set_document(doc) 
     doc.set_parser(parser) 
     doc.initialize() 
     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() 
+0

Como nota, también intenté usar pdftk como una utilidad externa y no pasó la contraseña del propietario. – Olson

Respuesta

25

Usted debe ser capaz de hacerlo con pdfminer, pero va a requerir alguna de ahondar en los detalles internos de pdfminer y un cierto conocimiento sobre el formato pdf (formas wrt por supuesto, pero también de las estructuras internas del pdf como "diccionarios "y" objetos indirectos ").

Este ejemplo podría ayudarle en su camino (creo que sólo funcionará en casos sencillos, sin campos anidados etc ...)

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) 

EDIT: se olvidó de mencionar: si usted necesita para proporcionar una contraseña, páselo a doc.initialize()

+0

Eso hizo el truco, gracias. Vi la demostración web y pensé que podía ver si lo que quería estaba allí y si no podía saltearlo. Resulta que no solo puede hacer exactamente lo que yo quiero, sino que incluso puede manejar los campos de firma que PdfBox no puede. – Olson

+1

Tengo un problema de codificación. Usar field.get ('V') no codifica caracteres especiales como 'ü' o 'ä' correctamente. ¿Alguien tiene una solución para esto? La conversión de la cadena a unicode genera un error de decodificación. – Basil

+2

En la versión actual de pdfminer, se ha eliminado el método PDFDocument.initialize. Este código funciona si solo eliminas esa línea. – joshua

3

Trabajo rápido y sucio de 2 minutos; solo use PDFminer para convertir PDF a xml y luego tome todos los campos.

from xml.etree import ElementTree 
from pprint import pprint 
import os 

def main(): 
    print "Calling PDFDUMP.py" 
    os.system("dumppdf.py -a FILE.pdf > out.xml") 

    # Preprocess the file to eliminate bad XML. 
    print "Screening the file" 
    o = open("output.xml","w") #open for append 
    for line in open("out.xml"): 
     line = line.replace("&#", "Invalid_XML") #some bad data in xml for formatting info. 
     o.write(line) 
    o.close() 

    print "Opening XML output" 
    tree = ElementTree.parse('output.xml') 
    lastnode = "" 
    lastnode2 = "" 
    list = {} 
    entry = {} 

    for node in tree.iter(): # Run through the tree..   
     # Check if New node 
     if node.tag == "key" and node.text == "T": 
      lastnode = node.tag + node.text 
     elif lastnode == "keyT": 
      for child in node.iter(): 
       entry["ID"] = child.text 
      lastnode = "" 

     if node.tag == "key" and node.text == "V": 
      lastnode2 = node.tag + node.text 
     elif lastnode2 == "keyV": 
      for child in node.iter(): 
       if child.tag == "string": 
        if entry.has_key("ID"): 
         entry["Value"] = child.text 
         list[entry["ID"]] = entry["Value"] 
         entry = {} 
      lastnode2 = "" 

    pprint(list) 

if __name__ == '__main__': 
    main() 

No es bonito, solo una simple prueba de concepto. Necesito implementarlo para un sistema en el que estoy trabajando, así que lo limpiaré, pero pensé que lo publicaría en caso de que alguien lo encuentre útil.

3

actualización para la última versión de la minera pdf (modificar importación y configuración del analizador/doc en la primera función)

from argparse import ArgumentParser 
import pickle 
import pprint 
from pdfminer.pdfparser import PDFParser 
from pdfminer.pdfdocument import PDFDocument 
from pdfminer.pdftypes import resolve1 
from pdfminer.pdftypes import 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) 
     parser.set_document(doc) 
     #doc.set_parser(parser) 
     doc.initialize() 
     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-8'), 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() 
+0

¿Dónde pones el nombre del archivo para que se pueda ejecutar la secuencia de comandos? – user2067030

0

Hay un error tipográfico en estas líneas:

file.write(pp.pformat(form)) 

debe ser:

outfile.write(pp.pformat(form)) 
3

Python 3.6+:

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)) 
0

El pitón PyPDF2 paquete (sucesor pyPdf) es muy conveniente:

import PyPDF2 
f = PyPDF2.PdfFileReader('form.pdf') 
ff = f.getFields() 

Entonces ff es una dict que contiene toda la información del formulario correspondiente.

Cuestiones relacionadas