【问题标题】:Python module for converting PDF to text [closed]用于将 PDF 转换为文本的 Python 模块 [关闭]
【发布时间】:2010-09-06 17:48:19
【问题描述】:

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

【问题讨论】:

标签: python pdf text-extraction pdf-scraping


【解决方案1】:

我使用pdftohtml-xml 参数,使用subprocess.Popen() 读取结果,这将为您提供每个sn-p pdf 中的文本。我认为这也是“evince”可能使用的东西,因为同样的错误信息会喷涌而出。

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

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

【讨论】:

    【解决方案2】:

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

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

    Python 3 版本在以下位置可用:

    【讨论】:

    • 我刚刚添加了一个描述如何使用 pdfminer 作为库的答案。
    • 不支持 python 3 :(
    • 我在this thread 中提供的答案可能对查看此答案并想知道如何使用库的人有用。我举例说明如何使用 PDFMiner 库从 PDF 中提取文本。由于文档有点稀疏,我认为它可能会对一些人有所帮助。
    • 关于python 3,有一个六基分叉pypi.python.org/pypi/pdfminer.six
    【解决方案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])  
    

    【讨论】:

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

    我需要在 python 模块中将特定的 PDF 转换为纯文本。我使用PDFMiner 20110515,在阅读了他们的pdf2txt.py 工具后,我写了这个简单的sn-p:

    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
    【解决方案5】:

    PDFMiner 软件包自 codeape 发布以来已更改。

    编辑(再次):

    PDFMiner 再次更新到版本20100213

    您可以通过以下方式检查您安装的版本:

    >>> import pdfminer
    >>> pdfminer.__version__
    '20100213'
    

    这是更新版本(我更改/添加的内容带有 cmets):

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

    【讨论】:

    • In [6]: import pdfminer In [7]: pdfminer.__version__ Out[7]: '20100424' In [8]: from pdfminer.converter import LTTextItem ImportError: cannot import name 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
    【解决方案6】:

    您也可以很容易地将 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 的源码,上面的代码是受该文件旧版本的启发。
    【解决方案7】:

    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",第 48 行,在 ImportError: cannot import name PDF} 但是 PDF 类在那里!你知道如何解决这个问题吗?
    • 不,这听起来很奇怪。你有依赖吗?
    • 通常我会收到有关丢失依赖项的消息,在这种情况下,我会收到经典消息“import slate File”C:\Python33\lib\site-packages\slate-0.3-py3.3.egg\ slate_init_.py",第 48 行,在 ImportError: cannot import name PDF"
    • Slate 0.3 需要 pdfminer 20110515,据此GitHub issue
    • 这个包不再维护。避免使用它。你甚至不能在 Python 3.5 中使用它
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      重新利用 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
      

      【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          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 得到了相同的结果——文本被提取,单词之间没有空格。
          • 当我执行 page.extractText() 函数时,我得到错误'TypeError: Can't convert 'bytes' object to str implicitly' 我该如何处理?
          【解决方案12】:

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

          【讨论】:

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-07-30
            • 2017-04-30
            • 1970-01-01
            相关资源
            最近更新 更多