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


какие модули Python лучше всего конвертировать PDF-файлы в текст?

13 346

13 ответов:

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

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

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

версия Python 3 доступна в разделе:

The 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])  

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

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

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

комментарий в коде extractText отмечает:

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

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

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

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

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

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

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

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

PDFminer дал мне, возможно, одну строку [Страница 1 из 7...] на каждой странице PDF-файла, я пробовал с ним.

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

посмотреть у меня вопрос для того, что выход pdftoipe выглядит.

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