使用PDFMiner解析没有/Root对象的PDF文件

14

我正在尝试使用PDFMiner的Python绑定从大量PDF中提取文本。我编写的模块适用于许多PDF,但对于部分PDF,我会收到这个有点晦涩的错误消息:

ipython堆栈跟踪:

/usr/lib/python2.7/dist-packages/pdfminer/pdfparser.pyc in set_parser(self, parser)
    331                 break
    332         else:
--> 333             raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
    334         if self.catalog.get('Type') is not LITERAL_CATALOG:
    335             if STRICT:

PDFSyntaxError: No /Root object! - Is this really a PDF?
当然,我立即检查这些PDF是否已经损坏了,但它们可以被正常读取。
尽管缺少根对象,有没有办法阅读这些PDF?我不太确定该从哪里开始。
非常感谢!
编辑:
我试图使用PyPDF进行一些差异诊断。下面是堆栈跟踪:
In [50]: pdf = pyPdf.PdfFileReader(file(fail, "rb"))
---------------------------------------------------------------------------
PdfReadError                              Traceback (most recent call last)
/home/louist/Desktop/pdfs/indir/<ipython-input-50-b7171105c81f> in <module>()
----> 1 pdf = pyPdf.PdfFileReader(file(fail, "rb"))

/usr/lib/pymodules/python2.7/pyPdf/pdf.pyc in __init__(self, stream)
    372         self.flattenedPages = None
    373         self.resolvedObjects = {}
--> 374         self.read(stream)
    375         self.stream = stream
    376         self._override_encryption = False

/usr/lib/pymodules/python2.7/pyPdf/pdf.pyc in read(self, stream)
    708             line = self.readNextEndLine(stream)
    709         if line[:5] != "%%EOF":
--> 710             raise utils.PdfReadError, "EOF marker not found"
    711 
    712         # find startxref entry - the location of the xref table


PdfReadError: EOF marker not found

Quonux建议可能是因为PDFMiner在遇到第一个EOF字符后停止解析。这似乎表明情况并非如此,但我很茫然。有什么想法吗?


可能是 PDFMiner 在找到第一个 %%EOF 标签后终止了对根节点的搜索,_但_在该标签之后可能还有更多节点,因此它无法找到。另一个原因可能是文件被压缩了? - Quonux
@Quonux,我该如何测试这是否是正确的?有没有选项可以强制PDFMiner搜索整个文档以查找根节点?关于压缩的可能性,有没有办法检查这一点?如果文件被压缩了怎么办? - Louis Thibault
@Quonux,我添加了一个使用pypdf尝试的堆栈跟踪。这有助于缩小原因吗? - Louis Thibault
也许解析器期望找到一个%%EOF标签,但是没有找到...也许你可以通过以下方式修复它:
  • 打开“错误”的文件
  • 以二进制模式写入/追加“%%EOF\n”到文件末尾
  • 关闭文件
  • 再次尝试解析
- Quonux
5个回答

6
在slate pdf中的解决方案是使用“rb”——读取二进制模式。由于slate pdf依赖于PDFMiner,我也有同样的问题,这应该可以解决你的问题。
fp = open('C:\Users\USER\workspace\slate_minner\document1.pdf','rb')
doc = slate.PDF(fp)
print doc

2
我尝试了这个选项,得到了完全相同的错误代码:“没有/Root对象!- 这真的是PDF吗?” - elPastor

5
有趣的问题。我进行了一些研究:
解析PDF文件的函数(来自矿工源代码):
def set_parser(self, parser):
        "Set the document to use a given PDFParser object."
        if self._parser: return
        self._parser = parser
        # Retrieve the information of each header that was appended
        # (maybe multiple times) at the end of the document.
        self.xrefs = parser.read_xref()
        for xref in self.xrefs:
            trailer = xref.get_trailer()
            if not trailer: continue
            # If there's an encryption info, remember it.
            if 'Encrypt' in trailer:
                #assert not self.encryption
                self.encryption = (list_value(trailer['ID']),
                                   dict_value(trailer['Encrypt']))
            if 'Info' in trailer:
                self.info.append(dict_value(trailer['Info']))
            if 'Root' in trailer:
                #  Every PDF file must have exactly one /Root dictionary.
                self.catalog = dict_value(trailer['Root'])
                break
        else:
            raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
        if self.catalog.get('Type') is not LITERAL_CATALOG:
            if STRICT:
                raise PDFSyntaxError('Catalog not found!')
        return

