Если http-сервер возвращает Content-type: text / * без кодировки, Response.text всегда декодирует его как текст ISO-8859-1.
Это может быть действительным в RFC2616 / 3.7.1, но в реальной жизни в 2013 году это неверно.
Я сделал пример страницы с китайским текстом:
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
Все браузеры правильно отображают эту страницу.
Но regests.get возвращает недопустимый текст.
А вот простой тест с этим URL:
https://gist.github.com/lavr/6572927
Спасибо за это @lavr!
Это преднамеренное дизайнерское решение для запросов. Мы следуем спецификации, если не окажемся в положении, когда спецификация настолько сильно расходится с поведением в реальном мире, что становится проблемой (например, GET после ответа 302 на POST).
Если вышестоящий сервер знает, что такое правильная кодировка, он должен сообщить об этом. В противном случае мы будем следовать тому, что сказано в спецификации. знак равно
Если вы считаете, что спецификация по умолчанию является плохой, я настоятельно рекомендую вам принять участие в процессе RFC для HTTP / 2.0, чтобы изменить это значение по умолчанию. знак равно
Что сказал @Lukasa + тот факт, что если кодировка, полученная из заголовков, не существует, мы полагаемся на шараду, чтобы угадать кодировку. С таким небольшим количеством символов charade не вернет ничего окончательного, потому что использует статистические данные, чтобы угадать правильную кодировку.
Честно говоря, год не имеет значения и не меняет спецификацию.
Если вы знаете, какую кодировку ожидаете, вы также можете выполнить декодирование самостоятельно следующим образом:
text = str(r.content, '<ENCODING>', errors='replace')
Насколько я понимаю, в запросах нет ничего плохого, и это тоже не ошибка в шараде. Поскольку @Lukasa, похоже, со мной согласен, я закрываю это.
@lavr (/ cc @ sigmavirus24), даже проще, чем это, вы можете просто предоставить кодировку самостоятельно.
>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'
Затем действуйте как обычно.
@kennethreitz , это разочаровывает. Почему мы упрощаем жизнь людям? = P
Абсолютно :)
В основном для японских сайтов. Все они лгут о своей кодировке.
@ sigmavirus24
обратите внимание, что utils.get_encoding_from_headers всегда возвращает 'ISO-8859-1', и charade не может быть вызван.
так что ошибка: мы ожидаем, что для угадывания кодировки используется шарада, но это не так.
Приведенный выше патч исправляет ошибку, но по-прежнему соответствует RFC.
Пожалуйста, рассмотрите возможность его просмотра.
@lavr Извините, мы не очень ясно дали это понять. Мы _не_ ожидаем, что в этом случае будет объявлена шарада. RFC очень ясен: если вы не укажете кодировку, а тип MIME - text/*
, кодировка должна быть принята как ISO-8859-1. Это означает «не угадай». знак равно
@lavr : просто установите r.encoding
на None
, и все будет работать так, как вы ожидаете (я думаю).
Или сделайте r.encoding = r.apparent_encoding
.
Даже лучше.
На r.encoding = None
и r.encoding = r.apparent_encoding
мы потеряли информацию о кодировке сервера.
Я думаю, что полное игнорирование заголовка сервера - не лучшее решение.
Правильное решение выглядит примерно так:
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
Странно выглядит :(
Или сделайте так:
r = requests.get(...)
if r.encoding is None or r.encoding == 'ISO-8859-1':
r.encoding = r.apparent_encoding
Я так не думаю :)
Условие r.encoding is None
не имеет смысла, потому что r.encoding никогда не может быть None для content-type = text / *.
r.encoding == 'ISO-8859-1'
... что это значит? Сервер отправил кодировку = 'ISO-8859-1' или сервер не отправил кодировку? Если первый, я не должен угадывать кодировку.
@lavr Я освещал нетекстовые основы. Вы можете исключить возможность charset
, используя вместо этого следующее условие:
r.encoding == 'ISO-8859-1' and not 'ISO-8859-1' in r.headers.get('Content-Type', '')
@Lukasa
Что ж, я могу использовать эту хитрость.
И все в Восточной Европе и Азии могут им пользоваться.
Но что, если мы исправим это в запросах? ;)
Что, если запросы могут честно устанавливать enconding=None
в ответ без кодировки?
Как мы уже много раз обсуждали, запросы строго следуют спецификации HTTP. Текущее поведение не является неправильным. знак равно
Тот факт, что это бесполезно для вашего варианта использования, - это совсем другая история. знак равно
Хорошо, хватит обсуждать это. Спасибо за ответ.
Обновленный HTTP 1.1 отменяет кодировку по умолчанию ISO-8859-1: http://tools.ietf.org/html/rfc7231#appendix -B
Мы уже отслеживаем это в # 2086. знак равно
Кому это может быть интересно, вот патч совместимости
создайте файл requests_patch.py
со следующим кодом и импортируйте его, тогда проблема должна быть решена.
#!/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), даже проще, чем это, вы можете просто предоставить кодировку самостоятельно.
>>> r = requests.get('http://irresponsible-server/') >>> r.encoding = 'utf-8'
Затем действуйте как обычно.
Спасибо за это! Есть идеи, как это сделать в одной строке?
Самый полезный комментарий
@lavr (/ cc @ sigmavirus24), даже проще, чем это, вы можете просто предоставить кодировку самостоятельно.
Затем действуйте как обычно.