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
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?
Comentários muito úteis
@lavr (/ cc @ sigmavirus24), ainda mais fácil do que isso, você pode simplesmente fornecer a codificação você mesmo.
Em seguida, prossiga normalmente.