Requests: Response.text возвращает неправильно декодированный текст (запросы 1.2.3, python 2.7)

Созданный на 15 сент. 2013  ·  24Комментарии  ·  Источник: psf/requests

Если 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 (/ cc @ sigmavirus24), даже проще, чем это, вы можете просто предоставить кодировку самостоятельно.

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

Затем действуйте как обычно.

Все 24 Комментарий

Спасибо за это @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'

Затем действуйте как обычно.

Спасибо за это! Есть идеи, как это сделать в одной строке?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

NoahCardoza picture NoahCardoza  ·  4Комментарии

JimHokanson picture JimHokanson  ·  3Комментарии

Matt3o12 picture Matt3o12  ·  3Комментарии

jakul picture jakul  ·  3Комментарии

remram44 picture remram44  ·  4Комментарии