Хороший способ получить кодировку/кодировку ответа HTTP в Python

Ищете простой способ получить информацию о кодировке/кодировании ответа HTTP с помощью Python urllib2 или любой другой библиотеки Python.

>>> url = 'http://some.url.value'
>>> request = urllib2.Request(url)
>>> conn = urllib2.urlopen(request)
>>> response_encoding = ?

Я знаю, что он иногда присутствует в заголовке Content-Type, но этот заголовок имеет другую информацию, и он встроен в строку, которую мне нужно будет анализировать. Например, заголовок Content-Type, возвращаемый Google,

>>> conn.headers.getheader('content-type')
'text/html; charset=utf-8'

Я мог бы с этим справиться, но я не уверен, насколько совместим формат. Я почти уверен, что возможно, что кодировка будет отсутствовать целиком, поэтому мне придется обрабатывать этот край. Некоторая операция разделения строк, чтобы вывести "utf-8" из этого, кажется, что это должен быть неправильный способ сделать это.

>>> content_type_header = conn.headers.getheader('content-type')
>>> if '=' in content_type_header:
>>>  charset = content_type_header.split('=')[1]

Это тот код, который чувствует, что он слишком много работает. Я также не уверен, что он будет работать в каждом случае. У кого-нибудь есть лучший способ сделать это?

Ответы

Ответ 1

Чтобы разобрать HTTP-заголовок, вы можете использовать cgi.parse_header():

_, params = cgi.parse_header('text/html; charset=utf-8')
print params['charset'] # -> utf-8

Или используя объект ответа:

response = urllib2.urlopen('http://example.com')
response_encoding = response.headers.getparam('charset')
# or in Python 3: response.headers.get_content_charset(default)

В общем случае сервер может лежать о кодировке или вообще не сообщать об этом (по умолчанию зависит от типа содержимого), или может быть указано кодирование внутри тела ответа, например, <meta> в html-документах или в xml объявление для документов xml. В крайнем случае кодирование можно было бы угадать из самого содержимого.

Вы можете использовать requests, чтобы получить текст Юникода:

import requests # pip install requests

r = requests.get(url)
unicode_str = r.text # may use `chardet` to auto-detect encoding

Или BeautifulSoup, чтобы разобрать html (и преобразовать в Unicode как побочный эффект):

from bs4 import BeautifulSoup # pip install beautifulsoup4

soup = BeautifulSoup(urllib2.urlopen(url)) # may use `cchardet` for speed
# ...

Или bs4.UnicodeDammit напрямую для произвольного содержимого (не обязательно html):

from bs4 import UnicodeDammit

dammit = UnicodeDammit(b"Sacr\xc3\xa9 bleu!")
print(dammit.unicode_markup)
# -> Sacré bleu!
print(dammit.original_encoding)
# -> utf-8

Ответ 2

Если вы знакомы с Flask/Werkzeug, вы с удовольствием узнаете, что библиотека Werkzeug имеет ответ для такого рода разбора HTTP-заголовков и учитывает случай, когда тип содержимого не указан вообще, как вы хотел.

 >>> from werkzeug.http import parse_options_header
 >>> import requests
 >>> url = 'http://some.url.value'
 >>> resp = requests.get(url)
 >>> if resp.status_code is requests.codes.ok:
 ...     content_type_header = resp.headers.get('content_type')
 ...     print content_type_header
 'text/html; charset=utf-8'
 >>> parse_options_header(content_type_header) 
 ('text/html', {'charset': 'utf-8'})

Итак, вы можете сделать:

 >>> content_type_header[1].get('charset')
 'utf-8'

Обратите внимание, что если charset не указывается, это будет производить вместо:

 >>> parse_options_header('text/html')
 ('text/html', {})

Это даже работает, если вы не поставляете ничего, кроме пустой строки или dict:

 >>> parse_options_header({})
 ('', {})
 >>> parse_options_header('')
 ('', {})

Таким образом, это ТОЧНО, что вы искали! Если вы посмотрите на исходный код, вы увидите, что у них была ваша цель: https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/http.py#L320-329

def parse_options_header(value):
    """Parse a ``Content-Type`` like header into a tuple with the content
    type and the options:
    >>> parse_options_header('text/html; charset=utf8')
    ('text/html', {'charset': 'utf8'})
    This should not be used to parse ``Cache-Control`` like headers that use
    a slightly different format.  For these headers use the
    :func:`parse_dict_header` function.
    ...

Надеюсь, кому-то это поможет!:)

Ответ 3

Библиотека requests делает это легко:

>>> import requests
>>> r = requests.get('http://some.url.value')
>>> r.encoding
'utf-8' # e.g.

Ответ 4

Оболочки могут быть указаны в много способов, но это часто делается в заголовках.

>>> urlopen('http://www.python.org/').info().get_content_charset()
'utf-8'
>>> urlopen('http://www.google.com/').info().get_content_charset()
'iso-8859-1'
>>> urlopen('http://www.python.com/').info().get_content_charset()
>>> 

Этот последний не указывал кодировку нигде, поэтому get_content_charset() вернулся None.

Ответ 5

Чтобы правильно (то есть в виде браузера - мы не можем сделать лучше), декодируем html, вам нужно принять во внимание:

  • Значение HTTP-заголовка Content-Type;
  • Знаки спецификации;
  • <meta> теги в теле страницы;
  • Различия между указанными именами кодировок, используемыми в сети, - именами кодировок, доступными в Python stdlib;
  • В крайнем случае, если все остальное терпит неудачу, предположение, основанное на статистике, является опцией.

Все вышесказанное реализовано в w3lib.encoding.html_to_unicode: оно имеет подпись html_to_unicode(content_type_header, html_body_str, default_encoding='utf8', auto_detect_fun=None) и возвращает (detected_encoding, unicode_html_content) tuple.

BeautifulSoup, UnicodeDamnnit, chardet или flask parse_options_header не являются правильными решениями, поскольку все они не работают в некоторых из этих точек.

Ответ 6

Это то, что работает для меня отлично. Я использую Python 2.7 и 3.4

print (text.encode('cp850','replace'))