Requests: Transmisión de respuestas comprimidas con gzip

Creado en 31 jul. 2014  ·  10Comentarios  ·  Fuente: psf/requests

Necesito procesar grandes respuestas XML como una secuencia. Las respuestas sin comprimir pueden tener un tamaño de varios cientos de megabytes, por lo que cargarlas por completo en la memoria antes de entregarlas al analizador XML no es una opción.

Estoy usando lxml para analizar y solo entrego el response.raw a su función iterparse() , como se describe en algún lugar de los documentos de solicitudes. Esto funciona bien para respuestas sin comprimir.

Desafortunadamente, la API a la que estoy llamando no es particularmente buena. Por lo tanto, a veces devolverá Content-Encoding: gzip incluso si solicito explícitamente datos sin comprimir. Además, la relación de compresión en estos archivos XML extremadamente repetitivos y detallados es realmente buena (10x +), por lo que realmente me gustaría hacer uso de respuestas comprimidas.

¿Es esto posible con solicitudes? No pude encontrarlo en la documentación. Investigando más profundamente en urllib3, su método HTTPResponse.read () parece admitir un parámetro decode_content . Si no se establece, urllib3 recurre a lo que está configurado en el constructor. Cuando las solicitudes llaman al constructor en request.adapters.HTTPAdapter.send () , establece explícitamente decode_content en False.

¿Hay alguna razón por la que las solicitudes hacen eso?

Curiosamente, iter_content() realidad establece decode_content=True mientras lee. ¿Por qué aquí? Todo parece un poco arbitrario. Realmente no entiendo la motivación para hacerlo de una manera aquí y de otra allí.
Personalmente, no puedo usar iter_content() por supuesto porque necesito un objeto similar a un archivo para lxml.

Anteriormente escribí mi propio objeto similar a un archivo que puedo conectar entre las solicitudes y lxml, pero, por supuesto, el almacenamiento en búfer es difícil y siento que personas más inteligentes que yo han escrito esto antes, así que prefiero no tener que rodar el mío. .

¿Cuál es tu consejo sobre cómo manejar esto? ¿Deberían cambiarse las solicitudes a las predeterminadas para configurar decode_content=True en urllib3?

Contributor Friendly Documentation Planned

Comentario más útil

He hecho esto en el pasado

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

Todos 10 comentarios

No, no debería configurarlo de forma predeterminada por una amplia variedad de razones. Lo que debe hacer es usar functools.partial para reemplazar el método read en la respuesta (o simplemente ajustarlo de otra manera) para que haga algo como:

response.raw.read = functools.partial(response.raw.read, decode_content=True)

y luego pase response.raw a su analizador.

@ sigmavirus24 ¡ Gracias, definitivamente es una solución elegante al problema que describí anteriormente!

Recomendaría agregar eso a la documentación de las solicitudes, por ejemplo, en las preguntas frecuentes: http://docs.python-requests.org/en/latest/community/faq/#encoded -data
Actualmente, la declaración "Las solicitudes descomprime automáticamente las respuestas codificadas en gzip" no es correcta para el caso stream=True y puede dar lugar a sorpresas.

En cuanto a mi problema, como has leído sobre el problema de urllib3 , la implementación de urllib3 de la descompresión gzip tiene sus propias pequeñas peculiaridades que tengo que solucionar en mi código, pero eso ya no es un problema para las solicitudes.

pero eso ya no es un problema para las solicitudes.

¿Sientes que esto se puede cerrar?

@ sigmavirus24 Creo que debería estar documentado, ya que la documentación actual es incorrecta.

Pero si no está de acuerdo con eso, sí, ¡acérquese!

La documentación podría ser más clara. Para mí (y esto se debe enteramente a que soy un desarrollador central) el primer párrafo habla al 90% de los usuarios que nunca tocarán la respuesta sin procesar, mientras que el segundo párrafo contradice al primero al decir "pero si necesitas acceder al datos brutos, está ahí para usted ". Como dije, eso es evidente para mí, pero puedo ver cómo se podría aclarar. Trabajaré en eso esta noche.

