用于将 PDF 转换为文本的 Python 模块[关闭]

2024-12-30 08:41:00
admin
原创
47
摘要:问题描述:有没有python模块可以将PDF文件转换为文本?我尝试了Activestate中找到的一段使用pypdf的代码,但生成的文本之间没有空格,毫无用处。解决方案 1:尝试PDFMiner。它可以从 PDF 文件中提取 HTML、SGML 或“标记 PDF”格式的文本。标记的 PDF 格式似乎是最干净的...

问题描述:

有没有python模块可以将PDF文件转换为文本?我尝试了Activestate中找到的一段使用pypdf的代码,但生成的文本之间没有空格,毫无用处。


解决方案 1:

尝试PDFMiner。它可以从 PDF 文件中提取 HTML、SGML 或“标记 PDF”格式的文本。

标记的 PDF 格式似乎是最干净的,去掉 XML 标签就只剩下裸文本。

Python 3 版本可从以下位置获取:

解决方案 2:

自codeape发布 以来, PDFMiner包已经发生了变化。

编辑(再次):

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

    # ... 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
" % i)
        interpreter.process_page(page)
        outfp.write("END PAGE %d
" % 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("
")

    # ... 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
" % i)
        if page is not None:
            interpreter.process_page(page)
        outfp.write("END PAGE %d
" % 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("
")

    # ... 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
" % i)
        if page is not None:
            interpreter.process_page(page)
        outfp.write("END PAGE %d
" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

解决方案 3:

由于这些解决方案都不支持最新版本的 PDFMiner,因此我编写了一个简单的解决方案,它将使用 PDFMiner 返回 pdf 的文本。这将适用于那些在使用时遇到导入错误的人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:

Pdftotext一个开源程序(Xpdf 的一部分),你可以从 python 调用它(不是你想要的,但可能有用)。我用过它,没有问题。我认为谷歌在谷歌桌面上使用它。

解决方案 5:

pyPDF运行良好(假设您使用的是格式良好的 PDF)。如果您只需要文本(带空格),您可以这样做:

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

您还可以轻松访问元数据、图像数据等。

extractText代码中的注释如下:

按照内容流中提供的顺序找到所有文本绘制命令,并提取文本。这对某些 PDF 文件很有效,但对其他文件则效果不佳,具体取决于所使用的生成器。这将在未来得到改进。不要依赖此功能输出的文本顺序,因为如果此功能变得更加复杂,文本顺序将会改变。

这是否是个问题取决于你对文本的操作(例如,如果顺序无关紧要,那就没问题,或者如果生成器按照显示的顺序将文本添加到流中,那就没问题)。我每天都在使用 pyPdf 提取代码,没有任何问题。

解决方案 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("
")

    # ... 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
" % i)
        interpreter.process_page(page)
        outfp.write("END PAGE %d
" % i)

    device.close()
    fp.close()

    return outfp.getvalue()

更新

上面的代码是针对旧版本的 API 编写的,请参阅下面的评论。

解决方案 7:

slate是一个可以非常简单地从库中使用 PDFMiner 的项目:

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

解决方案 8:

我需要在 Python 模块中将特定的 PDF 转换为纯文本。我使用了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:

重新利用 pdfminer 附带的 pdf2txt.py 代码;您可以创建一个函数,该函数将获取 pdf 的路径;可选地,outtype(txt|html|xml|tag)和 opts,如命令行 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:

我尝试用 PDFminer 在每一页 pdf 文件中只显示一行 [第 1 页,共 7 页...]。

到目前为止我得到的最佳答案是 pdftoipe,或者基于 Xpdf 的 c++ 代码。

请参阅我的问题,了解 pdftoipe 的输出是什么样的。

解决方案 11:

此外,还有PDFTextStream,它是一个商业 Java 库,也可以在 Python 中使用。

解决方案 12:

我使用了pdftohtml参数-xml,用读取结果subprocess.Popen(),这将为您提供 pdf 中每个文本片段的 x 坐标、y 坐标、宽度、高度和字体。我认为“evince”可能也使用了这种方法,因为会出现相同的错误消息。

如果您需要处理列数据,那么事情会变得稍微复杂一些,因为您必须发明一种适合您的 pdf 文件的算法。问题是,制作 PDF 文件的程序实际上并不一定以任何逻辑格式布局文本。您可以尝试简单的排序算法,有时它会起作用,但可能会有一些“落后者”和“离群者”,即文本片段没有按照您认为的顺序排列。所以你必须发挥创造力。

我花了大约 5 个小时才找到一个适合我正在处理的 PDF 的版本。不过现在它运行得很好。祝你好运。

解决方案 13:

今天找到了解决方案。对我来说效果很好。甚至可以将 PDF 页面渲染为 PNG 图像。http
://www.swftools.org/gfx_tutorial.html

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   990  
  在项目管理领域,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节,它不仅验证了项目团队的专业能力,还直接关系到项目的成功与否。在这一评审过程中,沟通技巧的运用至关重要。有效的沟通不仅能够确保信息的准确传递,还能增强团队协作,提升评审效率。本文将深入探讨CDCP...
华为IPD流程   26  
  IPD(Integrated Product Development,集成产品开发)是一种以客户需求为核心、跨部门协同的产品开发模式,旨在通过高效的资源整合和流程优化,提升产品开发的成功率和市场竞争力。在IPD培训课程中,掌握关键成功因素是确保团队能够有效实施这一模式的核心。以下将从五个关键成功因素展开讨论,帮助企业和...
IPD项目流程图   27  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司在其全球化进程中逐步构建和完善的一套高效产品开发管理体系。这一流程不仅帮助华为在技术创新和产品交付上实现了质的飞跃,还为其在全球市场中赢得了显著的竞争优势。IPD的核心在于通过跨部门协作、阶段性评审和市场需求驱动,确保...
华为IPD   26  
  华为作为全球领先的通信技术解决方案提供商,其成功的背后离不开一套成熟的管理体系——集成产品开发(IPD)。IPD不仅是一种产品开发流程,更是一种系统化的管理思想,它通过跨职能团队的协作、阶段评审机制和市场需求驱动的开发模式,帮助华为在全球市场中脱颖而出。从最初的国内市场到如今的全球化布局,华为的IPD体系在多个领域展现...
IPD管理流程   53  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用