Модуль Python для преобразования PDF в текст

Каковы лучшие модули Python для преобразования PDF файлов в текст?

Ответ 1

Попробуйте PDFMiner. Он может извлекать текст из файлов PDF в формате HTML, SGML или "Tagged PDF".

http://www.unixuser.org/~euske/python/pdfminer/index.html

Отмеченный формат в формате PDF кажется самым чистым, и удаление XML-тегов оставляет только голой текст.

Версия Python 3 доступна под:

Ответ 2

Пакет PDFMiner изменился после публикации codeape.

EDIT (снова):

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()

EDIT (еще один раз):

Обновлено для версии 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()

Ответ 3

Поскольку ни один из этих решений не поддерживает последнюю версию 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])  

Ответ 4

pyPDF отлично работает (при условии, что вы работаете с хорошо сформированными PDF файлами). Если все, что вам нужно, это текст (с пробелами), вы можете просто сделать:

import pyPdf
pdf = pyPdf.PdfFileReader(open(filename, "rb"))
for page in pdf.pages:
    print page.extractText()

Вы также можете легко получить доступ к метаданным, данным изображения и т.д.

Комментарий в примечаниях к фрагменту extractText:

Найдите все команды рисования текста в порядок, который они предоставляются в поток контента и извлечение текста. Это хорошо работает для некоторых файлов PDF, но плохо для других, в зависимости от используемый генератор. Это будет усовершенствованный в будущем. Не полагайтесь на порядок текста, выходящий из этого функции, поскольку это изменится, если это функция сделана более сложной.

Независимо от того, является ли это проблемой, зависит от того, что вы делаете с текстом (например, если порядок не имеет значения, это нормально, или если генератор добавляет текст в поток в том порядке, в котором он будет отображаться, это нормально). У меня есть код извлечения pyPdf в ежедневном использовании без каких-либо проблем.

Ответ 5

Pdftotext Программа с открытым исходным кодом (часть Xpdf), которую вы могли бы вызывать из python (не то, что вы просили, но может быть полезно). Я использовал его без проблем. Я думаю, что Google использует его на рабочем столе Google.

Ответ 6

Вы также можете легко использовать 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()

UPDATE

Приведенный выше код написан на старой версии API, см. мой комментарий ниже.

Ответ 7

slate - это проект, который очень упрощает использование PDFMiner из библиотеки:

>>> with open('example.pdf') as f:
...    doc = slate.PDF(f)
...
>>> doc
[..., ..., ...]
>>> doc[1]
'Text from page 2...'   

Ответ 8

Мне нужно было преобразовать определенный 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() 

Ответ 9

Повторное использование кода 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

Ответ 10

Кроме того, есть PDFTextStream, который является коммерческой библиотекой Java, которая также может использоваться с Python.

Ответ 11

Я использовал pdftohtml с аргументом "-xml", прочитал результат с помощью subprocess.Popen(), который даст вам координату x, y координату, ширину, высоту и шрифт каждого "фрагмента" текста в pdf. Я думаю, что это то, что "evince", вероятно, использует слишком, потому что одни и те же сообщения об ошибках выводятся из строя.

Если вам нужно обрабатывать столбчатые данные, это немного усложняется, поскольку вам нужно придумать алгоритм, который подходит вашему файлу PDF. Проблема в том, что программы, которые делают PDF файлы, на самом деле не обязательно выкладывают текст в любом логическом формате. Вы можете попробовать простые алгоритмы сортировки, и это работает иногда, но могут быть небольшие "отставшие" и "страйки", фрагменты текста, которые не попадают в порядок, который, по вашему мнению, они... так что вам нужно проявить творческий подход.

Мне потребовалось около 5 часов, чтобы выяснить, какой из них для pdf, над которым я работал. Но сейчас он работает очень хорошо. Удачи.

Ответ 12

PDFminer дал мне, пожалуй, одну строку [стр. 1 из 7...] на каждой странице pdf файла, с которым я попытался.

Лучший ответ, который у меня есть до сих пор, - pdftoipe, или код С++, основанный на Xpdf.

см. мой вопрос, для чего выглядит вывод pdftoipe.

Ответ 13

Нашел это решение сегодня. Отлично работает для меня. Даже предоставление PDF-страниц для изображений PNG. http://www.swftools.org/gfx_tutorial.html