Para mí, es más que habría interpretado "datos sin procesar" como "carga útil sin procesar", es decir, un flujo descomprimido. Solo tengo que leerlo en los fragmentos que necesite. A diferencia de .content , que es un blob descomprimido (también la carga útil, pero en una forma diferente).

La descompresión real me parece una preocupación de la biblioteca HTTP, un detalle de implementación de HTTP, por así decirlo, uno que esperaría que las solicitudes se abstraigan. Ya sea que lea la carga útil de las solicitudes como un flujo o como una masa de datos precargada, no marcaría la diferencia. De cualquier manera, las solicitudes abstraerían la 'compresión' del detalle de implementación.

(Esta suposición también estaba en el núcleo de mi solicitud original de decode_content por defecto a True . Por supuesto, ahora que veo qué abstracción con fugas es esta, ya no estoy sugiriendo eso).

Pero sí, estoy absolutamente de acuerdo en que el 99% de sus usuarios nunca se verán afectados por este detalle.

No dude en cerrar este problema.

Entonces, esto en realidad conduce a algo que ha estado dando vueltas en mi cabeza durante un tiempo y que aún no he propuesto porque sería un cambio significativo en la API.

No me gusta el hecho de que sugerimos que la gente use r.raw porque es un objeto que no documentamos y es un objeto proporcionado por urllib3 (que hemos afirmado en el pasado es más un detalle de implementación). Con eso en mente, he estado jugando con la idea de proporcionar métodos en un objeto Response que solo sea proxy de los métodos urllib3 ( read simplemente proxy de raw.read , etc.). Esto nos da una flexibilidad adicional alrededor de urllib3 y nos permite manejar (en nombre de los usuarios) un cambio de API en urllib3 (que históricamente casi nunca ha sido un problema, por lo que no hay ningún urgencia en eso).

Dicho esto, ya tenemos suficientes métodos en un objeto Response en mi opinión y hacer crecer nuestra API no es ideal. La mejor API es la API de la que no queda nada por eliminar. Así que estoy continuamente indeciso sobre esto.


Esta suposición también estaba en el centro de mi solicitud original de decode_content predeterminado en True. Por supuesto, ahora que veo qué abstracción con fugas es esta, ya no estoy sugiriendo eso.

Para otros que encuentren esto y no estén seguros de por qué esto es cierto, permítanme explicarles.

Hay varios usuarios de solicitudes que desactivan la descompresión automática para validar la longitud de una respuesta o para hacer otras cosas importantes con ella. Un consumidor del primer tipo es OpenStack. Muchos de los clientes de OpenStack validan el encabezado Content-Length enviado al cliente y la longitud real del cuerpo recibido. Para ellos, manejar la descompresión es una compensación justa para asegurarse de que reciben y manejan una respuesta válida.

Otro consumidor es Betamax (o realmente cualquier herramienta que (re) construya objetos Response) porque cuando está manejando el proceso completo de hacer una respuesta totalmente válida, necesita que el contenido esté en formato comprimido.

Estoy seguro de que hay otros que ni @Lukasa ni yo sabemos que también dependen en gran medida de este comportamiento.

Golpeé el mismo problema hoy y terminé asumiendo lo mismo, ya que no hay otra forma de transmitir respuestas en este momento.

En lugar de múltiples métodos nuevos en Response, ¿por qué no un solo atributo nuevo, por ejemplo, response.stream que desempeñaría el mismo papel de proxy que .raw ? También reflejaría muy bien la configuración / parámetro stream=True , y no afectaría a los usuarios que necesiten el comportamiento actual de .raw .

He hecho esto en el pasado

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

Tenga en cuenta que la solución alternativa de @ sigmavirus24 rompe la semántica del método tell , que devolverá compensaciones incorrectas.

Me encontré con esto cuando transmití una respuesta como una carga reanudable en la API de Google Cloud Storage, que usa tell() para calcular la cantidad de bytes que se acaban de leer ( aquí ).

¿Fue útil esta página
0 / 5 - 0 calificaciones