7

在使用 pdfminer (pdf2txt.py) 处理 PDF文件 (2.pdf)时,我收到以下错误:

pdf2txt.py 2.pdf 

Traceback (most recent call last):
  File "/usr/local/bin/pdf2txt.py", line 115, in <module>
    if __name__ == '__main__': sys.exit(main(sys.argv))
  File "/usr/local/bin/pdf2txt.py", line 109, in main
    interpreter.process_page(page)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdfinterp.py", line 832, in process_page
    self.render_contents(page.resources, page.contents, ctm=ctm)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdfinterp.py", line 843, in render_contents
    self.init_resources(resources)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdfinterp.py", line 347, in init_resources
    self.fontmap[fontid] = self.rsrcmgr.get_font(objid, spec)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdfinterp.py", line 195, in get_font
    font = self.get_font(None, subspec)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdfinterp.py", line 186, in get_font
    font = PDFCIDFont(self, spec)
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py", line 654, in __init__
    StringIO(self.fontfile.get_data()))
  File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py", line 375, in __init__
    (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))
struct.error: unpack requires a string argument of length 16

虽然类似的文件(1.pdf)不会引起问题。

我找不到有关该错误的任何信息。我在 pdfminer GitHub 存储库上添加了一个问题,但仍未得到答复。有人可以向我解释为什么会这样吗?我该怎么做才能解析2.pdf


更新:直接从 GitHub 存储库安装 pdfminer后,我收到了类似的错误,BytesIO而不是。StringIO

    $ pdf2txt.py 2.pdf 
Traceback (most recent call last):
  File "/home/danil/projects/python/pdfminer-source/env/bin/pdf2txt.py", line 116, in <module>
    if __name__ == '__main__': sys.exit(main(sys.argv))
  File "/home/danil/projects/python/pdfminer-source/env/bin/pdf2txt.py", line 110, in main
    interpreter.process_page(page)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdfinterp.py", line 839, in process_page
    self.render_contents(page.resources, page.contents, ctm=ctm)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdfinterp.py", line 850, in render_contents
    self.init_resources(resources)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdfinterp.py", line 356, in init_resources
    self.fontmap[fontid] = self.rsrcmgr.get_font(objid, spec)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdfinterp.py", line 204, in get_font
    font = self.get_font(None, subspec)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdfinterp.py", line 195, in get_font
    font = PDFCIDFont(self, spec)
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdffont.py", line 665, in __init__
    BytesIO(self.fontfile.get_data()))
  File "/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdffont.py", line 386, in __init__
    (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))
struct.error: unpack requires a string argument of length 16
4

6 回答 6

5

TL; 博士

感谢@mkl 和@hynecker 提供的额外信息……我可以确认这是pdfminer 和你的PDF 中的一个错误。每当 pdfminer 尝试获取嵌入的文件流(例如字体定义)时,它都会在endobj. 可悲的是,并非所有 PDF 都严格添加结束标签,因此 pdfminer 应该对此具有弹性。

快速修复此问题

我创建了一个补丁 - 已作为拉取请求在 github 上提交。请参阅https://github.com/euske/pdfminer/pull/159

详细诊断

正如其他答案中所述,您看到此问题的原因是您没有从流中获得预期的字节数,因为 pdfminer 正在解包数据。但为什么?

正如您在堆栈跟踪中看到的那样,pdfminer(正确地)发现它具有要处理的 CID 字体。然后它继续将嵌入的字体文件处理为 TrueType 字体(在 中pdffont.py)。它试图通过读出一组二进制表来解析相关的流(流 ID 18)。

这不起作用,2.pdf因为它有一个文本流。你可以通过运行看到这一点dumppdf -b -i 18 2.pdf。我在这里开始:

/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0
>> def /CMapName /Adobe-Identity-UCS def
...

所以,垃圾输入,垃圾输出......这是您的文件或pdfminer中的错误吗?好吧,其他读者可以处理它的事实让我怀疑。

进一步挖掘,我发现这个流流 ID 17 相同,它是该ToUnicode字段的 cmap。快速浏览一下PDF 规范会发现它们不可能相同。

进一步深入研究代码,我发现所有流都在获取相同的数据。哎呀!这是错误。原因似乎与此 PDF 缺少一些结束标签这一事实有关 - 正如@hynecker 所指出的那样。

解决方法是为每个流返回正确的数据。任何其他仅吞下错误的修复都将导致错误数据用于所有流,因此,例如,不正确的字体定义。

我相信附带的补丁可以解决您的问题,并且通常可以安全使用。

于 2016-10-27T23:39:27.190 回答
4

我在源代码中修复了您的问题,并尝试了您的文件2.pdf以确保它有效。

在文件pdffont.py我替换了:

class TrueTypeFont(object):

    class CMapNotFound(Exception):
        pass

    def __init__(self, name, fp):
        self.name = name
        self.fp = fp
        self.tables = {}
        self.fonttype = fp.read(4)
        (ntables, _1, _2, _3) = struct.unpack('>HHHH', fp.read(8))
        for _ in xrange(ntables):
            (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))
            self.tables[name] = (offset, length)
        return

这样:

class TrueTypeFont(object):

    class CMapNotFound(Exception):
        pass

    def __init__(self, name, fp):
        self.name = name
        self.fp = fp
        self.tables = {}
        self.fonttype = fp.read(4)
        (ntables, _1, _2, _3) = struct.unpack('>HHHH', fp.read(8))
        for _ in xrange(ntables):
            fp_bytes = fp.read(16)
            if len(fp_bytes) < 16:
                break
            (name, tsum, offset, length) = struct.unpack('>4sLLL', fp_bytes)
            self.tables[name] = (offset, length)
        return