如果您在EOF方面遇到问题,将会引发另一个异常: '''源代码中的另一个函数'''

def load(self, parser, debug=0):
        while 1:
            try:
                (pos, line) = parser.nextline()
                if not line.strip(): continue
            except PSEOF:
                raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
            if not line:
                raise PDFNoValidXRef('Premature eof: %r' % parser)
            if line.startswith('trailer'):
                parser.seek(pos)
                break
            f = line.strip().split(' ')
            if len(f) != 2:
                raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
            try:
                (start, nobjs) = map(long, f)
            except ValueError:
                raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
            for objid in xrange(start, start+nobjs):
                try:
                    (_, line) = parser.nextline()
                except PSEOF:
                    raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
                f = line.strip().split(' ')
                if len(f) != 3:
                    raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
                (pos, genno, use) = f
                if use != 'n': continue
                self.offsets[objid] = (int(genno), long(pos))
        if 1 <= debug:
            print >>sys.stderr, 'xref objects:', self.offsets
        self.load_trailer(parser)
        return

来自维基百科(PDF规范):PDF文件主要由对象组成,其中有八种类型:

Boolean values, representing true or false
Numbers
Strings
Names
Arrays, ordered collections of objects
Dictionaries, collections of objects indexed by Names
Streams, usually containing large amounts of data
The null object
对象可以是直接的(嵌入在另一个对象中)或间接的。间接对象用一个对象号和一个生成号进行编号。xref表是一个索引表,给出了每个间接对象从文件开始的字节偏移量。这种设计允许对文件中的对象进行高效的随机访问,并且允许进行小的更改而不必重新编写整个文件(增量更新)。从PDF版本1.5开始,间接对象也可以位于称为对象流的特殊流中。这种技术可以减小具有大量小间接对象的文件的大小,特别适用于标记PDF。
我认为问题在于您的“损坏的PDF”页面上有一些“根元素”。
可能的解决方案:
您可以下载源代码,在检索xref对象和尝试解析此对象的解析器的每个位置编写“打印函数”。在出现此错误之前,可以确定完整的错误堆栈。
PS:我认为这是产品中的某种错误。

Dmitry,感谢您的回复。如果我理解正确,您怀疑这是PDFMiner中的一个错误?我很惊讶,因为在PyPDF中也观察到了类似的行为。或者您是指该错误出现在创建“损坏”的PDF的任何软件中?关于您的解决方案,您是指我应该在PDFParser对象方法中添加打印行,无论它们管理xref对象的方式如何?我对我应该做什么还有点不清楚。谢谢! - Louis Thibault
您好。只需取两个文件(正常和损坏的),并尝试在PDF分析工具中分析每个文件。我认为在损坏的PDF中将会有无效的xref结构。分析后,请尝试修复PDF结构(http://www.w3.org/WAI/GL/WCAG20-TECHS/pdf.html)。 - Dmitry Zagorulkin
http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/ 是一套非常棒的工具,可以执行这种类型的操作。 - Dmitry Zagorulkin
Dimitry,谢谢你提供的链接。我会尝试使用pdftk。如果工作已经完成,为什么还要费力去修复呢;-) - Louis Thibault
1
如果您使用Python制作PDF修复工具,请给我反馈 =) - Dmitry Zagorulkin

1
我在Ubuntu中遇到了同样的问题,我有一个非常简单的解决方法。只需将pdf文件打印为pdf即可。如果您在Ubuntu上:
  1. 使用(Ubuntu)文档查看器打开pdf文件。
  2. 转到“文件”
  3. 转到“打印”
  4. 选择“打印为文件”并勾选“pdf”标记
如果您想使该过程自动化,请按照this进行操作,即使用此脚本自动打印所有pdf文件。像这样的Linux脚本也可以工作:
for f in *.pdfx
do
lowriter --headless --convert-to pdf "$f"
done

注意,我将原始(有问题的)PDF文件称为pdfx。

0

我也遇到了这个错误,并且一直尝试使用以下代码:

fp = open('example','rb')

然而,我仍然得到了 OP 指出的错误。后来我发现我的代码中有一个 bug,即 PDF 文件还被另一个函数打开了。
因此,请确保在内存中没有其他地方打开了该 PDF。


-1

上面的答案是正确的。这个错误只出现在Windows中,解决方法是将 with open(path, 'rb') 替换为 fp = open(path,'rb')


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接