Модуль Python для преобразования PDF в текст
Какие модули Python являются лучшими для преобразования файлов PDF в текст?
13 ответов
Попробуйте PDFMiner. Он может извлекать текст из файлов PDF в формате HTML, SGML или "Tagged PDF".
http://www.unixuser.org/~euske/python/pdfminer/index.html
Формат PDF с тегами кажется наиболее чистым, а удаление тегов XML оставляет только пустой текст.
Версия Python 3 доступна в разделе:
Пакет PDFMiner изменился с момента codeape.
РЕДАКТИРОВАТЬ (снова):
PDFMiner был снова обновлен в версии 20100213
Вы можете проверить установленную версию со следующим:
>>> import pdfminer
>>> pdfminer.__version__
'20100213'
Вот обновленная версия (с комментариями о том, что я изменил / добавил):
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()
Изменить (еще раз):
Вот обновление для последней версии в Pypi, 20100619p1
, Короче я заменил LTTextItem
с LTChar
и передал экземпляр LAParams конструктору 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()
РЕДАКТИРОВАТЬ (еще раз):
Обновлено для версии 20110515
(спасибо 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()
Поскольку ни одно из этих решений не поддерживает последнюю версию PDFMiner, я написал простое решение, которое будет возвращать текст PDF с использованием PDFMiner. Это будет работать для тех, кто получает ошибки импорта с 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])
Ниже приведен код, который работает для 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])
Pdftotext Программа с открытым исходным кодом (часть Xpdf), которую вы можете вызывать из python (не то, что вы просили, но может быть полезно). Я использовал это без проблем. Я думаю, что Google использует его в Google Desktop.
pyPDF работает нормально (при условии, что вы работаете с правильно сформированными PDF-файлами). Если вам нужен только текст (с пробелами), вы можете просто сделать:
import pyPdf
pdf = pyPdf.PdfFileReader(open(filename, "rb"))
for page in pdf.pages:
print page.extractText()
Вы также можете легко получить доступ к метаданным, данным изображения и так далее.
Комментарий в примечаниях к коду extractText:
Найдите все команды рисования текста в порядке их предоставления в потоке контента и извлеките текст. Это работает хорошо для некоторых файлов PDF, но плохо для других, в зависимости от используемого генератора. Это будет уточнено в будущем. Не полагайтесь на порядок текста, выходящего из этой функции, так как он изменится, если эта функция будет сделана более сложной.
Является ли это проблемой, зависит от того, что вы делаете с текстом (например, если порядок не имеет значения, это нормально, или если генератор добавляет текст в поток в порядке, в котором он будет отображаться, это нормально), У меня есть код извлечения pyPdf в повседневном использовании, без каких-либо проблем.
Вы также можете довольно легко использовать pdfminer в качестве библиотеки. У вас есть доступ к модели содержимого PDF, и вы можете создать свой собственный текст извлечения. Я сделал это, чтобы преобразовать содержимое PDF в текст, разделенный точкой с запятой, используя код ниже.
Функция просто сортирует объекты содержимого TextItem в соответствии с их координатами y и x и выводит элементы с той же координатой y, что и в одной текстовой строке, разделяя объекты на одной строке с помощью ';' персонажи.
Используя этот подход, я смог извлечь текст из PDF-файла, из которого другой инструмент не смог извлечь контент, пригодный для дальнейшего анализа. Другие инструменты, которые я пробовал, включают pdftotext, ps2ascii и онлайн-инструмент pdftextonline.com.
pdfminer - бесценный инструмент для 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()
ОБНОВЛЕНИЕ:
Код выше написан на старой версии API, см. Мой комментарий ниже.
slate
это проект, который позволяет очень просто использовать PDFMiner из библиотеки:
>>> with open('example.pdf') as f:
... doc = slate.PDF(f)
...
>>> doc
[..., ..., ...]
>>> doc[1]
'Text from page 2...'
Мне нужно было конвертировать конкретный PDF в обычный текст в модуле Python. Я использовал PDFMiner 20110515, после прочтения их инструмента pdf2txt.py я написал этот простой фрагмент:
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()
Повторное использование кода pdf2txt.py, который поставляется с pdfminer; вы можете создать функцию, которая будет указывать путь к pdf; опционально, outtype (txt | html | xml | tag) и выбирает как командную строку pdf2txt {'-o': '/path/to/outfile.txt'...}. По умолчанию вы можете позвонить:
convert_pdf(path)
Будет создан текстовый файл, родной файл в файловой системе с оригинальным pdf.
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 дал мне, возможно, одну строку [страница 1 из 7...] на каждую страницу файла PDF, который я пробовал с ним.
Лучший ответ, который у меня есть, это pdftoipe, или код на C++, основанный на Xpdf.
см. мой вопрос о том, как выглядит вывод pdftoipe.
Кроме того, есть PDFTextStream, которая является коммерческой библиотекой Java, которую также можно использовать из Python.
Я использовал pdftohtml с аргументом '-xml', прочитал результат с помощью subprocess.Popen(), который даст вам координаты x, координаты y, ширину, высоту и шрифт каждого фрагмента текста в pdf. Я думаю, что это, вероятно, тоже "evince", потому что выдают те же сообщения об ошибках.
Если вам нужно обрабатывать столбчатые данные, они становятся немного сложнее, так как вам нужно изобрести алгоритм, который подходит для вашего PDF-файла. Проблема в том, что программы, которые создают файлы PDF, не обязательно выкладывают текст в каком-либо логическом формате. Вы можете попробовать простые алгоритмы сортировки, и иногда это работает, но могут быть небольшие "отставшие" и "заблудившиеся" фрагменты текста, которые не помещаются в том порядке, в котором, как вы думали, они будут… поэтому вам нужно проявить творческий подход.
У меня ушло около 5 часов, чтобы выяснить, какой из PDF-файлов я работал. Но это работает довольно хорошо сейчас. Удачи.
Нашел это решение сегодня. Прекрасно работает для меня. Даже рендеринг страниц PDF в изображения PNG. http://www.swftools.org/gfx_tutorial.html