Modulo Python per convertire PDF in testo [chiuso]
-
09-06-2019 - |
Domanda
Quali sono i migliori moduli Python per convertire file PDF in testo?
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