ChatGPT解决这个技术问题 Extra ChatGPT

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

关闭。此问题不符合 Stack Overflow 准则。它目前不接受答案。我们不允许提出有关书籍、工具、软件库等建议的问题。您可以编辑问题,以便可以用事实和引用来回答它。 7年前关闭。改进这个问题

是否有任何 python 模块可以将 PDF 文件转换为文本?我尝试了在使用 pypdf 的 Activestate 中找到的 one piece of code,但生成的文本之间没有空格并且没有用。

我正在寻找类似的解决方案。我只需要从 pdf 文件中读取文本。我不需要图像。 pdfminer 是一个不错的选择,但我没有找到关于如何提取文本的简单示例。最后我得到了这个答案(stackoverflow.com/questions/5725278/…),现在正在使用它。
由于问题已关闭,我将其重新发布在 Stack Exchange 上,专门用于软件推荐,以防有人想写一个新答案:Python module for converting PDF to text
对 UTF-8 内容有效的唯一解决方案:Apache Tika
我想更新 Python 中 PDF 到文本转换的可用选项列表,GroupDocs.Conversion Cloud SDK for Python 将 PDF 准确地转换为文本。
尝试使用 PDFminer.six,请参阅此答案以获取示例:stackoverflow.com/a/61857301/7483211

F
Felipe Augusto

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

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

Python 3 版本可在以下位置获得:

https://github.com/pdfminer/pdfminer.six


我刚刚添加了一个描述如何将 pdfminer 用作库的答案。
我在 this thread 中提供的答案可能对查看此答案并想知道如何使用该库的人有用。我举例说明如何使用 PDFMiner 库从 PDF 中提取文本。由于文档有点稀疏,我认为它可能会帮助一些人。
你能帮我弄清楚如何将pdf转换为标记的pdf格式吗?
不幸的是,pdfminer 并不是很快,尤其是当您要使用它来处理超过 100 页的长 pdf 文档时。
C
Community

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

编辑(再次):

这是 pypi20100619p1 中最新版本的更新。简而言之,我将 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()

在 [6]:导入 pdfminer 在 [7]:pdfminer.__version__ Out[7]:'20100424' 在 [8]:从 pdfminer.converter 导入 LTTextItem ImportError:无法导入名称 LTTextItem .... LITERALS_DCT_DECODE LTChar LTImage LTPolygon LTTextBox LITERAL_DEVICE_GRAY LTContainer LTLine LTRect LTTextGroup LITERAL_DEVICE_RGB LTFigure LTPage LTText LTTextLine
@skyl,上面的代码适用于以前的版本“20100213”。从他们网站上的更改列表来看,他们似乎将 LTTextItem 更改为 LTCharunixuser.org/~euske/python/pdfminer/index.html#changes
@Oeufcoque Penteano,谢谢!根据您的评论,我已在版本 20110515 的答案中添加了另一部分。
@user3272884 给出的答案截至 2014 年 5 月 1 日
我今天不得不解决同样的问题,稍微修改了 tgray 的代码以提取有关空格的信息,发布它here
T
Thomas

由于这些解决方案都不支持最新版本的 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])  

这是我发现的第一个片段,它实际上适用于奇怪的 PDF 文件(特别是可以从 packtpub 获得的免费电子书)。其他所有代码只返回奇怪编码的原始内容,但您的实际上返回文本。谢谢!
您可能想在获取数据后执行 retstr.seek(0),否则您将从所有页面中累积文本。
要与 python3 一起使用,除了 print 命令后明显的括号外,必须将 file 命令替换为 open 并从包 io 中导入 StringIO
哇。当我第一次复制它时,这个块就完美地工作了。太棒了!解析和修复数据,而不必担心输入数据。
pdfminer 不适用于 python3。此代码不适用于 pdfminer3k
J
Jamie

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


这似乎是此处列出的工具中最有用的工具,-layout 选项可将文本保持在与 PDF 中相同的位置。现在,如果我能弄清楚如何将 PDF 的内容通过管道传输到其中。
在测试了几种解决方案之后,这个似乎是最简单和最强大的选择。可以很容易地被 Python 包装,使用临时文件来指示输出写入的位置。
Cerin,使用“-”作为文件名将输出重定向到标准输出。这样你就可以使用简单的 subprocess.check_output 并且这个调用感觉就像一个内部函数。
只是为了重新强制任何使用它的人。 . . pdftotext 似乎工作得很好,但如果您想在标准输出上查看结果,它需要第二个参数是连字符。
这将递归地转换从当前文件夹开始的所有 PDF 文件:find . -iname "*.pdf" -exec pdftotext -enc UTF-8 -eol unix -raw {} \; 默认情况下,生成的文件采用带有 .txt 扩展名的原始名称。
T
Tony Meyer

