Разбор PDF без объекта /Root с помощью PDFMiner

Я пытаюсь извлечь текст из большого количества PDF-файлов, используя привязки Python PDFMiner. Модуль, который я написал, работает для многих 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. Казалось бы, это говорит об обратном, но я очень невежественен. Есть предположения?


person Louis Thibault    schedule 08.07.2012    source источник
comment
возможно, PDFMiner прекращает поиск корневого узла после первой метки %%EOF, но после этой метки может появиться больше узлов, поэтому он не находит его. Другая причина может заключаться в том, что файлы сжаты?   -  person Quonux    schedule 08.07.2012
comment
@Quonux, и как мне проверить, так это или нет? Есть ли возможность заставить PDFMiner искать корневой узел во всем документе? Что касается возможности сжатия, есть ли способ проверить это? Что делать, если файлы сжаты?   -  person Louis Thibault    schedule 08.07.2012
comment
@Quonux, я добавил трассировку стека из аналогичной попытки с использованием pypdf. Помогает ли это сузить причину?   -  person Louis Thibault    schedule 08.07.2012
comment
Возможно, синтаксический анализатор ожидает метку %%EOF, но ничего не найдено... возможно, вы можете исправить это с помощью: - открыть неправильный файл - записать/добавить в двоичном режиме %%EOF\n в конец файла - закрыть его - попробуйте еще раз разобрать   -  person Quonux    schedule 08.07.2012


Ответы (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

Объекты могут быть как прямыми (встроенными в другой объект), так и косвенными. Косвенные объекты нумеруются номером объекта и номером поколения. Индексная таблица, называемая таблицей внешних ссылок, дает смещение в байтах каждого косвенного объекта от начала файла. Такой дизайн обеспечивает эффективный произвольный доступ к объектам в файле, а также позволяет вносить небольшие изменения без перезаписи всего файла (добавочное обновление). Начиная с PDF версии 1.5, непрямые объекты также могут располагаться в специальных потоках, известных как потоки объектов. Этот метод позволяет уменьшить размер файлов с большим количеством мелких косвенных объектов и особенно полезен для PDF с тегами.

Я думаю, проблема в том, что ваш «поврежденный pdf» имеет несколько «корневых элементов» на странице.

Possible solution:

вы можете скачать исходники и написать `функции печати' в каждом месте, где извлекаются объекты xref и где синтаксический анализатор пытается проанализировать эти объекты. можно будет определить полный стек ошибки (до появления этой ошибки).

PS: я думаю, что это какая-то ошибка в продукте.

person Dmitry Zagorulkin    schedule 11.07.2012
comment
Дмитрий, спасибо за ответ. Если я правильно понял, вы подозреваете, что это баг PDFMiner? Я удивлен, потому что подобное поведение наблюдается и в PyPDF. Или вы имели в виду, что ошибка заключается в том, какое программное обеспечение создало поврежденный PDF-файл? Что касается вашего решения, вы имеете в виду, что я должен добавлять строки печати в методы объекта PDFParser, когда бы они ни управляли объектом внешней ссылки? Я немного не понимаю, что именно я должен делать. Спасибо! - person Louis Thibault; 12.07.2012
comment
Добрый день. Просто возьмите два файла (обычный и поврежденный) и попробуйте проанализировать каждый в инструменте анализатора pdf. я думаю, что в поврежденном pdf будет недопустимая структура xref. После анализа попробуйте восстановить структуру pdf(w3.org/WAI/GL/ WCAG20-TECHS/pdf.html) - person Dmitry Zagorulkin; 12.07.2012
comment
pdflabs.com/tools/pdftk-the-pdf-toolkit отличный набор инструментов для выполнения подобного рода действий. - person Dmitry Zagorulkin; 12.07.2012
comment
Дмитрий, спасибо за ссылки. Я попробую pdftk. Зачем что-то исправлять, если работа уже сделана ;-) - person Louis Thibault; 12.07.2012
comment
дайте мне отзыв, если вы сделаете инструмент для восстановления PDF в python =) - person Dmitry Zagorulkin; 12.07.2012

Решение в slate pdf — использовать «rb» -> читать двоичный режим.

Поскольку slate pdf зависит от PDFMiner, и у меня та же проблема, это должно решить вашу проблему.

fp = open('C:\Users\USER\workspace\slate_minner\document1.pdf','rb')
doc = slate.PDF(fp)
print doc
person Carlos Neves    schedule 15.08.2013
comment
Я попробовал этот вариант и получил точно такой же код ошибки: No /Root object! - Is this really a PDF? - person elPastor; 19.07.2017

У меня была такая же проблема в Ubuntu. У меня есть очень простое решение. Просто распечатайте pdf-файл как pdf. Если вы находитесь в Ubuntu:

  1. Откройте файл PDF с помощью средства просмотра документов (ubuntu).

  2. Перейти к файлу

  3. Перейти к печати

  4. Выберите печать как файл и установите флажок «pdf».

Если вы хотите сделать процесс автоматическим, выполните, например, this, то есть используйте этот скрипт для автоматической печати всех ваших pdf-файлов. Подобный linux-скрипт также работает:

for f in *.pdfx
do
lowriter --headless --convert-to pdf "$f"
done

Примечание. Я назвал исходные (проблемные) pdf-файлы pdfx.

person DanielTheRocketMan    schedule 13.12.2018

Ответ выше правильный. Эта ошибка появляется только в окнах, и обходным путем является замена with open(path, 'rb') на fp = open(path,'rb')

person AeroSM    schedule 07.02.2018

Я тоже получил эту ошибку и продолжал пытаться fp = open('example','rb')

Тем не менее, я все еще получил указанную ошибку OP. Я обнаружил, что в моем коде была ошибка, из-за которой PDF-файл все еще был открыт другой функцией.
Поэтому убедитесь, что PDF-файл не открыт в памяти в другом месте.

person dasvootz    schedule 03.04.2018