Existe algum módulo python para converter arquivos PDF em texto? Eu tentei um pedaço de código encontrado no Activestate que usa pypdf, mas o texto gerado não tinha espaço entre e era inútil.
Existe algum módulo python para converter arquivos PDF em texto? Eu tentei um pedaço de código encontrado no Activestate que usa pypdf, mas o texto gerado não tinha espaço entre e era inútil.
Respostas:
Experimente o PDFMiner . Ele pode extrair texto de arquivos PDF no formato HTML, SGML ou "PDF marcado".
O formato PDF marcado parece ser o mais limpo, e remover as tags XML deixa apenas o texto simples.
Uma versão do Python 3 está disponível em:
O pacote PDFMiner mudou desde a publicação do código .
EDIT (novamente):
O PDFMiner foi atualizado novamente na versão 20100213
Você pode verificar a versão que instalou com o seguinte:
>>> import pdfminer
>>> pdfminer.__version__
'20100213'
Aqui está a versão atualizada (com comentários sobre o que eu 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
. Em suma, eu substituído 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()
EDIT (mais uma vez):
Atualizado para a 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()
LTTextItem
para LTChar
. unixuser.org/~euske/python/pdfminer/index.html#changes
20110515
por seu comentário.
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 comprocess_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])
python3
, além dos parênteses óbvias após o print
comando, é preciso substituir o file
comando com open
e importação StringIO
do pacoteio
Pdftotext Um programa de código aberto (parte do Xpdf) que você pode chamar do python (não o que você pediu, mas pode ser útil). Eu usei sem problemas. Eu acho que o Google usá-lo no Google Desktop.
-layout
opção de manter o texto na mesma posição do PDF. Agora, se eu pudesse descobrir como canalizar o conteúdo de um PDF para ele.
pdftotext
parece funcionar muito bem, mas precisa de um segundo argumento que seja um hífen, se você quiser ver os resultados no stdout.
find . -iname "*.pdf" -exec pdftotext -enc UTF-8 -eol unix -raw {} \;
Por padrão, os arquivos gerados recebem o nome original com a .txt
extensão.
O pyPDF funciona bem (supondo que você esteja trabalhando com PDFs bem formados). Se tudo o 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 facilmente acesso aos metadados, dados de imagem e assim por diante.
Um comentário no código extractText observa:
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 é ruim para outros, dependendo do gerador usado. Isso será refinado no futuro. Não confie na ordem do texto que sai dessa função, pois ela mudará se essa função for mais sofisticada.
Se isso é ou não um problema, depende do que você está fazendo com o texto (por exemplo, se o pedido não importa, tudo bem, ou se o gerador adiciona texto ao fluxo na ordem em que será exibido, tudo bem) . Eu tenho código de extração pyPdf em uso diário, sem problemas.
Você também pode facilmente usar 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 em pdf em texto separado por ponto e vírgula, usando o código abaixo.
A função simplesmente classifica os objetos de conteúdo TextItem de acordo com suas coordenadas yex, e gera itens com a mesma coordenada y de 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 conseguiu extrair conteúdo adequado para análise adicional. Outras ferramentas que experimentei incluem pdftotext, ps2ascii e a ferramenta online pdftextonline.com.
O pdfminer é uma ferramenta inestimável para a raspagem 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()
ATUALIZAÇÃO :
O código acima está escrito em uma versão antiga da API, veja meu comentário abaixo.
pdfminer
, não pdflib
). Eu sugiro que você dê uma olhada na fonte do pdf2txt.py
PDFminer, o código acima foi inspirado na versão antiga desse arquivo.
slate
é um projeto que facilita o uso do PDFMiner 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 sem formatação dentro de um módulo python. Usei o PDFMiner 20110515, depois de ler a ferramenta pdf2txt.py , 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()
C:\Python27\Scripts\pdfminer\tools\pdf2txt.py
Reutilizando o código pdf2txt.py que acompanha o pdfminer; você pode criar uma função que seguirá o caminho para o pdf; opcionalmente, um tipo de saída (txt | html | xml | tag) e opta como a linha de comando pdf2txt {'-o': '/path/to/outfile.txt' ...}. Por padrão, você pode chamar:
convert_pdf(path)
Um arquivo de texto será criado, um irmão no sistema de arquivos para o 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 todas as páginas de um arquivo pdf que tentei com ele.
A melhor resposta que tenho até agora é pdftoipe, ou o código c ++ é baseado em Xpdf.
veja minha pergunta sobre como é a saída do pdftoipe.
Além disso, há o PDFTextStream, que é uma biblioteca Java comercial que também pode ser usada no Python.
Eu usei pdftohtml
com o -xml
argumento, li o resultado com subprocess.Popen()
, que fornecerá x coord, y coord, largura, altura e fonte, de cada trecho de texto no pdf. Eu acho que é isso que 'evince' provavelmente também usa porque as mesmas mensagens de erro são exibidas.
Se você precisar processar dados colunares, isso ficará um pouco mais complicado, pois você precisará inventar um algoritmo adequado ao seu arquivo pdf. O problema é que os programas que produzem arquivos PDF não apresentam necessariamente o texto em nenhum formato lógico. Você pode tentar algoritmos simples de classificação e, às vezes, funciona, mas pode haver pequenos 'stragglers' e 'strays', pedaços de texto que não são colocados na ordem que você imaginava. Então você tem que ser criativo.
Demorei cerca de 5 horas para descobrir um dos pdf em que eu estava trabalhando. Mas funciona muito bem agora. Boa sorte.
Encontrei essa solução hoje. Funciona muito bem para mim. Até renderizando páginas PDF em imagens PNG. http://www.swftools.org/gfx_tutorial.html