Módulo Python para conversão de PDF em texto [fechado]
-
09-06-2019 - |
Pergunta
Quais são os melhores módulos Python para converter arquivos PDF em texto?
Solução
Tentar PDFMiner.Ele pode extrair texto de arquivos PDF nos formatos HTML, SGML ou "Tagged PDF".
O formato Tagged PDF parece ser o mais limpo, e remover as tags XML deixa apenas o texto simples.
Uma versão Python 3 está disponível em:
Outras dicas
O PDFMiner pacote mudou desde código postou.
EDITAR (novamente):
PDFMiner foi atualizado novamente na versão 20100213
Você pode verificar a versão instalada com o seguinte:
>>> import pdfminer
>>> pdfminer.__version__
'20100213'
Aqui está a versão atualizada (com comentários sobre o que alterei/adicionei):
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()
Editar (mais uma vez):
Aqui está uma atualização para a versão mais recente em pypi, 20100619p1
.Resumindo eu substituí LTTextItem
com LTChar
e passou uma instância de LAParams para o construtor 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()
EDITAR (mais uma vez):
Atualizado para versão 20110515
(graças ao 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()
Como nenhuma dessas soluções suporta a versão mais recente do PDFMiner, escrevi uma solução simples que retornará o texto de um PDF usando o PDFMiner.Isso funcionará para aqueles que estão recebendo erros de importação com 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])
Veja abaixo o código que funciona para 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])
PDFtotexto Um programa de código aberto (parte do Xpdf) que você pode chamar de python (não o que você pediu, mas pode ser útil).Eu usei sem problemas.Acho que o Google usa isso no Google Desktop.
pyPDF funciona bem (supondo que você esteja trabalhando com PDFs bem formados).Se tudo que você deseja é o texto (com espaços), basta fazer:
import pyPdf
pdf = pyPdf.PdfFileReader(open(filename, "rb"))
for page in pdf.pages:
print page.extractText()
Você também pode obter acesso facilmente aos metadados, dados de imagem e assim por diante.
Um comentário nas notas do código extractText:
Localize todos os comandos de desenho de texto, na ordem em que são fornecidos no fluxo de conteúdo e extraia o texto.Isso funciona bem para alguns arquivos PDF, mas mal para outros, dependendo do gerador usado.Isso será refinado no futuro.Não confie na ordem do texto que sai dessa função, pois mudará se essa função for tornada mais sofisticada.
Se isso é um problema ou não, depende do que você está fazendo com o texto (por exemplo,se a ordem não importa, tudo bem, ou se o gerador adicionar texto ao stream na ordem em que será exibido, tudo bem).Tenho código de extração pyPdf de uso diário, sem problemas.
Você também pode usar facilmente o pdfminer como uma biblioteca.Você tem acesso ao modelo de conteúdo do pdf e pode criar sua própria extração de texto.Fiz isso para converter o conteúdo do PDF em texto separado por ponto e vírgula, usando o código abaixo.
A função simplesmente classifica os objetos de conteúdo do TEXTItem de acordo com suas coordenadas y e x e produz itens com a mesma coordenada y que uma linha de texto, separando os objetos na mesma linha com ';' personagens.
Usando essa abordagem, consegui extrair texto de um PDF do qual nenhuma outra ferramenta foi capaz de extrair conteúdo adequado para análise posterior.Outras ferramentas que experimentei incluem pdftotext, ps2ascii e a ferramenta online pdftextonline.com.
pdfminer é uma ferramenta inestimável para extração de 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()
ATUALIZAR:
O código acima foi escrito em uma versão antiga da API, veja meu comentário abaixo.
slate
é um projeto que simplifica muito o uso do PDFMiner a partir de uma biblioteca:
>>> with open('example.pdf') as f:
... doc = slate.PDF(f)
...
>>> doc
[..., ..., ...]
>>> doc[1]
'Text from page 2...'
Eu precisava converter um PDF específico em texto simples dentro de um módulo python.eu usei PDFMiner 20110515, depois de ler seus pdf2txt.py ferramenta, escrevi este trecho simples:
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()
Reaproveitando o código pdf2txt.py que vem com o pdfminer;você pode criar uma função que levará ao caminho do pdf;opcionalmente, um outtype (txt|html|xml|tag) e opções como a linha de comando pdf2txt {'-o':'/caminho/para/outfile.txt' ...}.Por padrão, você pode ligar para:
convert_pdf(path)
Um arquivo de texto será criado, um irmão no sistema de arquivos do PDF original.
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
O PDFminer me deu talvez uma linha [página 1 de 7...] em cada página de um arquivo PDF que tentei com ele.
A melhor resposta que tenho até agora é o pdftoipe, ou o código c++ baseado no Xpdf.
ver minha pergunta para saber como é a saída do pdftoipe.
Além disso há PDFTextStream que é uma biblioteca Java comercial que também pode ser usada em Python.
Eu tenho usado pdftohtml
com o -xml
argumento, leia o resultado com subprocess.Popen()
, que lhe dará coordenada x, coordenada y, largura, altura e fonte, de cada trecho de texto no pdf.Acho que é isso que 'evince' provavelmente também usa porque as mesmas mensagens de erro são exibidas.
Se você precisar processar dados colunares, fica um pouco mais complicado, pois você precisa inventar um algoritmo adequado ao seu arquivo PDF.O problema é que os programas que criam arquivos PDF não apresentam necessariamente o texto em nenhum formato lógico.Você pode tentar algoritmos de classificação simples e às vezes funciona, mas pode haver pequenos 'retardatários' e 'perdidos', pedaços de texto que não são colocados na ordem que você esperava.Então você tem que ser criativo.
Levei cerca de 5 horas para descobrir um para o PDF em que estava trabalhando.Mas funciona muito bem agora.Boa sorte.
Encontrei essa solução hoje.Funciona muito bem para mim.Até mesmo renderizando páginas PDF em imagens PNG.http://www.swftools.org/gfx_tutorial.html