struct.error: для распаковки требуется строковый аргумент длиной 16

При обработке PDF-файла файла (2.pdf) с помощью pdfminer (pdf2txt.py) я получил следующую ошибку :

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?


Обновление: я получаю аналогичную ошибку с BytesIO вместо StringIO после установка pdfminer прямо из репозитория GitHub.

    $ 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

person Daniil Mashkin    schedule 20.10.2016    source источник
comment
Я подозреваю, что вы достигли конца файла раньше, чем анализатор ожидал этого из-за ошибки. Попробуйте вместо этого запустить dumppdf.py и посмотрите, есть ли явно неверные данные непосредственно перед этой ошибкой.   -  person Peter Brittain    schedule 25.10.2016
comment
это то, что я получаю:   -  person Daniil Mashkin    schedule 26.10.2016
comment
Я думаю, вы также хотите использовать опцию -a...   -  person Peter Brittain    schedule 26.10.2016
comment
Итак... можете ли вы объяснить, что я могу сделать с этим дампом?   -  person Daniil Mashkin    schedule 26.10.2016
comment
Глядя на трассировку стека, вы можете видеть, что он умер при обработке шрифта. В дампе всего 2 такого типа и присутствуют оба потока, используемые этими шрифтами, так что не очевидно, что не так.   -  person Peter Brittain    schedule 26.10.2016
comment
Тем не менее, ваш код ссылается на StringIO, что означает, что ему не менее 2 лет... Вы пробовали обновлять?   -  person Peter Brittain    schedule 26.10.2016
comment
Вы имеете в виду инструмент обновления pdf2txt.py? Я попытался установить pdfminer непосредственно из репозитория github, но получил ту же ошибку.   -  person Daniil Mashkin    schedule 27.10.2016
comment
Включала ли трассировка стека ссылку на StringIO? Если это так, ваша установка не удалась...   -  person Peter Brittain    schedule 27.10.2016
comment
извините, вы правы. Я получаю аналогичную ошибку с BytesIO вместо StringIO. Я обновил свой вопрос.   -  person Daniil Mashkin    schedule 27.10.2016
comment
Хорошо - я думаю, что нашел ошибку. Я предлагаю вам связать мой ответ с этой проблемой: github.com/euske/pdfminer/issues/144< /а>   -  person Peter Brittain    schedule 29.10.2016
comment
Файл 1.pdf содержит только недопустимый объект /Info (номер 5), который, к счастью, не используется pdfminer, поэтому нет проблем.   -  person hynekcer    schedule 30.10.2016


Ответы (6)


TL; DR

Спасибо @mkl и @hynecker за дополнительную информацию... Тем самым я могу подтвердить, что это ошибка в pdfminer и вашем PDF. Всякий раз, когда pdfminer пытается получить встроенные файловые потоки (например, определения шрифтов), он выбирает последний в файле перед endobj. К сожалению, не все PDF-файлы строго добавляют конечный тег, поэтому pdfminer должен быть устойчив к этому.

Быстрое решение этой проблемы

Я создал патч, который был отправлен в виде запроса на включение на github. См. https://github.com/euske/pdfminer/pull/159.

Подробная диагностика

Как упоминалось в других ответах, причина, по которой вы видите это, заключается в том, что вы не получаете ожидаемое количество байтов из потока, поскольку pdfminer распаковывает данные. Но почему?

