Requests: Response.text devuelve texto descodificado incorrectamente (solicitudes 1.2.3, python 2.7)

Creado en 15 sept. 2013  ·  24Comentarios  ·  Fuente: psf/requests

Si el servidor http devuelve Content-type: text / * sin codificación, Response.text siempre lo decodifica como texto 'ISO-8859-1'.

Puede ser válido en RFC2616 / 3.7.1, pero esto es incorrecto en la vida real en 2013.

Hice una página de ejemplo con texto chino:
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
Todos los navegadores muestran esta página correctamente.
Pero reguests.get devuelve texto no válido.

Y aquí hay una prueba simple con esa URL:
https://gist.github.com/lavr/6572927

Comentario más útil

@lavr (/ cc @ sigmavirus24), incluso más fácil que eso, simplemente puede proporcionar la codificación usted mismo.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

Luego, proceda normalmente.

Todos 24 comentarios

¡Gracias por esto @lavr!

Esta es una decisión de diseño deliberada para las solicitudes. Seguimos la especificación a menos que nos encontremos en una posición en la que la especificación difiera tanto del comportamiento del mundo real que se convierta en un problema (por ejemplo, GET después de una respuesta 302 a un POST).

Si el servidor ascendente sabe cuál es la codificación correcta, debería indicarlo. De lo contrario, seguiremos lo que dice la especificación. =)

Si cree que la especificación predeterminada es mala, le recomiendo que se involucre en el proceso RFC para HTTP / 2.0 para cambiar esta configuración predeterminada. =)

Lo que dijo @Lukasa + el hecho de que si la codificación recuperada de los encabezados es inexistente, confiamos en la farsa para adivinar la codificación. Con tan pocos caracteres, charada no devolverá nada definitivo porque usa datos estadísticos para adivinar cuál es la codificación correcta.

Francamente, el año no hace ninguna diferencia y tampoco cambia las especificaciones.

Si sabe qué codificación está esperando, también puede realizar la decodificación usted mismo así:

text = str(r.content, '<ENCODING>', errors='replace')

En lo que a mí respecta, las solicitudes no tienen nada de malo y esto tampoco es un error en una farsa. Como @Lukasa parece estar de acuerdo conmigo, cierro esto.

@lavr (/ cc @ sigmavirus24), incluso más fácil que eso, simplemente puede proporcionar la codificación usted mismo.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

Luego, proceda normalmente.

@kennethreitz eso es decepcionante. ¿Por qué se lo ponemos fácil a la gente? = P

Absolutamente :)

Principalmente para sitios web japoneses. Todos mienten sobre su codificación.

@ sigmavirus24
tenga en cuenta que utils.get_encoding_from_headers siempre devuelve 'ISO-8859-1', y charade no tiene posibilidad de ser llamado.
entonces el error es: esperamos que charada se use para adivinar la codificación, pero no lo es.

Un parche anterior corrige un error, pero aún sigue a RFC.
Por favor, considere revisarlo.

@lavr Lo siento, no lo dejamos muy claro. _No_ esperamos que se invoque una farsa en este caso. El RFC es muy claro: si no especifica un juego de caracteres y el tipo MIME es text/* , se debe asumir que la codificación es ISO-8859-1. Eso significa "no adivine". =)

@lavr : simplemente configure r.encoding en None , y funcionará como espera (creo).

O haz r.encoding = r.apparent_encoding .

Aun mejor.

En r.encoding = None y r.encoding = r.apparent_encoding perdimos la información del juego de caracteres del servidor.
Ignorar totalmente el encabezado del servidor no es una buena solución, creo.

La solución correcta es algo como esto:

r = requests.get(...)
params = cgi.parse_header(r.headers.get('content-type'))[0]
server_encoding = ('charset' in params) and params['charset'].strip("'\"") or None
r.encoding = server_encoding or r.apparent_encoding
text = r.text

Se ve raro :(

O haz esto:

r = requests.get(...)

if r.encoding is None or r.encoding == 'ISO-8859-1':
    r.encoding = r.apparent_encoding

No lo creo :)

La condición r.encoding is None no tiene sentido, porque r.encoding nunca puede ser None para content-type = text / *.

r.encoding == 'ISO-8859-1' ... ¿qué significa? ¿El servidor envió el juego de caracteres = 'ISO-8859-1' o el servidor no envió ningún juego de caracteres? Si es el primero, no debería adivinar el juego de caracteres.

@lavr Estaba cubriendo las bases que no son de texto. Puede descartar la posibilidad charset usando esta condición en su lugar:

r.encoding == 'ISO-8859-1' and not 'ISO-8859-1' in r.headers.get('Content-Type', '')

@Lukasa
Bueno, puedo usar este truco.
Y todos en Europa del Este y Asia pueden usarlo.

Pero, ¿y si lo arreglamos en las solicitudes? ;)
¿Qué pasa si las solicitudes pueden establecer honestamente enconding=None en la respuesta sin juego de caracteres?

Como hemos discutido muchas veces, Requests sigue la especificación HTTP al pie de la letra. El comportamiento actual no está mal. =)

El hecho de que no sea útil para su caso de uso es otra historia. =)

Muy bien, eso es suficiente discusión sobre esto. Gracias por la respuesta.

HTTP 1.1 actualizado obsoleto conjunto de caracteres predeterminado ISO-8859-1: http://tools.ietf.org/html/rfc7231#appendix -B

Ya estamos rastreando esto en el n. ° 2086. =)

A quien corresponda, aquí hay un parche de compatibilidad

cree el archivo requests_patch.py con el siguiente código e impórtelo, entonces el problema debería resolverse.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import chardet

def monkey_patch():
    prop = requests.models.Response.content
    def content(self):
        _content = prop.fget(self)
        if self.encoding == 'ISO-8859-1':
            encodings = requests.utils.get_encodings_from_content(_content)
            if encodings:
                self.encoding = encodings[0]
            else:
                self.encoding = chardet.detect(_content)['encoding']

            if self.encoding:
                _content = _content.decode(self.encoding, 'replace').encode('utf8', 'replace')
                self._content = _content
                self.encoding = 'utf8'

        return _content
    requests.models.Response.content = property(content)

monkey_patch()


@lavr (/ cc @ sigmavirus24), incluso más fácil que eso, simplemente puede proporcionar la codificación usted mismo.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

Luego, proceda normalmente.

¡Gracias por esto! ¿Alguna idea de cómo hacerlo en una línea?

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

Temas relacionados

mitar picture mitar  ·  4Comentarios

JimHokanson picture JimHokanson  ·  3Comentarios

thadeusb picture thadeusb  ·  3Comentarios

remram44 picture remram44  ·  4Comentarios

8key picture 8key  ·  3Comentarios