在使用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文件中间接对象的定义应包括其对象编号和生成编号(以空格分隔),后面是括在关键字obj和endobj之间的对象值。(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)
它帮助我在应用上述解决方案后摆脱了其余的错误