Как вы можете видеть в трассировке стека, pdfminer (правильно) определяет, что у него есть шрифт CID для обработки. Затем он обрабатывает встроенный файл шрифта как шрифт TrueType (в pdffont.py). Он пытается проанализировать связанный поток (идентификатор потока 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? Что ж, тот факт, что другие читатели могут с этим справиться, вызвал у меня подозрения.

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

Углубившись в код, я вижу, что все потоки получают одни и те же данные. Ой! Это ошибка. Причина, по-видимому, связана с тем, что в этом PDF-файле отсутствуют некоторые конечные теги, как отметил @hynecker.

Исправление состоит в том, чтобы возвращать правильные данные для каждого потока. Любое другое исправление, просто проглатывающее ошибку, приведет к использованию неверных данных для всех потоков и, таким образом, к неправильным определениям шрифтов.

Я считаю, что прилагаемый патч решит вашу проблему и должен быть безопасным для использования в целом.

person Peter Brittain    schedule 27.10.2016
comment
Вы уверены в своем анализе PDF? Я проверил 2.pdf с помощью Adobe Acrobat Preflight, в частности объект 18, и он выглядит как этот, т. е. в частности содержимое явно похоже на файл шрифта. Используя предварительную проверку для проверки синтаксических ошибок PDF, он просто предупреждает об отсутствующих элементах FontName... - person mkl; 29.10.2016
comment
Интересно... Я использовал dumppdf. Может быть, в его обработке потока есть ошибка, которая также влияет на pdfminer? - person Peter Brittain; 29.10.2016
comment
@mkl ОК, так что копаясь в анализе потока, я вижу, что он всегда возвращает последний поток, независимо от того, какой идентификатор был запрошен. Это ошибка. Я покопаюсь еще немного и обновлю свой ответ... - person Peter Brittain; 29.10.2016
comment
Ах, радость от охоты на жуков... ;) - person mkl; 29.10.2016
comment
Да, это достаточно просто и правильно, если сразу после отсутствующего endobj следует новый косвенный объект. Это может быть немного проблематично быть принятым в качестве исправления для pdfminer, если позже потребуется более общее решение, но для текущего состояния pdfminer это кажется достаточно хорошим. Я добавил в патч слово сломано, на случай, если его потом придется рефакторить. - person hynekcer; 31.10.2016
comment
@PeterBrittain, вы можете сделать запрос на включение в репозиторий pdfminer? - person Daniil Mashkin; 31.10.2016

Я исправил вашу проблему в исходном коде и попробую ваш файл 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

Пояснения

@Набил Ахмед был прав

Для строки foramt ›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 прямо сейчас.

person Kruupös    schedule 27.10.2016

Это действительно недействительный PDF-файл, поскольку отсутствуют некоторые ключевые слова endobj после трех косвенных объектов. (объект 5, 18 и 22)

Определение косвенного объекта в файле PDF должно состоять из номера его объекта и номера поколения (разделенных пробелом), за которыми следует значение объекта, заключенное в скобки между ключевыми словами obj и endobj. (глава 7.3.10 в справочнике в формате PDF)

Пример 2.pdf представляет собой простую версию PDF 1.3, в которой используются простые несжатые перекрестные ссылки и несжатые разделители объектов. Сбой можно легко найти с помощью команды grep и обычной программы просмотра файлов, которая содержит 22 косвенных объекта PDF. Образец " 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 не имеет endobj перед объектом 1, а объект 22 не имеет endobj перед объектом 21.

Известно, что неработающие перекрестные ссылки в PDF могут и должны обычно восстанавливаться с помощью ключевых слов obj/endobj (см. справочник по PDF, глава C.2). нет письменного совета.

