Una buena manera de obtener el conjunto de caracteres / encoding de una respuesta HTTP en Python

Buscando una manera fácil de obtener la información del conjunto de caracteres / encoding de una respuesta HTTP utilizando Python urllib2, o cualquier otra biblioteca de Python.

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

Sé que a veces está presente en el encabezado “Tipo de contenido”, pero ese encabezado tiene otra información y está incrustado en una cadena que necesitaría analizar. Por ejemplo, el encabezado Content-Type devuelto por Google es

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

Podría trabajar con eso, pero no estoy seguro de qué tan consistente será el formato. Estoy bastante seguro de que es posible que falte un charset por completo, así que tendría que manejar ese caso de borde. Parece que algún tipo de operación de división de cadenas para obtener el ‘utf-8’ es la forma incorrecta de hacer este tipo de cosas.

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

Ese es el tipo de código que parece que está haciendo demasiado trabajo. Tampoco estoy seguro de si funcionará en todos los casos. ¿Alguien tiene una mejor manera de hacer esto?

Para analizar el encabezado http puede usar cgi.parse_header() :

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

O usando el objeto de respuesta:

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

En general, el servidor puede mentir acerca de la encoding o no informarlo (el valor predeterminado depende del tipo de contenido) o la encoding se puede especificar dentro del cuerpo de la respuesta, por ejemplo, el elemento en documentos html o en la statement xml para xml documentos. Como último recurso, la encoding podría deducirse del contenido mismo.

Podría usar requests para obtener texto Unicode:

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

O BeautifulSoup para analizar html (y convertirlo a Unicode como efecto secundario):

 from bs4 import BeautifulSoup # pip install beautifulsoup4 soup = BeautifulSoup(urllib2.urlopen(url)) # may use `cchardet` for speed # ... 

O bs4.UnicodeDammit directamente para contenido arbitrario (no necesariamente un html):

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

Si está familiarizado con la stack de desarrollo web de Flask / Werkzeug , le complacerá saber que la biblioteca Werkzeug tiene una respuesta para este tipo de análisis de encabezado HTTP, y en el caso de que no se especifique el tipo de contenido en Todo, como tú habías querido.

  >>> 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'}) 

Entonces puedes hacer:

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

Tenga en cuenta que si no se suministra charset , esto producirá en su lugar:

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

Incluso funciona si no proporciona nada más que una cadena o un dict vacío:

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

Por lo tanto, parece ser EXACTAMENTE lo que estabas buscando! Si observa el código fuente, verá que tenían su propósito en mente: 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. ... 

Espero que esto ayude a alguien algún día! 🙂

La biblioteca de requests hace esto fácil:

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

Los conjuntos de caracteres se pueden especificar de muchas maneras , pero a menudo se hace en los encabezados.

 >>> 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() >>> 

El último no especificó un conjunto de caracteres en cualquier lugar, por lo que get_content_charset() devolvió None .

Para decodificar correctamente html (es decir, de forma similar a un navegador, no podemos hacerlo mejor), debe tener en cuenta:

  1. Valor de encabezado HTTP de tipo de contenido;
  2. Marcas de la lista de materiales;
  3. tags en el cuerpo de la página;
  4. Diferencias entre los nombres de encoding definidos utilizados en la web y los nombres de encoding disponibles en Python stdlib;
  5. Como último recurso, si todo lo demás falla, la opción de adivinar en base a estadísticas es una opción.

Todo lo anterior se implementa en la función w3lib.encoding.html_to_unicode : tiene html_to_unicode(content_type_header, html_body_str, default_encoding='utf8', auto_detect_fun=None) Firma y devuelve (detected_encoding, unicode_html_content) .

las solicitudes, BeautifulSoup, UnicodeDamnnit, chardet o flask parse_options_header no son soluciones correctas ya que todas fallan en algunos de estos puntos.