结构体.错误:unpack需要长度为16的字符串参数



在使用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

TL;

感谢@mkl和@hynecker提供的额外信息…这样我就可以确认这是pdfminer和您的PDF中的错误。每当pdfminer试图获得嵌入文件流(例如字体定义)时,它就会在endobj之前拾取文件中的最后一个。遗憾的是,并不是所有的pdf都严格地添加了结束标签,所以pdfminer应该能够适应这一点。

快速修复此问题

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

<

详细诊断/strong>

正如在其他答案中提到的,您看到这种情况的原因是,当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规范就会发现,它们不可能是相同的。

进一步挖掘代码,我看到所有流都获得相同的数据。哦!这就是bug。原因似乎与这个PDF缺少一些结束标签有关——正如@hynecker所指出的。

修复方法是为每个流返回正确的数据。任何其他只是吞下错误的修复将导致所有流使用坏数据,例如,不正确的字体定义。

我相信附带的补丁会解决你的问题,应该是安全的使用一般。

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

格式字符串>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文件。

这确实是一个无效的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,但没有书面建议。

最后一个错误消息告诉你很多:

File "/usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py",第375行,

init (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))结构体。错误:unpack需要长度为16的字符串参数

您可以很容易地调试正在发生的事情,例如,通过将必要的调试语句正好放在pdffont.py文件中。我猜你的pdf内容有一些特别之处。根据抛出错误消息的方法名称TrueTypeFont判断,该方法与字体类型不兼容。

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

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

,其中概要为:

struct.unpack(fmt, buffer)

方法unpack,从缓冲区buffer(其中大概是早些时候由pack(fmt, ...)根据格式字符串 fmt。结果是一个元组,即使它只包含一个项。缓冲区的字节大小必须匹配格式所需的大小,由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字节的内存。这里有一个很好的解释


为了进一步澄清它——让我们看看>4sLLL格式字符串。为了验证unpack对缓冲区(从PDF文件读取的字节)的期望大小。引用自docs:

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

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

到这里,我们可以说语句没有问题:

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

格式字符串>4sLLL需要16字节的缓冲区大小,正确地指定为fp。Read表示每次读取16字节。

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


可能是一个bug——根据这个注释:

这是@euske在上游PDFminer中的一个错误补丁,所以它应该是一个容易的修复。除此之外,我还需要加强PDF解析,使我们永远不会从失败的解析

如果我发现有什么有用的东西可以在这里添加——一个解决方案,或者一个补丁,我会编辑这个问题。

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

rsrcmgr = PDFResourceManager(caching=False)

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

最新更新