person hynekcer    schedule 30.10.2016
comment
Хороший улов. Так что есть проблемы как в pdfminer, так и в pdf... ;) - person mkl; 30.10.2016
comment
@mkl Считаете ли вы, что из документации можно процитировать правило, объясняющее, что pdfminer не соответствует требованиям для чтения в соответствии со ссылкой на PDF? Я хотел бы написать патч, но знаю, что некоторые странные реализации правильны и не должны исправляться. Я вижу обсуждение с Леонардом Розентолом, архитектором стандартов PDF в Adobe - ... пока объект, на который он указывает, действителен, он может находиться где угодно — даже в середине несжатого потока - person hynekcer; 31.10.2016
comment
@hynekcer Я не думаю, что нам нужно прибегать к внешним ссылкам. pdfminer фактически выполняет синтаксический анализ на (косвенном) уровне объекта, поэтому ему нужно знать только, когда он заканчивается. Поскольку (я считаю) косвенные объекты не могут быть вложенными, вы можете обнаружить и использовать следующий тег obj как неявный endobj. Кодирование сработало для меня с этими двумя файлами. - person Peter Brittain; 31.10.2016
comment
Можете прислать ссылку на рабочую ветку, чтобы проверить? Я могу найти несколько контрпримеров для тестов, пока все в порядке. Я думаю создать несколько условных разрывов, чтобы завершить основной цикл while not self.results: в psparser.PSStackParser.nextobject(). Косвенный объект может содержать косвенные объекты, и это не проблема, поскольку ссылка откладывается до тех пор, пока вы не вызовете метод resolve() косвенного объекта. Возможности any_object [ comment | whitespace ]* [ stream .. endstream ] [ comment | whitespace ]* { endobj | other_token }. Другой токен обозначает потерянный эндообъект. - person hynekcer; 31.10.2016
comment
@hynekcer Не будет ли косвенный объект содержать ссылку на другой косвенный объект? В каком случае использование тега obj будет правильным, поскольку это будет просто еще одна запись в последовательности в теле PDF (а не вложенная)? В любом случае, код вставлен в мой ответ: stackoverflow.com/a/40295837/4994021 - person Peter Brittain; 31.10.2016
comment
Прежде всего, соответствующий процессор PDF должен использовать информацию из перекрестных ссылок. В частности, если есть несколько объектов с одним и тем же объектом и номером поколения, правильный не обязательно будет последним в файле (хотя обычно это так). Кроме того, даже если существует только одно вхождение объекта с данным объектом и номером поколения, этот объект следует игнорировать, если он отсутствует в перекрестных ссылках или если он помечен как удаленный. И есть некоторые крайние случаи, которые неоднозначны без ссылок... - person mkl; 31.10.2016
comment
@mkl Извините, я не ясно выразился. Конечно, pdfminer использует перекрестные ссылки. Однако при необходимости он также извлекает косвенные объекты из файла. Проблема заключается в последней обработке. Он просто не распознал конец объекта и поэтому вытащил больше данных, чем нужно (и затем из-за этого использовал неправильный поток). - person Peter Brittain; 31.10.2016
comment
Ах хорошо. В таком случае, как насчет использования записи словаря потока длин? - person mkl; 31.10.2016
comment
@mkl Длина потока ясна. Проблем с длиной потока никогда не было. Между ключевыми словами obj и endobj находятся два объекта любого типа и дополнительные два целочисленных объекта и ключевое слово obj. Структура PDF основана, помимо прочего, на Postscript. Операнды в Postscript помещаются в стек. Затем должен появиться оператор (например, endobj), который потребляет данные. Поведение pdfminer было логичным, поскольку использовался последний объект в стеке, но мы согласны с тем, что endobj не следует использовать как простой оператор Postscript в процессоре PS. (Я принимаю решение Питера Британии. Мир... - person hynekcer; 01.11.2016
comment
... мир не идеален.) - person hynekcer; 01.11.2016

Последнее сообщение об ошибке говорит вам о многом:

Файл /usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py, строка 375, в

init (имя, tsum, смещение, длина) = struct.unpack('›4sLLL', fp.read(16)) struct.error: для распаковки требуется строковый аргумент длиной 16

Вы можете легко отладить происходящее, например, поместив необходимые операторы отладки точно в pdffont.py. Я предполагаю, что в вашем pdf-содержимом есть что-то особенное. Судя по названию метода - TrueTypeFont - который выдает сообщение об ошибке, есть какая-то несовместимость с типом шрифта.

person Jacobian    schedule 25.10.2016

Давайте начнем с объяснения оператора, в котором вы получаете исключение:

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

где синопсис:

struct.unpack(fmt, buffer)

Метод unpack распаковывает из буфера buffer (который предположительно ранее упакованный pack(fmt, ...)) согласно строка формата< /strong> 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

Причина - структура формата ('BH') ожидает 4 байта, т.е. когда мы что-то упаковываем в формате 'BH', это будет занимать 4 байта памяти. Хорошее объяснение здесь .


Чтобы прояснить это дальше, давайте посмотрим на строку формата >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.


Может быть ошибкой — согласно этому комментарию:

Это ошибка в вышестоящем PDFminer от @euske. Кажется, для этого есть исправления, поэтому это должно быть легко исправить. Помимо этого, мне также нужно усилить синтаксический анализ pdf, чтобы мы никогда не ошибались из-за неудачного синтаксического анализа.

Я отредактирую вопрос, если найду что-то полезное, чтобы добавить сюда - решение или патч.

person Nabeel Ahmed    schedule 25.10.2016

Если вы по-прежнему получаете некоторые структурные ошибки после применения патча Питера, особенно при анализе большого количества файлов в одном запуске скрипта (с использованием os.listdir), попробуйте изменить кеширование диспетчера ресурсов на false.

rsrcmgr = PDFResourceManager(caching=False)

Это помогло мне избавиться от остальных ошибок после применения вышеуказанных решений.

person murnko    schedule 03.11.2016
comment
Это сработало для меня! Спасибо - person technophile_3; 30.06.2021