Requests: Response.text retorna texto decodificado incorretamente (solicitações 1.2.3, python 2.7)

Criado em 15 set. 2013  ·  24Comentários  ·  Fonte: psf/requests

Se o servidor http retornar Content-type: text / * sem codificação, Response.text sempre o decodifica como texto 'ISO-8859-1'.

Pode ser válido no RFC2616 / 3.7.1, mas está errado na vida real em 2013.

Criei uma página de exemplo com texto em chinês:
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
Todos os navegadores renderizam esta página corretamente.
Mas reguests.get retorna um texto inválido.

E aqui está um teste simples com esse url:
https://gist.github.com/lavr/6572927

Comentários muito úteis

@lavr (/ cc @ sigmavirus24), ainda mais fácil do que isso, você pode simplesmente fornecer a codificação você mesmo.

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

Em seguida, prossiga normalmente.

Todos 24 comentários

Obrigado por este @lavr!

Esta é uma decisão deliberada de design para Solicitações. Estamos seguindo a especificação, a menos que nos encontremos em uma posição onde a especificação diverge tanto do comportamento do mundo real que se torna um problema (por exemplo, GET após 302 resposta a um POST).

Se o servidor upstream souber qual é a codificação correta, ele deve sinalizá-la. Caso contrário, seguiremos o que diz a especificação. =)

Se você acha que o padrão de especificação é ruim, recomendo fortemente que você se envolva com o processo de RFC para HTTP / 2.0 para que esse padrão seja alterado. =)

O que @Lukasa disse + o fato de que se a codificação recuperada dos cabeçalhos não existe, dependemos de charadas para adivinhar a codificação. Com tão poucos caracteres, charada não retornará nada definitivo porque usa dados estatísticos para adivinhar qual é a codificação correta.

Francamente, o ano não faz diferença e também não muda as especificações.

Se você sabe a codificação que está esperando, também pode fazer a decodificação por conta própria, assim:

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

Não há nada de errado com solicitações, no que me diz respeito, e isso também não é um bug de charada. Já que @Lukasa parece concordar comigo, estou encerrando isso.

@lavr (/ cc @ sigmavirus24), ainda mais fácil do que isso, você pode simplesmente fornecer a codificação você mesmo.

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

Em seguida, prossiga normalmente.

@kennethreitz isso é decepcionante. Por que estamos tornando isso fácil para as pessoas? = P

Absolutamente :)

Principalmente para sites japoneses. Todos eles mentem sobre sua codificação.

@ sigmavirus24
observe que utils.get_encoding_from_headers sempre retorna 'ISO-8859-1', e charade não tem chance de ser chamado.
então o bug é: esperamos que a charada seja usada para adivinhar a codificação, mas não é.

Um patch acima corrige um bug, mas ainda segue o RFC.
Por favor, considere revisá-lo.

@lavr Desculpe, não deixamos isso muito claro. _Não_ esperamos que charada seja chamada neste caso. O RFC é muito claro: se você não especificar um conjunto de caracteres e o tipo MIME for text/* , a codificação deve ser assumida como ISO-8859-1. Isso significa "não adivinhe". =)

@lavr : apenas defina r.encoding para None e funcionará como você espera (eu acho).

Ou faça r.encoding = r.apparent_encoding .

Melhor ainda.

Em r.encoding = None e r.encoding = r.apparent_encoding perdemos as informações do conjunto de caracteres do servidor.
Ignorar totalmente o cabeçalho do servidor não é uma boa solução, eu acho.

A solução certa é algo assim:

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

Parece estranho :(

Ou faça isso:

r = requests.get(...)

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

Acho que não :)

A condição r.encoding is None não tem sentido, porque r.encoding nunca pode ser Nenhum para content-type = text / *.

r.encoding == 'ISO-8859-1' ... o que isso significa? O servidor enviou o conjunto de caracteres = 'ISO-8859-1' ou o servidor não enviou nenhum conjunto de caracteres? Se primeiro, eu não deveria adivinhar charset.

@lavr Eu estava cobrindo as bases não textuais. Você pode descartar a possibilidade de charset usando esta condição:

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

@Lukasa
Bem, eu posso usar este hack.
E todos na Europa Oriental e na Ásia podem usá-lo.

Mas e se corrigirmos isso nas solicitações? ;)
E se as solicitações puderem definir honestamente enconding=None na resposta sem charset?

Como já discutimos muitas vezes, Requests está seguindo a especificação HTTP ao pé da letra. O comportamento atual não está errado. =)

O fato de não ser útil para o seu caso de uso é uma outra história. =)

Tudo bem, já chega de discussão sobre isso. Obrigado pelo feedback.

O HTTP 1.1 torna obsoleto o conjunto de caracteres padrão ISO-8859-1: http://tools.ietf.org/html/rfc7231#apethoscope -B

Já estamos rastreando isso em # 2086. =)

Para quem possa interessar, aqui está um patch de compatibilidade

crie o arquivo requests_patch.py com o seguinte código e importe-o, então o problema deve ser resolvido.

#!/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), ainda mais fácil do que isso, você pode simplesmente fornecer a codificação você mesmo.

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

Em seguida, prossiga normalmente.

Obrigado por isso! Alguma ideia de como fazer isso em uma linha?

Esta página foi útil?
0 / 5 - 0 avaliações