web-dev-qa-db-fra.com

Un bon moyen d'obtenir le jeu de caractères / encodage d'une réponse HTTP dans Python

Vous cherchez un moyen facile d'obtenir les informations de jeu de caractères/d'encodage d'une réponse HTTP en utilisant Python urllib2, ou toute autre bibliothèque Python.

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

Je sais qu'il est parfois présent dans l'en-tête "Content-Type", mais cet en-tête contient d'autres informations, et il est intégré dans une chaîne que j'aurais besoin d'analyser. Par exemple, l'en-tête Content-Type renvoyé par Google est

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

Je pourrais travailler avec cela, mais je ne suis pas sûr de la cohérence du format. Je suis presque sûr qu'il est possible que charset soit complètement absent, donc je devrais gérer ce cas Edge. Une sorte d'opération de division de chaîne pour en retirer le "utf-8" semble être la mauvaise façon de faire ce genre de chose.

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

C'est le genre de code qui donne l'impression de faire trop de travail. Je ne sais pas non plus si cela fonctionnera dans tous les cas. Quelqu'un at-il une meilleure façon de procéder?

26
Clay Wardell

Pour analyser l'en-tête http, vous pouvez utiliser cgi.parse_header() :

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

Ou en utilisant l'objet réponse:

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

En général, le serveur peut mentir à propos de l'encodage ou ne pas le signaler du tout (la valeur par défaut dépend du type de contenu) ou l'encodage peut être spécifié dans le corps de la réponse, par exemple, <meta> élément dans les documents html ou dans la déclaration xml pour les documents xml. En dernier recours, l'encodage pourrait être deviné à partir du contenu lui-même.

Vous pouvez utiliser requests pour obtenir le texte Unicode:

import requests # pip install requests

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

Ou BeautifulSoup pour analyser le HTML (et le convertir en Unicode comme effet secondaire):

from bs4 import BeautifulSoup # pip install beautifulsoup4

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

Ou bs4.UnicodeDammit directement pour le contenu arbitraire (pas nécessairement 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
22
jfs

Si vous connaissez la pile de développement Web Flask / Werkzeug , vous serez heureux de savoir que la bibliothèque Werkzeug a une réponse pour ce type d'analyse syntaxique d'en-tête HTTP, et rend compte du cas où le type de contenu n'est pas spécifié du tout, comme vous l'aviez voulu.

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

Alors vous pouvez faire:

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

Notez que si charset n'est pas fourni, cela produira à la place:

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

Cela fonctionne même si vous ne fournissez rien d'autre qu'une chaîne ou un dict vide:

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

Ainsi, il semble être EXACTEMENT ce que vous cherchiez! Si vous regardez le code source, vous verrez qu'ils avaient votre objectif en tête: 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.
    ...

J'espère que cela aidera quelqu'un un jour! :)

7
Brian Peterson

La bibliothèque requests facilite cela:

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

Les jeux de caractères peuvent être spécifiés dans de nombreuses façons , mais cela se fait souvent dans les en-têtes.

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

Ce dernier n'a spécifié aucun jeu de caractères, donc get_content_charset() a renvoyé None.

3
Cees Timmerman

Pour bien (c'est-à-dire comme un navigateur - nous ne pouvons pas faire mieux) décoder le HTML, vous devez prendre en compte:

  1. Valeur d'en-tête HTTP Content-Type;
  2. Marques de nomenclature;
  3. <meta> Balises dans le corps de la page;
  4. Différences entre les noms de codage définis utilisés dans le Web et les noms de codage disponibles dans Python stdlib;
  5. En dernier recours, si tout le reste échoue, deviner sur la base de statistiques est une option.

Tout ce qui précède est implémenté dans la fonction w3lib.encoding.html_to_unicode : il a la signature html_to_unicode(content_type_header, html_body_str, default_encoding='utf8', auto_detect_fun=None) et retourne (detected_encoding, unicode_html_content) Tuple.

les requêtes, BeautifulSoup, UnicodeDamnnit, chardet ou parse_options_header du flacon ne sont pas des solutions correctes car elles échouent toutes à certains de ces points.

1
Mikhail Korobov

C'est ce qui fonctionne parfaitement pour moi. J'utilise python 2.7 et 3.4

print (text.encode('cp850','replace'))
0
Usama Tahir