struct.error:unpack需要长度为16的字符串参数

时间:2016-10-20 15:28:22

标签: python pdf pdftotext pdfminer pdf-parsing

使用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)不会导致问题。

我找不到有关错误的任何信息。我在pdfminer GitHub存储库中添加了issue,但它仍未得到答复。有人可以向我解释为什么会这样吗?我该怎么做才能解析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

6 个答案:

答案 0 :(得分:5)

<强> TL; DR

感谢@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所指出的那样。

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

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

答案 1 :(得分:4)

我在源代码中修复了您的问题,并尝试使用您的文件/Products?$select=ProductDetails 以确保其有效。

pdffont.py文件中,我更换了:

2.pdf

由此:

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

<强>说明

@Nabeel Ahmed是对的

  

foramt字符串&gt; 4sLLL需要16字节大小的缓冲区,正确指定fp.read一次读取16个字节。

     

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

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

为了避免这个问题,当出现这种问题时,我只是{for}循环中的EOF

break

在任何常规案例中,无论如何都不应该改变任何内容。

我会尝试在github上做一个拉取请求,但我甚至不确定它会被接受所以我建议你现在做一个猴子补丁并立即修改你的 for _ in xrange(ntables): fp_bytes = fp.read(16) if len(fp_bytes) < 16: break 文件。

答案 2 :(得分:4)

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

  

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

示例2.pdf是一个简单的PDF 1.3版本,它使用简单的无压缩交叉引用和未压缩的对象分隔符。通过grep命令和一般文件查看器可以很容易地找到失败,即PDF有22个间接对象。模式“obj”正好被正确找到22次(绝对不会出现在字符串对象或流中,幸运的是为了简单起见),但关键字 endobj 缺少三次。

$ 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 ,但这不是书面建议。

答案 3 :(得分:2)

最后一条错误消息告诉您很多:

  

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

     

<强>初始化       (name,tsum,offset,length)= struct.unpack(&#39;&gt; 4sLLL&#39;,fp.read(16))   struct.error:unpack需要一个长度为16的字符串参数

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

答案 4 :(得分:2)

首先解释一下您将获得异常的声明:

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

概要是:

struct.unpack(fmt, buffer)

  

方法unpack,从缓冲区buffer解包(其中   据推测,早先由pack(fmt, ...)包装   format string fmt。结果是一个元组,即使它   只包含一个项目。缓冲区的大小(字节)必须与   格式所需的大小,由calcsize()反映。

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

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

为此你会得到

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

原因 - 格式结构(&#39; BH&#39;)需要4个字节,即当我们使用&#39; BH&#39;格式化它将占用4个字节的内存。 一个很好的解释here

为了进一步澄清 - 让我们看看>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文件的内容。

可能是一个错误 - 根据comment

  

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

我会编辑一个问题,我发现这里有一些有用的东西 - 解决方案或补丁。

答案 5 :(得分:0)

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

rsrcmgr = PDFResourceManager(caching=False)

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