Biblioteca de Python para obtener el nombre de archivo del contenido descargado con HTTP

Descargo un archivo usando la getfunción de la biblioteca de Python requests. Para almacenar el archivo, me gustaría determinar el nombre de archivo de la forma en que lo haría un navegador web para su cuadro de diálogo 'guardar' o 'guardar como ...'.

Fácil, ¿verdad? Puedo obtenerlo delContent-Disposition encabezado HTTP, accesible en el objeto de respuesta:

import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)

Pero mirando más de cerca este tema, no es tan fácil:

De acuerdo con RFC 6266 sección 4.3 , y la gramática en la sección 4.1 , el valor puede ser un token sin comillas (p. ej. the_report.pdf) o una cadena entrecomillada que también puede contener espacios en blanco (p. ej. "the report.pdf") y secuencias de escape (sin embargo, se desaconsejan estas últimas, por lo que su el manejo no es un requisito difícil para mí). Más lejos,

cuando tanto "nombre de archivo" como "nombre de archivo*" están presentes en un solo valor de campo de encabezado, [nosotros] DEBEMOS elegir "nombre de archivo*" e ignorar "nombre de archivo".

Sin embargo, el valor de filename*, es todavía un poco más complicado que el de filename.

Además, el RFC parece permitir espacios en blanco adicionales alrededor del archivo =.

Por lo tanto, para los ejemplos enumerados en el RFC , me gustaría obtener los siguientes resultados:

- Contenido-Disposición: Anexo; filename=example.html nombre de archivo:example.html

    Content-Disposition: INLINE; FILENAME= "an example.html"

Nombre del archivo:an example.html

    Content-Disposition: attachment;
                         filename*= UTF-8''%e2%82%ac%20rates

Nombre del archivo:€ rates

    Content-Disposition: attachment;
                         filename="EURO rates";
                         filename*=utf-8''%e2%82%ac%20rates

nombre de archivo: € ratesaquí también (no EURO rates, ya que filename*tiene prioridad)

Podría implementar el análisis del Content-Dispositionencabezado que obtengo requestsen consecuencia, pero si puedo evitarlo y usar una implementación probada existente, lo preferiría.

¿Hay una biblioteca de Python que pueda hacer esto?

Requisitos

La biblioteca tendría que

  • proporcionar una función que extraiga y devuelva el nombre de archivo adecuado (si lo hay) de una requestsrespuesta pasada
    o
  • proporcionar una función que extraiga y devuelva el nombre de archivo adecuado (si lo hay) de un Content-Dispositionvalor de campo de encabezado pasado (una cadena)
    o
  • proporcione una función que acepte todos los mismos parámetros que requests.getrealiza la solicitud y devuelva la respuesta, así como el nombre del archivo (si lo hay)
    o
  • proporciona algo igualmente práctico

no requisitos

Lo que no tiene que manejar (pero si lo hace, aún mejor) ya que puedo hacerlo yo mismo:

  • desinfecte los valores para que no contengan nombres de directorio u otros elementos de ruta, excepto un solo nombre de archivo, por lo que almacenar con ese nombre no hará que se creen o sobrescriban archivos en ubicaciones arbitrarias

  • producir extensiones de nombre de archivo "guardar" "que coincidan de manera óptima con el tipo de medio de la carga útil recibida" (consulte la sección 4.3 )

  • desinfecte los nombres de archivo para evitar la confusión del usuario ( la sección 4.3 menciona la sustitución de "caracteres de control y espacios en blanco iniciales y finales")

  • proporcionar un respaldo

    • para cuando ni el filenameni el filename*parámetro de disposición están presentes o
    • porque cuando los que están presentes no pueden ser analizados o
    • para cuando Content-Dispositionfalta el encabezado completo

    Aunque debería informar eso constantemente (ya sea subiendo o regresando Noneo ''), para que pueda dejar que mi propio retroceso se active.

Creo que sería una buena idea preguntar en StackOverflow y no en SoftwareRecommendation.
@YoshiBotX a pesar de que estoy pidiendo explícitamente una recomendación de biblioteca. Esos están fuera de tema en Stack Overflow.
Su pregunta es lo suficientemente específica "cómo determinar el nombre del archivo..." que lo intentaría allí.

Respuestas (1)

Echa un vistazo a rfc6266 . Parece hacer todo lo que quieras. Está licenciado bajo la LGPL 3.0. Es posible que la bifurcación principal no sea súper activa, y algunas otras bifurcaciones pueden tener más ventajas.

Aquí está el flaco:

>>> from rfc6266 import *

>>> parse_headers('Attachment; filename=example.html', relaxed=True)
ContentDisposition(u'Attachment', {u'filename': u'example.html'}, None)

>>> parse_headers('INLINE; FILENAME= "an example.html"', relaxed=True)
ContentDisposition(u'INLINE', {u'filename': u'an example.html'}, None)

>>> h='''attachment;
...                      filename*= UTF-8''%e2%82%ac%20rates'''
>>> parse_headers(h, relaxed=True)
ContentDisposition(u'attachment', {u'filename*': LangTagged(string=u'\u20ac rates', langtag=None)}, None)

>>> h='''attachment;
...                      filename="EURO rates";
...                      filename*=utf-8''%e2%82%ac%20rates'''
>>> parse_headers(h, relaxed=True)
ContentDisposition(u'attachment', {u'filename*': LangTagged(string=u'\u20ac rates', langtag=None), u'filename': u'EURO rates'}, None)
@ das-g Soy un imbécil, no había visto que hubieras publicado eso en SO y que se respondiera allí ... Nota personal para 2017: ¡lee los comentarios antes de responder!
Eh, tiene sentido tener esta respuesta en ambos lugares. ( Aquí está el equivalente en la pregunta cruzada).