2010-10-21 8 views
25

Sto provando a utilizzare Python per elaborare alcuni moduli PDF che sono stati compilati e firmati utilizzando Adobe Acrobat Reader.Come estrarre i campi PDF da un modulo compilato in Python?

ho provato:

  • Il pdfminer demo: non ha discarica nessuno dei dati compilati.
  • pyPdf: ha raggiunto il valore massimo per 2 minuti quando ho provato a caricare il file con PdfFileReader (f) e ho rinunciato e l'ho eliminato.
  • Jython e PDFBox: ha funzionato benissimo ma il tempo di avvio è eccessivo, scriverò semplicemente un'utilità esterna in Java diretto se questa è la mia unica opzione.

Posso continuare a cercare le biblioteche e provarle, ma spero che qualcuno abbia già una soluzione efficiente per questo.


Aggiornamento: in base alla risposta di Steven ho guardato in pdfminer e lo ha fatto egregiamente.

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

Come nota, ho anche provato a utilizzare pdftk come utility esterna e non ha superato la password del proprietario. – Olson

risposta

25

Dovreste essere in grado di farlo con pdfminer, ma richiederà un po 'di approfondire i meccanismi interni di pdfminer e una certa conoscenza sul formato pdf (forme WRT, naturalmente, ma anche di strutture interne del pdf come "dizionari "e" oggetti indiretti ").

Questo esempio potrebbe aiutare nel vostro cammino (penso che funziona solo su casi semplici, senza campi nidificati, ecc ...)

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: dimenticato di dire: se è necessario fornire una password, passarla a doc.initialize()

+0

Questo ha fatto il trucco, grazie. Ho visto la demo del web e ho capito che potevo vedere se quello che volevo fosse lì dentro e se no potevo saltarlo. Risulta non solo può fare esattamente il modo che voglio, può anche gestire i campi firma che PdfBox non può. – Olson

+1

Ho un problema di codifica. L'uso di field.get ('V') non codifica caratteri speciali come 'ü' o 'ä' correttamente. Qualcuno ha una soluzione a questo? La conversione della stringa in unicode genera un errore di decodifica. – Basil

+2

Nella versione corrente di pdfminer è stato rimosso il metodo PDFDocument.initialize. Questo codice funziona se rimuovi questa linea. – joshua

3

Lavoro rapido e sporco di 2 minuti; basta usare PDFminer per convertire PDF in xml e quindi prendere tutti i campi.

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

Non è bello, solo una semplice dimostrazione di concetto. Devo implementarlo per un sistema su cui sto lavorando, quindi lo ripulirò, ma ho pensato di pubblicarlo nel caso qualcuno lo trovasse utile.

3

Aggiornamento per la versione più recente del pdf minatore (cambiamento importazione e messa a punto parser/doc in prima funzione)

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

Dove si inserisce il nome del file in modo che lo script possa essere eseguito? – user2067030

0

C'è un errore di battitura su queste linee:

file.write(pp.pformat(form)) 

dovrebbe essere:

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

Il pacchetto Python PyPDF2 (successore pyPdf) è molto conveniente:

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

Poi ff è un dict che contiene tutte le informazioni pertinenti modulo.

Problemi correlati