pyPDF 工作正常(假设您使用格式良好的 PDF)。如果你想要的只是文本(带空格),你可以这样做:

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

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

extractText 代码注释中的注释:

按照内容流中提供的顺序找到所有文本绘制命令,然后提取文本。这适用于某些 PDF 文件,但对其他文件效果不佳,具体取决于所使用的生成器。这将在未来进行完善。不要依赖从这个函数出来的文本的顺序,因为如果这个函数变得更复杂,它会改变。

这是否是一个问题取决于您对文本所做的事情(例如,如果顺序无关紧要,那很好,或者如果生成器按照显示顺序将文本添加到流中,那很好) .我有日常使用的pyPdf提取代码,没有任何问题。


不支持 Unicode :(
pyPdf 现在确实支持 UTF。
这个库看起来像垃圾。对随机 PDF 进行测试时出现错误“pyPdf.utils.PdfReadError: EOF marker not found”
从问题:生成的文本之间没有空格并且没有用。我使用了 pyPDF 并得到了相同的结果——文本被提取,单词之间没有空格。
c
codeape

你也可以很容易地将 pdfminer 用作库。您可以访问 pdf 的内容模型,并且可以创建自己的文本提取。我这样做是为了使用下面的代码将 pdf 内容转换为分号分隔的文本。

该函数只是简单地根据它们的 y 和 x 坐标对 TextItem 内容对象进行排序,并输出与一个文本行具有相同 y 坐标的项目,用 ';' 分隔同一行上的对象人物。

使用这种方法,我能够从 pdf 中提取文本,而其他工具无法提取适合进一步解析的内容。我尝试过的其他工具包括 pdftotext、ps2ascii 和在线工具 pdftextonline.com。

pdfminer 是一个非常宝贵的 pdf-scraping 工具。


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 编写的,请参阅下面的评论。


你需要什么样的插件才能工作?我下载并安装了pdfminer,但还不够...
上面的代码是针对旧版本的 PDFminer 编写的。 API 在更新的版本中发生了变化(例如,包现在是 pdfminer,而不是 pdflib)。我建议您查看 PDFminer 源代码中 pdf2txt.py 的源代码,上面的代码是受该文件旧版本的启发。
T
Tim McNamara

slate 是一个项目,它使使用库中的 PDFMiner 变得非常简单:

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

执行“import slate”时出现导入错误:{File "C:\Python33\lib\site-packages\slate-0.3-py3.3.egg\slate_init_.py", line 48, in ImportError : 无法导入名称 PDF} 但是 PDF 类在那里!你知道如何解决这个问题吗?
不,这听起来很奇怪。你有依赖吗?
通常我会收到有关丢失依赖项的消息,在这种情况下,我会收到经典消息“import slate File "C:\Python33\lib\site-packages\slate-0.3-py3.3.egg\slate_init_.py",第 48 行,在 ImportError: 无法导入名称 PDF"
根据此GitHub issue,Slate 0.3 需要 pdfminer 20110515
此软件包不再维护。避免使用它。你甚至不能在 Python 3.5 中使用它
g
gonz

我需要在 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() 

def to_txt(pdf_path):
如果我只想转换一定数量的页面,我将如何使用这段代码?
@psychok7 您是否尝试过使用 pdf2txt 工具?它似乎在当前版本中使用 -p 标志支持该功能,实现似乎很容易遵循并且也应该很容易定制:github.com/euske/pdfminer/blob/master/tools/pdf2txt.py希望它有所帮助! :)
thanx @gonz,我尝试了以上所有方法,但你的解决方案对我来说是完美的,输出带空格:)
pdf2txt.py 为我安装在这里:C:\Python27\Scripts\pdfminer\tools\pdf2txt.py
S
Skylar Saveland

重新利用 pdfminer 附带的 pdf2txt.py 代码;您可以创建一个函数,该函数将获取 pdf 的路径;可选地,一个输出类型(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

C
Community

PDFminer 在我尝试使用的 pdf 文件的每一页上都给了我一行 [page 1 of 7...]。

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

请参阅 my question 了解 pdftoipe 的输出内容。


m
msanders

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


F
Felipe Augusto

我已将 pdftohtml-xml 参数一起使用,使用 subprocess.Popen() 读取结果,这将为您提供每个 snippet 文本的 x 坐标、y 坐标、宽度、高度和字体在pdf中。我认为这也是“evince”可能使用的内容,因为同样的错误消息会喷涌而出。

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

我花了大约 5 个小时才为我正在处理的 pdf 找出一个。但它现在工作得很好。祝你好运。


A
Andrey Shipilov

今天找到了解决方案。对我很有用。甚至将 PDF 页面渲染为 PNG 图像。 http://www.swftools.org/gfx_tutorial.html