¿Hay algún módulo de Python para convertir archivos PDF en texto? Probé una pieza de código que se encuentra en Activestate que utiliza pypdf pero el texto generado tenido ningún espacio entre y era de ninguna utilidad.
¿Hay algún módulo de Python para convertir archivos PDF en texto? Probé una pieza de código que se encuentra en Activestate que utiliza pypdf pero el texto generado tenido ningún espacio entre y era de ninguna utilidad.
Respuestas:
Prueba PDFMiner . Puede extraer texto de archivos PDF en formato HTML, SGML o "PDF etiquetado".
El formato PDF etiquetado parece ser el más limpio, y quitar las etiquetas XML deja solo el texto desnudo.
Una versión de Python 3 está disponible en:
El paquete PDFMiner ha cambiado desde que se publicó codeape .
EDITAR (nuevamente):
PDFMiner ha sido actualizado nuevamente en la versión 20100213
Puede verificar la versión que ha instalado con lo siguiente:
>>> import pdfminer
>>> pdfminer.__version__
'20100213'
Aquí está la versión actualizada (con comentarios sobre lo que cambié / agregué):
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 (una vez más):
Aquí es una actualización para la versión más reciente en PyPI , 20100619p1
. En resumen He sustituido LTTextItem
con LTChar
y pasé una instancia de LAParams al constructor 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 (una vez más):
Actualizado para la versión 20110515
(¡gracias 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()
LTTextItem
a LTChar
. unixuser.org/~euske/python/pdfminer/index.html#changes
20110515
según su comentario.
Como ninguna de estas soluciones admite la última versión de PDFMiner, escribí una solución simple que devolverá el texto de un PDF usando PDFMiner. Esto funcionará para aquellos que reciben errores de importación conprocess_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])
Vea a continuación el 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
, además de los paréntesis obvios después del print
comando, uno tiene que reemplazar el file
comando open
e importarlo StringIO
desde el paqueteio
Pdftotext Un programa de código abierto (parte de Xpdf) al que puede llamar desde python (no es lo que solicitó pero podría ser útil). Lo he usado sin problemas. Creo que Google lo usa en Google Desktop.
-layout
opción de mantener el texto en la misma posición que está en el PDF. Ahora, si solo pudiera descubrir cómo canalizar el contenido de un PDF.
pdftotext
parece funcionar muy bien, pero necesita un segundo argumento que sea un guión, si desea ver los resultados en stdout.
find . -iname "*.pdf" -exec pdftotext -enc UTF-8 -eol unix -raw {} \;
por defecto, los archivos generados toman el nombre original con la .txt
extensión.
pyPDF funciona bien (suponiendo que esté trabajando con archivos PDF bien formados). Si todo lo que desea es el texto (con espacios), puede hacer lo siguiente:
import pyPdf
pdf = pyPdf.PdfFileReader(open(filename, "rb"))
for page in pdf.pages:
print page.extractText()
También puede acceder fácilmente a los metadatos, datos de imágenes, etc.
Un comentario en las notas del código extractText:
Localice todos los comandos de dibujo de texto, en el orden en que se proporcionan en la secuencia de contenido, y extraiga el texto. Esto funciona bien para algunos archivos PDF, pero deficiente para otros, dependiendo del generador utilizado. Esto será refinado en el futuro. No confíe en el orden del texto que sale de esta función, ya que cambiará si esta función se hace más sofisticada.
Si esto es un problema o no depende de lo que esté haciendo con el texto (por ejemplo, si el orden no importa, está bien, o si el generador agrega texto a la secuencia en el orden en que se mostrará, está bien) . Tengo código de extracción pyPdf en uso diario, sin ningún problema.
También puede usar pdfminer con bastante facilidad como biblioteca. Tiene acceso al modelo de contenido del pdf y puede crear su propia extracción de texto. Hice esto para convertir contenido PDF a texto separado por punto y coma, usando el código a continuación.
La función simplemente ordena los objetos de contenido TextItem de acuerdo con sus coordenadas y y x, y genera elementos con la misma coordenada y como una línea de texto, separando los objetos en la misma línea con ';' caracteres.
Utilizando este enfoque, pude extraer texto de un pdf del que ninguna otra herramienta pudo extraer contenido adecuado para un análisis posterior. Otras herramientas que probé incluyen pdftotext, ps2ascii y la herramienta en línea pdftextonline.com.
pdfminer es una herramienta invaluable para el raspado 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()
ACTUALIZACIÓN :
El código anterior está escrito en una versión anterior de la API, vea mi comentario a continuación.
pdfminer
, no pdflib
). Sugiero que eche un vistazo a la fuente pdf2txt.py
en la fuente PDFminer, el código anterior se inspiró en la versión anterior de ese archivo.
slate
es un proyecto que simplifica el uso de PDFMiner desde una biblioteca:
>>> with open('example.pdf') as f:
... doc = slate.PDF(f)
...
>>> doc
[..., ..., ...]
>>> doc[1]
'Text from page 2...'
Necesitaba convertir un PDF específico a texto plano dentro de un módulo de Python. Utilicé PDFMiner 20110515, después de leer su herramienta pdf2txt.py escribí este fragmento simple:
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 el código pdf2txt.py que viene con pdfminer; puede hacer una función que tome un camino hacia el pdf; opcionalmente, un tipo externo (txt | html | xml | tag) y opta por la línea de comandos pdf2txt {'-o': '/path/to/outfile.txt' ...}. Por defecto, puede llamar a:
convert_pdf(path)
Se creará un archivo de texto, un hermano en el sistema de archivos para el 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
PDFminer me dio quizás una línea [página 1 de 7 ...] en cada página de un archivo pdf que probé con él.
La mejor respuesta que tengo hasta ahora es pdftoipe, o el código c ++ está basado en Xpdf.
vea mi pregunta sobre cómo se ve la salida de pdftoipe.
Además, hay PDFTextStream, que es una biblioteca comercial de Java que también se puede usar desde Python.
He utilizado pdftohtml
con el -xml
argumento, lea el resultado con subprocess.Popen()
, que le dará x coord, y coord, ancho, alto y fuente, de cada fragmento de texto en el pdf. Creo que esto es lo que 'evince' probablemente también usa porque se escuchan los mismos mensajes de error.
Si necesita procesar datos en columnas, se vuelve un poco más complicado ya que tiene que inventar un algoritmo que se adapte a su archivo pdf. El problema es que los programas que hacen archivos PDF realmente no necesariamente presentan el texto en ningún formato lógico. Puede probar algoritmos de clasificación simples y funciona a veces, pero puede haber pequeños 'rezagados' y 'extraviados', fragmentos de texto que no se colocan en el orden que pensaba. Entonces tienes que ser creativo.
Me llevó alrededor de 5 horas descubrir uno para los archivos PDF en los que estaba trabajando. Pero funciona bastante bien ahora. Buena suerte.
Encontré esa solución hoy. Funciona muy bien para mi. Incluso renderizando páginas PDF a imágenes PNG. http://www.swftools.org/gfx_tutorial.html