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

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

在使用 pdfminer (pdf2txt.py) 处理 PDF file (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

虽然类似file (1.pdf)不会造成问题。

我找不到有关该错误的任何信息。我添加了一个 issue on the pdfminer GitHub repository, but it remained unanswered. Can someone explain to me why this is happening? What can I do to parse 2.pdf?


更新:我在 installing pdfminer 之后直接从 GitHub 存储库中得到类似的错误 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

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

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

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

让我们从解释出现异常的语句开始:

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

大纲所在:

struct.unpack(fmt, buffer)

The method unpack, unpacks from the buffer buffer (which presumably earlier packed by pack(fmt, ...)) according to the format string fmt. The result is a tuple even if it contains exactly one item. The buffer’s size in bytes must match the size required by the format, as reflected by calcsize().

最常见的情况是,所用格式 (>4sLLL) 的字节数 (16) 错误 - 例如,对于需要 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 个字节的内存。 很好的解释here.


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

The buffer’s size in bytes must match the size required by the format, as reflected by calcsize().

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

至此我们可以说声明没有错:

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

格式字符串 >4sLLL 需要 16 字节大小的缓冲区,正确指定为 fp.read 一次读取 16 字节。

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


可能是一个错误 - 根据这个 comment:

This is a bug in the upstream PDFminer by @euske There seems to be patches for this so it should be an easy fix. Beyond this I also need to strengthen the pdf parsing such that we never error out from a failed parse

我将编辑问题,我发现可以在此处添加一些有用的东西 - 解决方案或补丁。

我在源代码中修复了你的问题,我尝试了你的文件 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 是对的

The foramt string >4sLLL requires 16 bytes size of buffer, which is specified correctly to fp.read to read 16 bytes at a time.

So, the problem can only be with the buffer stream it's reading i.e. the content of your specific PDF file.

在代码中我们看到 fp.read(16) 是在循环中生成的,没有任何 check.Thus,我们不确定它是否成功读取了所有内容。例如,它可以达到 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 文件现在。

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 spec 表明它们不能相同。

进一步深入研究代码,我发现所有流都获得相同的数据。哎呀!这是错误。原因似乎与此 PDF 缺少一些结束标记有关 - 如@hynecker 所述。

解决方法是 return 每个流的正确数据。任何其他只是吞下错误的修复都会导致错误的数据被用于所有流,例如,不正确的字体定义。

我相信随附的补丁可以解决您的问题,一般来说应该可以安全使用。

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

The definition of an indirect object in a PDF file shall consist of its object number and generation number (separated by white space), followed by the value of the object bracketed between the keywords obj and endobj. (chapter 7.3.10 in PDF reference)

示例 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 如果交叉引用是正确的,但它不是书面建议。

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

rsrcmgr = PDFResourceManager(caching=False)

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