struct.error: unpack requiere un argumento de cadena de longitud 16

Mientras procesaba un archivo PDF (2.pdf) con pdfminer (pdf2txt.py), recibí el siguiente error:

pdf2txt.py 2.pdf Traceback (most recent call last): File "/usr/local/bin/pdf2txt.py", line 115, in  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 

Mientras que el archivo similar (1.pdf) no causa un problema.

No puedo encontrar ninguna información sobre el error. Agregué un problema en el repository de pdfminer GitHub, pero permaneció sin respuesta. ¿Puede alguien explicarme por qué esto está sucediendo? ¿Qué puedo hacer para analizar 2.pdf ?


Actualización : Recibo un error similar con BytesIO lugar de StringIO después de instalar pdfminer directamente desde el repository de GitHub.

  $ pdf2txt.py 2.pdf Traceback (most recent call last): File "/home/danil/projects/python/pdfminer-source/env/bin/pdf2txt.py", line 116, in  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; DR

    Gracias a @mkl y @hynecker por la información adicional … Con eso puedo confirmar que hay un error en pdfminer y su PDF. Cada vez que pdfminer intenta obtener secuencias de archivos incrustadas (por ejemplo, definiciones de fonts), está recogiendo la última en el archivo antes de un endobj . Lamentablemente, no todos los PDF agregan rigurosamente la etiqueta final y, por lo tanto, pdfminer debe ser resistente a esto.

    Solución rápida para este problema

    He creado un parche, que se ha enviado como una solicitud de extracción en github. Consulte https://github.com/euske/pdfminer/pull/159 .

    Diagnóstico detallado

    Como se mencionó en las otras respuestas, la razón por la que está viendo esto es que no está obteniendo el número esperado de bytes de la secuencia, ya que pdfminer está desempaquetando los datos. ¿Pero por qué?

    Como puede ver en su seguimiento de stack, pdfminer (con razón) señala que tiene una fuente CID para procesar. Luego continúa procesando el archivo de fuente incrustado como una fuente TrueType (en pdffont.py ). Intenta analizar el flujo asociado (flujo ID 18) leyendo un conjunto de tablas binarias.

    Esto no funciona para 2.pdf porque tiene una secuencia de texto. Puede ver esto ejecutando dumppdf -b -i 18 2.pdf . He puesto el comienzo aquí:

     /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def ... 

    Entonces, basura dentro, basura fuera … ¿Esto es un error en su archivo o pdfminer? Bueno, el hecho de que otros lectores puedan manejarlo me hizo sospechar.

    Examinando un poco más, veo que esta secuencia es idéntica a la ID 17, que es el cmap para el campo ToUnicode . Un vistazo rápido a la especificación de PDF muestra que no pueden ser iguales.

    Al profundizar en el código, veo que todas las transmisiones obtienen los mismos datos. ¡Uy! Este es el error. La causa parece estar relacionada con el hecho de que a este PDF le faltan algunas tags finales, como lo indica @hynecker.

    La solución es devolver los datos correctos para cada flujo. Cualquier otra solución para simplemente tragar el error provocará que se utilicen datos incorrectos en todas las transmisiones y, por ejemplo, definiciones de fonts incorrectas.

    Creo que el parche adjunto solucionará su problema y debería ser seguro de usar en general.

    2.pdf su problema en el código fuente, y trato su archivo 2.pdf para asegurarme de que funcionó.

    En el archivo pdffont.py reemplacé:

     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 

    por esto:

     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 

    Explicaciones

    @Nabeel Ahmed tenía razón

    La cadena foramt> 4sLLL requiere un tamaño de búfer de 16 bytes, que se especifica correctamente en fp.read para leer 16 bytes a la vez.

    Por lo tanto, el problema solo puede ser con el flujo de búfer que está leyendo, es decir, el contenido de su archivo PDF específico.

    En el código vemos que fp.read(16) se realiza en un bucle sin ningún tipo de comprobación. Por lo tanto, no sabemos con seguridad si lo leyó todo con éxito. Por ejemplo, podría alcanzar un EOF .

    Para evitar este problema, simplemente salgo del bucle for cuando aparece este tipo de problema.

      for _ in xrange(ntables): fp_bytes = fp.read(16) if len(fp_bytes) < 16: break 

    En cualquier caso regular , no debería cambiar nada de todos modos.

    Trataré de hacer una solicitud de extracción en github, pero ni siquiera estoy seguro de que sea aceptado, así que te sugiero que hagas un parche de mono por ahora y modifiques tu /home/danil/projects/python/pdfminer-source/env/local/lib/python2.7/site-packages/pdfminer/pdffont.py ahora mismo.

    Esto es realmente un PDF no válido porque faltan algunas palabras clave endobj después de tres objetos indirectos . (objeto 5, 18 y 22)

    La definición de un objeto indirecto en un archivo PDF constará de su número de objeto y su número de generación (separados por espacios en blanco), seguido del valor del objeto entre corchetes entre las palabras clave obj y endobj . (Capítulo 7.3.10 en referencia PDF )

    El ejemplo 2.pdf es una versión simple de PDF 1.3 que utiliza una referencia cruzada simple sin comprimir y separadores de objetos sin comprimir. La falla se puede encontrar fácilmente con el comando grep y con un visor de archivos general que el PDF tiene 22 objetos indirectos. El patrón “obj” se encuentra correctamente exactamente 22 veces (nunca accidentalmente en un objeto de cadena o en una secuencia, afortunadamente por simplicidad), pero la palabra clave endobj falta tres veces.

     $ 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 ... 

    De manera similar, el objeto 5 no tiene endobj antes del objeto 1 y el objeto 22 no tiene endobj antes del objeto 21.

    Se sabe que las referencias cruzadas rotas en PDF pueden y deben ser generalmente reconstruidas por palabras clave obj / endobj (consulte la referencia en PDF, capítulo C.2). Algunas aplicaciones probablemente solucionan viceversa endobj faltante si las referencias cruzadas son correctas, pero No hay un consejo escrito.

    El último mensaje de error te dice mucho:

    Archivo “/usr/local/lib/python2.7/dist-packages/pdfminer/pdffont.py”, línea 375, en

    init (name, tsum, offset, length) = struct.unpack (‘> 4sLLL’, fp.read (16)) struct.error: unpack requiere un argumento de cadena de longitud 16

    Puede depurar fácilmente lo que está sucediendo, por ejemplo, colocando las instrucciones de depuración necesarias exactamente en el archivo pdffont.py . Mi conjetura es que hay algo especial en sus contenidos pdf. A juzgar por el nombre del método, TrueTypeFont , que arroja el mensaje de error, existe cierta incompatibilidad con el tipo de fuente.

    Comencemos con la explicación de la afirmación donde está obteniendo una excepción:

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

    donde la sinopsis es:

    struct.unpack(fmt, buffer)

    El método de unpack desempaqueta del búfer de buffer (que presumiblemente se empaquetó antes por pack(fmt, ...) ) de acuerdo con la cadena de formato fmt . El resultado es una tupla incluso si contiene exactamente un elemento. El tamaño del búfer en bytes debe coincidir con el tamaño requerido por el formato, como se refleja en calcsize ().

    El caso más común es el número incorrecto de bytes ( 16 ) para el formato utilizado ( >4sLLL ); por ejemplo, para un formato que espera 4 bytes, ha especificado 3 bytes:

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

    para esto obtendrás

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

    La razón: la estructura de formato (‘BH’) espera 4 bytes, es decir, cuando empaquetamos algo con el formato ‘BH’, ocupará 4 bytes de memoria. Una buena explicación aquí .


    Para aclararlo aún más, echemos un vistazo a la cadena de formato >4sLLL . Para verificar el tamaño, unpack estaría esperando el búfer (los bytes que está leyendo del archivo PDF). Cita de documentos:

    El tamaño del búfer en bytes debe coincidir con el tamaño requerido por el formato, como se refleja en calcsize ().

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

    Hasta este punto, podemos decir que no hay nada de malo en la statement:

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

    La cadena foramt >4sLLL requiere un tamaño de búfer de 16 bytes, que se especifica correctamente en fp.read para leer 16 bytes a la vez.

    Por lo tanto, el problema solo puede ser con el flujo de búfer que está leyendo, es decir, el contenido de su archivo PDF específico.


    Puede ser un error – según este comentario :

    Esto es un error en el PDFminer ascendente por @euske Parece que hay parches para esto, así que debería ser una solución fácil. Más allá de esto, también necesito reforzar el análisis de PDF de tal forma que nunca salgamos por error de un análisis fallido.

    Editaré la pregunta si encuentro algo útil para agregar aquí: una solución o un parche.

    En caso de que aún tenga algunos errores de estructura después de aplicar el parche de Peter, especialmente al analizar muchos archivos en la ejecución de un script (usando os.listdir), intente cambiar el almacenamiento en caché del administrador de recursos a falso.

     rsrcmgr = PDFResourceManager(caching=False) 

    Me ayudó a deshacerme del rest de errores después de aplicar las soluciones anteriores.