2016-10-20 5 views
6

При обработке PDF file (2.pdf) с pdfminer (pdf2txt.py) Я получил следующее сообщение об ошибке:struct.error: распаковка требует строкового аргумента длины 16

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) не вызывает проблемы ,

Не удается найти информацию об ошибке. Я добавил issue в репозиторий pdfminer GitHub, но он остался без ответа. Может кто-нибудь объяснить мне, почему это происходит? Что я могу сделать, чтобы разобрать 2.pdf?


Update: Я получаю подобную ошибку с BytesIO вместо StringIO после installing 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 
+0

Я подозреваю, что вы попали в конец файла, прежде чем парсер ожидал его из-за ошибки. Попробуйте запустить [dumppdf.py] (https://euske.github.io/pdfminer/#dumppdf) и посмотреть, есть ли плохие данные перед этой ошибкой. –

+0

это то, что я получаю https://gist.github.com/danmash/a8b42f72787ca0c329a0b2c2ae6aeea3 –

+0

Я думаю, вы также хотите использовать опцию '-a' тоже ... –

ответ

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, который является cmap для поля ToUnicode. Быстрый взгляд на PDF spec показывает, что они не могут быть одинаковыми.

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

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

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

+0

Уверены ли вы в своем анализе PDF? Я проверил 2.pdf, используя Adobe Acrobat Preflight, в частности объект 18, и он выглядит как [this] (https://i.stack.imgur.com/rvaLQ.png), то есть, в частности, содержимое явно выглядит как файл шрифта. Используя Preflight для проверки ошибок синтаксиса PDF, он просто предупреждает о недостатках ** записей в FontName ** ... – mkl

+0

Интересно ... Я использовал dumppdf. Может быть, есть ошибка в обработке потока, которая также влияет на файл pdfminer? –

+0

@mkl ОК - поэтому, копаясь в синтаксическом анализе потока, я вижу, что он всегда возвращает последний поток, независимо от того, какой идентификатор был запрошен. Это ошибка. Я буду копать еще немного и обновить свой ответ ... –

2

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

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

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

2

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

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

где конспект является:

struct.unpack(fmt, buffer)

The method unpack , unpacks from the buffer buffer (which presumably earlier packed by pack(fmt, ...)) according to the format string fmt . The result is a tuple even if it contains exactly one item. The buffer’s size in bytes must match the size required by the format, as reflected by calcsize().

Наиболее распространенный случай, неправильное число байты (16) для используемого формата (>4sLLL) - например, для формата, ожидающего 4 байта, вы указали 3 байт s:

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

для этого вы получите

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

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


Чтобы уточнить его дальше - давайте смотреть в строку формата >4sLLL. Чтобы проверить размер unpack 'd ожидать буфера (байты, которые вы читаете из файла PDF). Цитируя документы:

The buffer’s size in bytes must match the size required by the format, as reflected by calcsize().

>>> import struct 
>>> struct.calcsize('>4sLLL') 
16 
>>> 

В данный момент мы можем сказать, что нет ничего плохого с утверждением:

(name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16)) 

foramt строка >4sLLL требует 16 байт размера буфера, который правильно указанные в Fp.читать читать 16 байт за раз.

Таким образом, проблема может быть связана только с потоком буфера, который он читает, то есть содержимым вашего конкретного файла PDF.


Может быть ошибка - в соответствии с настоящим comment:

This is a bug in the upstream PDFminer by @euske There seems to be patches for this so it should be an easy fix. Beyond this I also need to strengthen the pdf parsing such that we never error out from a failed parse

Я буду править вопрос, я нахожу что-то полезное, чтобы добавить здесь - решение, или патч.

4

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

The foramt string >4sLLL requires 16 bytes size of buffer, which is specified correctly to fp.read to read 16 bytes at a time.

So, the problem can only be with the buffer stream it's reading i.e. the content of your specific PDF file.

В коде мы видим, что 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 прямо сейчас.

4

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

The definition of an indirect object in a PDF file shall consist of its object number and generation number (separated by white space), followed by the value of the object bracketed between the keywords obj and endobj. (chapter 7.3.10 in PDF reference)

Пример 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 не имеет endobj перед объектом 1 и объект 22 не имеет endobj перед объектом 21.

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

+0

Хороший улов. Итак, есть проблемы как в pdfminer, так и в pdf ...;) – mkl

+0

@mkl Как вы думаете, можно ли привести правило из документации, чтобы объяснить, что pdfminer не является «соответствующим читателем» в соответствии с ссылкой на PDF? Я хотел бы написать патч, но я знаю, что некоторые странные реализации верны и не должны быть исправлены. Я вижу [обсуждение] (https://feliam.wordpress.com/2010/08/14/pdf-a-broken-spec/#div-comment-122) с Leonard Rosenthol - архитектор стандартов PDF в Adobe - «... пока объект, на который он указывает, действителен, тогда он может быть где угодно - даже в середине несжатого потока» – hynekcer

+1

@hynekcer Я не думаю, что нам нужно отступить на внешние ссылки. pdfminer фактически разбирается на (косвенном) уровне объекта и поэтому только должен знать, когда он заканчивается. Поскольку (я считаю) косвенные объекты не могут быть вложенными, вы можете обнаружить и использовать следующий тег obj как неявный endobj. Кодирование, которое работало над этими двумя файлами для меня. –

0

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

rsrcmgr = PDFResourceManager(caching=False) 

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