解释

@Nabeel Ahmed 是对的

foramt 字符串 >4sLLL 需要 16 字节大小的缓冲区,该缓冲区已正确指定给 fp.read 以一次读取 16 个字节。

因此,问题只能出在它正在读取的缓冲流上,即特定 PDF 文件的内容。

在代码中,我们看到它fp.read(16)是在没有任何检查的情况下循环生成的。因此,我们不确定它是否成功读取了所有内容。例如,它可以达到EOF.

为了避免这个问题,break当出现这种问题时,我只是退出了for循环。

    for _ in xrange(ntables):
        fp_bytes = fp.read(16)
        if len(fp_bytes) < 16:
            break

在任何常规情况下,它都不应该改变任何东西。

我会尝试在 github 上做一个拉取请求,但我什至不确定它会被接受,所以我建议你现在做一个猴子补丁并立即修改你的/home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdffont.py文件。

于 2016-10-27T11:40:32.453 回答
4

这确实是一个无效的 PDF,因为在三个间接对象之后缺少一些关键字endobj。(对象 5、18 和 22)

PDF 文件中间接对象的定义应包括其对象编号和世代编号(由空格分隔),后跟括在关键字objendobj之间的对象的值。(PDF 参考中的第 7.3.10 章)

示例 2.pdf 是一个简单的 PDF 1.3 版本,它使用简单的未压缩交叉引用和未压缩对象分隔符。通过 grep 命令和 PDF 有 22 个间接对象的通用文件查看器可以很容易地发现故障。模式“obj”被正确地找到了 22 次(幸运的是,为了简单起见,在字符串对象或流中从来没有偶然发现),但是关键字endobj丢失了 3 次。

$ grep --binary-files=text -B1 -A2 -E " obj|endobj" 2.pdf
...
18 0 obj
<< /Length 451967/Length1 451967/Filter [/FlateDecode] >> 
stream
...
endstream                 % # see the missing "endobj" here
17 0 obj
<< /Length 12743 /Filter [/FlateDecode] >> 
stream
...
endstream
endobj
...

类似地,对象 5在对象 1 之前没有endobj ,对象 22 在对象 21 之前没有endobj

众所周知,PDF 中损坏的交叉引用可以并且应该通常由 obj/endobj 关键字重建(参见 PDF 参考,第 C.2 章)如果交叉引用正确,某些应用程序可能会反过来修复丢失的 endobj,但它没有书面建议。

于 2016-10-30T02:16:50.483 回答
2

最后一条错误信息告诉你很多:

文件“/usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py”,第 375 行,在

init (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16)) struct.error: unpack 需要一个长度为 16 的字符串参数

您可以轻松地调试正在发生的事情,例如,将必要的调试语句准确地放在pdffont.py文件中。我的猜测是您的 pdf 内容有一些特别之处。从抛出错误信息的方法名称来看,TrueTypeFont与字体类型有些不兼容。

于 2016-10-25T08:53:20.020 回答
2

让我们从解释您遇到异常的语句开始:

struct.unpack('>4sLLL', fp.read(16))

概要是:

struct.unpack(fmt, buffer)

该方法根据 格式字符串unpack从缓冲区buffer(大概是之前打包的)中解包。结果是一个元组,即使它只包含一个项目。缓冲区的字节大小必须与格式要求的大小相匹配,如 calcsize() 所反映的。pack(fmt, ...) fmt

最常见的情况是,16使用的格式 ( ) 的字节数 ( ) 错误>4sLLL- 例如,对于需要 4 个字节的格式,您指定了 3 个字节:

(name, tsum, offset, length) = struct.unpack('BH', fp.read(3))

为此你会得到

struct.error: unpack requires a string argument of length 4

原因 - 格式结构 ('BH') 需要 4 个字节,即当我们使用 'BH' 格式打包某些内容时,它将占用 4 个字节的内存。这里有一个很好的解释。


为了进一步澄清 - 让我们看看>4sLLL格式字符串。验证unpack缓冲区的大小(您从 PDF 文件中读取的字节数)。引用文档:

缓冲区的字节大小必须与格式要求的大小相匹配,如 calcsize() 所反映的。

>>> import struct 
>>> struct.calcsize('>4sLLL')
16
>>> 

至此,我们可以说这句话没有错:

(name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))

foramt 字符串>4sLLL需要 16 字节大小的缓冲区,该缓冲区已正确指定给 fp.read 以一次读取 16 个字节。

因此,问题只能出在它正在读取的缓冲流上,即特定 PDF 文件的内容。


可能是一个错误 - 根据此评论

这是@euske 的上游 PDFminer 中的一个错误,似乎有针对此的补丁,所以它应该是一个简单的修复。除此之外,我还需要加强 pdf 解析,以便我们永远不会从失败的解析中出错

我将编辑问题,如果我发现在此处添加一些有用的内容 - 解决方案或补丁。

于 2016-10-25T14:17:30.100 回答
1

如果您在应用 Peter 的补丁后仍然遇到一些结构错误,尤其是在一个脚本运行中解析多个文件时(使用 os.listdir),请尝试将资源管理器缓存更改为 false。

rsrcmgr = PDFResourceManager(caching=False)

在应用上述解决方案后,它帮助我摆脱了其余的错误。

于 2016-11-03T14:17:07.293 回答