Domanda

Quali sono i migliori moduli Python per convertire file PDF in testo?

È stato utile?

Soluzione

Tentativo PDFMiner.Può estrarre testo da file PDF in formato HTML, SGML o "PDF con tag".

Il formato PDF con tag sembra essere il più pulito e rimuovendo i tag XML lascia solo il testo nudo.

Una versione Python 3 è disponibile in:

Altri suggerimenti

IL PDFMiner il pacchetto è cambiato da allora codeape pubblicato.

EDIT (di nuovo):

PDFMiner è stato nuovamente aggiornato nella versione 20100213

Puoi verificare la versione che hai installato con quanto segue:

>>> import pdfminer
>>> pdfminer.__version__
'20100213'

Ecco la versione aggiornata (con commenti su cosa ho cambiato/aggiunto):

def pdf_to_csv(filename):
    from cStringIO import StringIO  #<-- added so you can copy/paste this to try it
    from pdfminer.converter import LTTextItem, TextConverter
    from pdfminer.pdfparser import PDFDocument, PDFParser
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

    class CsvConverter(TextConverter):
        def __init__(self, *args, **kwargs):
            TextConverter.__init__(self, *args, **kwargs)

        def end_page(self, i):
            from collections import defaultdict
            lines = defaultdict(lambda : {})
            for child in self.cur_item.objs:
                if isinstance(child, LTTextItem):
                    (_,_,x,y) = child.bbox                   #<-- changed
                    line = lines[int(-y)]
                    line[x] = child.text.encode(self.codec)  #<-- changed

            for y in sorted(lines.keys()):
                line = lines[y]
                self.outfp.write(";".join(line[x] for x in sorted(line.keys())))
                self.outfp.write("\n")

    # ... the following part of the code is a remix of the 
    # convert() function in the pdfminer/tools/pdf2text module
    rsrc = PDFResourceManager()
    outfp = StringIO()
    device = CsvConverter(rsrc, outfp, codec="utf-8")  #<-- changed 
        # becuase my test documents are utf-8 (note: utf-8 is the default codec)

    doc = PDFDocument()
    fp = open(filename, 'rb')
    parser = PDFParser(fp)       #<-- changed
    parser.set_document(doc)     #<-- added
    doc.set_parser(parser)       #<-- added
    doc.initialize('')

    interpreter = PDFPageInterpreter(rsrc, device)

    for i, page in enumerate(doc.get_pages()):
        outfp.write("START PAGE %d\n" % i)
        interpreter.process_page(page)
        outfp.write("END PAGE %d\n" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

Modifica (ancora una volta):

Ecco un aggiornamento per l'ultima versione in pipi, 20100619p1.In breve ho sostituito LTTextItem con LTChar e ha passato un'istanza di LAParams al costruttore CsvConverter.

def pdf_to_csv(filename):
    from cStringIO import StringIO  
    from pdfminer.converter import LTChar, TextConverter    #<-- changed
    from pdfminer.layout import LAParams
    from pdfminer.pdfparser import PDFDocument, PDFParser
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

    class CsvConverter(TextConverter):
        def __init__(self, *args, **kwargs):
            TextConverter.__init__(self, *args, **kwargs)

        def end_page(self, i):
            from collections import defaultdict
            lines = defaultdict(lambda : {})
            for child in self.cur_item.objs:
                if isinstance(child, LTChar):               #<-- changed
                    (_,_,x,y) = child.bbox                   
                    line = lines[int(-y)]
                    line[x] = child.text.encode(self.codec)

            for y in sorted(lines.keys()):
                line = lines[y]
                self.outfp.write(";".join(line[x] for x in sorted(line.keys())))
                self.outfp.write("\n")

    # ... the following part of the code is a remix of the 
    # convert() function in the pdfminer/tools/pdf2text module
    rsrc = PDFResourceManager()
    outfp = StringIO()
    device = CsvConverter(rsrc, outfp, codec="utf-8", laparams=LAParams())  #<-- changed
        # becuase my test documents are utf-8 (note: utf-8 is the default codec)

    doc = PDFDocument()
    fp = open(filename, 'rb')
    parser = PDFParser(fp)       
    parser.set_document(doc)     
    doc.set_parser(parser)       
    doc.initialize('')

    interpreter = PDFPageInterpreter(rsrc, device)

    for i, page in enumerate(doc.get_pages()):
        outfp.write("START PAGE %d\n" % i)
        if page is not None:
            interpreter.process_page(page)
        outfp.write("END PAGE %d\n" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

EDIT (ancora una volta):

Aggiornato per la versione 20110515 (grazie a Oeufcoque Penteano!):

def pdf_to_csv(filename):
    from cStringIO import StringIO  
    from pdfminer.converter import LTChar, TextConverter
    from pdfminer.layout import LAParams
    from pdfminer.pdfparser import PDFDocument, PDFParser
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

    class CsvConverter(TextConverter):
        def __init__(self, *args, **kwargs):
            TextConverter.__init__(self, *args, **kwargs)

        def end_page(self, i):
            from collections import defaultdict
            lines = defaultdict(lambda : {})
            for child in self.cur_item._objs:                #<-- changed
                if isinstance(child, LTChar):
                    (_,_,x,y) = child.bbox                   
                    line = lines[int(-y)]
                    line[x] = child._text.encode(self.codec) #<-- changed

            for y in sorted(lines.keys()):
                line = lines[y]
                self.outfp.write(";".join(line[x] for x in sorted(line.keys())))
                self.outfp.write("\n")

    # ... the following part of the code is a remix of the 
    # convert() function in the pdfminer/tools/pdf2text module
    rsrc = PDFResourceManager()
    outfp = StringIO()
    device = CsvConverter(rsrc, outfp, codec="utf-8", laparams=LAParams())
        # becuase my test documents are utf-8 (note: utf-8 is the default codec)

    doc = PDFDocument()
    fp = open(filename, 'rb')
    parser = PDFParser(fp)       
    parser.set_document(doc)     
    doc.set_parser(parser)       
    doc.initialize('')

    interpreter = PDFPageInterpreter(rsrc, device)

    for i, page in enumerate(doc.get_pages()):
        outfp.write("START PAGE %d\n" % i)
        if page is not None:
            interpreter.process_page(page)
        outfp.write("END PAGE %d\n" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

Poiché nessuna di queste soluzioni supporta l'ultima versione di PDFMiner, ho scritto una soluzione semplice che restituirà il testo di un PDF utilizzando PDFMiner.Funzionerà per coloro che riscontrano errori di importazione process_pdf

import sys
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import XMLConverter, HTMLConverter, TextConverter
from pdfminer.layout import LAParams
from cStringIO import StringIO

def pdfparser(data):

    fp = file(data, 'rb')
    rsrcmgr = PDFResourceManager()
    retstr = StringIO()
    codec = 'utf-8'
    laparams = LAParams()
    device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)
    # Create a PDF interpreter object.
    interpreter = PDFPageInterpreter(rsrcmgr, device)
    # Process each page contained in the document.

    for page in PDFPage.get_pages(fp):
        interpreter.process_page(page)
        data =  retstr.getvalue()

    print data

if __name__ == '__main__':
    pdfparser(sys.argv[1])  

Vedi sotto il codice che funziona per Python 3:

import sys
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import XMLConverter, HTMLConverter, TextConverter
from pdfminer.layout import LAParams
import io

def pdfparser(data):

    fp = open(data, 'rb')
    rsrcmgr = PDFResourceManager()
    retstr = io.StringIO()
    codec = 'utf-8'
    laparams = LAParams()
    device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)
    # Create a PDF interpreter object.
    interpreter = PDFPageInterpreter(rsrcmgr, device)
    # Process each page contained in the document.

    for page in PDFPage.get_pages(fp):
        interpreter.process_page(page)
        data =  retstr.getvalue()

    print(data)

if __name__ == '__main__':
    pdfparser(sys.argv[1])  

Pdftesto Un programma open source (parte di Xpdf) che potresti chiamare da Python (non quello che hai chiesto ma potrebbe essere utile).L'ho usato senza problemi.Penso che Google lo usi in Google Desktop.

pyPDF funziona bene (supponendo che tu stia lavorando con PDF ben formati).Se tutto ciò che desideri è il testo (con spazi), puoi semplicemente fare:

import pyPdf
pdf = pyPdf.PdfFileReader(open(filename, "rb"))
for page in pdf.pages:
    print page.extractText()

Puoi anche accedere facilmente ai metadati, ai dati delle immagini e così via.

Un commento nel codice extractText rileva:

Individua tutti i comandi di disegno di testo, nell'ordine in cui sono forniti nel flusso di contenuto ed estrarre il testo.Funziona bene per alcuni file PDF, ma male per altri, a seconda del generatore utilizzato.Questo sarà raffinato in futuro.Non fare affidamento sull'ordine del testo che esce da questa funzione, poiché cambierà se questa funzione viene resa più sofisticata.

Se questo sia o meno un problema dipende da cosa stai facendo con il testo (ad es.se l'ordine non ha importanza, va bene, o se il generatore aggiunge testo allo stream nell'ordine in cui verrà visualizzato, va bene).Ho il codice di estrazione pyPdf nell'uso quotidiano, senza problemi.

Puoi anche utilizzare abbastanza facilmente pdfminer come libreria.Hai accesso al modello di contenuto del PDF e puoi creare la tua estrazione di testo.L'ho fatto per convertire i contenuti PDF in testo separato da punto e virgola, utilizzando il codice seguente.

La funzione ordina semplicemente gli oggetti del contenuto di Textitem in base alle loro coordinate Y e X e produce elementi con la stessa coordinata Y di una riga di testo, separando gli oggetti sulla stessa linea con ';'; caratteri.

Utilizzando questo approccio, sono stato in grado di estrarre testo da un PDF da cui nessun altro strumento era in grado di estrarre contenuti adatti per un'ulteriore analisi.Altri strumenti che ho provato includono pdftotext, ps2ascii e lo strumento online pdftextonline.com.

pdfminer è uno strumento prezioso per lo scraping dei pdf.


def pdf_to_csv(filename):
    from pdflib.page import TextItem, TextConverter
    from pdflib.pdfparser import PDFDocument, PDFParser
    from pdflib.pdfinterp import PDFResourceManager, PDFPageInterpreter

    class CsvConverter(TextConverter):
        def __init__(self, *args, **kwargs):
            TextConverter.__init__(self, *args, **kwargs)

        def end_page(self, i):
            from collections import defaultdict
            lines = defaultdict(lambda : {})
            for child in self.cur_item.objs:
                if isinstance(child, TextItem):
                    (_,_,x,y) = child.bbox
                    line = lines[int(-y)]
                    line[x] = child.text

            for y in sorted(lines.keys()):
                line = lines[y]
                self.outfp.write(";".join(line[x] for x in sorted(line.keys())))
                self.outfp.write("\n")

    # ... the following part of the code is a remix of the 
    # convert() function in the pdfminer/tools/pdf2text module
    rsrc = PDFResourceManager()
    outfp = StringIO()
    device = CsvConverter(rsrc, outfp, "ascii")

    doc = PDFDocument()
    fp = open(filename, 'rb')
    parser = PDFParser(doc, fp)
    doc.initialize('')

    interpreter = PDFPageInterpreter(rsrc, device)

    for i, page in enumerate(doc.get_pages()):
        outfp.write("START PAGE %d\n" % i)
        interpreter.process_page(page)
        outfp.write("END PAGE %d\n" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

AGGIORNAMENTO:

Il codice sopra è scritto su una vecchia versione dell'API, vedi il mio commento qui sotto.

slate è un progetto che rende molto semplice l'utilizzo di PDFMiner da una libreria:

>>> with open('example.pdf') as f:
...    doc = slate.PDF(f)
...
>>> doc
[..., ..., ...]
>>> doc[1]
'Text from page 2...'   

Avevo bisogno di convertire un PDF specifico in testo semplice all'interno di un modulo Python.ero solito PDFMiner 20110515, dopo aver letto il loro pdf2txt.py tool ho scritto questo semplice snippet:

from cStringIO import StringIO
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams

def to_txt(pdf_path):
    input_ = file(pdf_path, 'rb')
    output = StringIO()

    manager = PDFResourceManager()
    converter = TextConverter(manager, output, laparams=LAParams())
    process_pdf(manager, converter, input_)

    return output.getvalue() 

Riproporre il codice pdf2txt.py fornito con pdfminer;puoi creare una funzione che prenderà un percorso verso il pdf;facoltativamente, un outtype (txt|html|xml|tag) e opta come la riga di comando pdf2txt {'-o':'/percorso/file/outfile.txt' ...}.Per impostazione predefinita, puoi chiamare:

convert_pdf(path)

Verrà creato un file di testo, fratello nel filesystem del pdf originale.

def convert_pdf(path, outtype='txt', opts={}):
    import sys
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter, process_pdf
    from pdfminer.converter import XMLConverter, HTMLConverter, TextConverter, TagExtractor
    from pdfminer.layout import LAParams
    from pdfminer.pdfparser import PDFDocument, PDFParser
    from pdfminer.pdfdevice import PDFDevice
    from pdfminer.cmapdb import CMapDB

    outfile = path[:-3] + outtype
    outdir = '/'.join(path.split('/')[:-1])

    debug = 0
    # input option
    password = ''
    pagenos = set()
    maxpages = 0
    # output option
    codec = 'utf-8'
    pageno = 1
    scale = 1
    showpageno = True
    laparams = LAParams()
    for (k, v) in opts:
        if k == '-d': debug += 1
        elif k == '-p': pagenos.update( int(x)-1 for x in v.split(',') )
        elif k == '-m': maxpages = int(v)
        elif k == '-P': password = v
        elif k == '-o': outfile = v
        elif k == '-n': laparams = None
        elif k == '-A': laparams.all_texts = True
        elif k == '-D': laparams.writing_mode = v
        elif k == '-M': laparams.char_margin = float(v)
        elif k == '-L': laparams.line_margin = float(v)
        elif k == '-W': laparams.word_margin = float(v)
        elif k == '-O': outdir = v
        elif k == '-t': outtype = v
        elif k == '-c': codec = v
        elif k == '-s': scale = float(v)
    #
    CMapDB.debug = debug
    PDFResourceManager.debug = debug
    PDFDocument.debug = debug
    PDFParser.debug = debug
    PDFPageInterpreter.debug = debug
    PDFDevice.debug = debug
    #
    rsrcmgr = PDFResourceManager()
    if not outtype:
        outtype = 'txt'
        if outfile:
            if outfile.endswith('.htm') or outfile.endswith('.html'):
                outtype = 'html'
            elif outfile.endswith('.xml'):
                outtype = 'xml'
            elif outfile.endswith('.tag'):
                outtype = 'tag'
    if outfile:
        outfp = file(outfile, 'w')
    else:
        outfp = sys.stdout
    if outtype == 'txt':
        device = TextConverter(rsrcmgr, outfp, codec=codec, laparams=laparams)
    elif outtype == 'xml':
        device = XMLConverter(rsrcmgr, outfp, codec=codec, laparams=laparams, outdir=outdir)
    elif outtype == 'html':
        device = HTMLConverter(rsrcmgr, outfp, codec=codec, scale=scale, laparams=laparams, outdir=outdir)
    elif outtype == 'tag':
        device = TagExtractor(rsrcmgr, outfp, codec=codec)
    else:
        return usage()

    fp = file(path, 'rb')
    process_pdf(rsrcmgr, device, fp, pagenos, maxpages=maxpages, password=password)
    fp.close()
    device.close()

    outfp.close()
    return

PDFminer mi ha dato forse una riga [pagina 1 di 7...] su ogni pagina di un file pdf con cui ho provato.

La migliore risposta che ho finora è pdftoipe, ovvero il codice C++ basato su Xpdf.

Vedere la mia domanda per sapere come appare l'output di pdftoipe.

Inoltre c'è PDFTextStream che è una libreria Java commerciale che può essere utilizzata anche da Python.

ho usato pdftohtml con il -xml argomento, leggi il risultato con subprocess.Popen(), che ti darà x coord, y coord, larghezza, altezza e carattere di ogni frammento di testo nel pdf.Penso che questo sia ciò che probabilmente usa anche "evince" perché vengono emessi gli stessi messaggi di errore.

Se devi elaborare dati colonnari, diventa leggermente più complicato poiché devi inventare un algoritmo adatto al tuo file pdf.Il problema è che i programmi che creano file PDF non necessariamente dispongono il testo in un formato logico.Puoi provare semplici algoritmi di ordinamento e a volte funziona, ma possono esserci piccoli "ritardi" e "vaganti", pezzi di testo che non vengono inseriti nell'ordine in cui pensavi.Quindi devi essere creativo.

Mi ci sono volute circa 5 ore per trovarne uno per i PDF su cui stavo lavorando.Ma funziona piuttosto bene adesso.Buona fortuna.

Ho trovato quella soluzione oggi.Funziona benissimo per me.Anche il rendering di pagine PDF in immagini PNG.http://www.swftools.org/gfx_tutorial.html

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top