Requests: Response.text renvoie du texte décodé de manière incorrecte (requêtes 1.2.3, python 2.7)

Créé le 15 sept. 2013  ·  24Commentaires  ·  Source: psf/requests

Si le serveur http renvoie Content-type: text/* sans encodage, Response.text le décode toujours en tant que texte 'ISO-8859-1'.

C'est peut-être valide dans la RFC2616/3.7.1, mais c'est faux dans la vraie vie en 2013.

J'ai fait une page d'exemple avec du texte chinois :
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
Tous les navigateurs restituent correctement cette page.
Mais reguests.get renvoie un texte invalide.

Et voici un test simple avec cette URL :
https://gist.github.com/lavr/6572927

Commentaire le plus utile

@lavr (/cc @sigmavirus24), encore plus simple que cela, vous pouvez simplement fournir l'encodage vous-même.

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

Ensuite, procédez normalement.

Tous les 24 commentaires

Merci pour ce @lavr !

Il s'agit d'une décision de conception délibérée pour les demandes. Nous suivons la spécification à moins que nous ne nous trouvions dans une position où la spécification diverge tellement du comportement du monde réel que cela devient un problème (par exemple GET après une réponse 302 à un POST).

Si le serveur amont sait quel est le codage correct, il doit le signaler. Sinon, nous allons suivre ce que dit la spécification. =)

Si vous pensez que la spécification par défaut est mauvaise, je vous encourage fortement à vous impliquer dans le processus RFC pour HTTP/2.0 afin de modifier cette valeur par défaut. =)

Ce que @Lukasa a dit + le fait que si l'encodage récupéré à partir des en-têtes est inexistant, nous nous appuyons sur la charade pour deviner l'encodage. Avec si peu de caractères, charade ne renverra rien de définitif car il utilise des données statistiques pour deviner quel est le bon encodage.

Franchement, l'année ne fait aucune différence et ne change pas non plus les spécifications.

Si vous savez à quel encodage vous vous attendez, vous pouvez également effectuer le décodage vous-même comme suit :

text = str(r.content, '<ENCODING>', errors='replace')

Il n'y a rien de mal avec les demandes en ce qui me concerne et ce n'est pas non plus un bug de mascarade. Puisque @Lukasa semble être d'accord avec moi, je ferme ceci.

@lavr (/cc @sigmavirus24), encore plus simple que cela, vous pouvez simplement fournir l'encodage vous-même.

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

Ensuite, procédez normalement.

@kennethreitz c'est décevant. Pourquoi rendons-nous cela facile pour les gens? =P

Absolument :)

Principalement pour les sites Web japonais. Ils mentent tous sur leur encodage.

@sigmavirus24
veuillez noter que utils.get_encoding_from_headers renvoie toujours 'ISO-8859-1', et charade n'a aucune chance d'être appelée.
donc le bug est : nous nous attendons à ce que charade soit utilisé pour deviner l'encodage, mais ce n'est pas le cas.

Un correctif ci-dessus corrige un bogue, mais suit toujours RFC.
S'il vous plaît, pensez à le revoir.

@lavr Désolé, nous n'avons pas été très clairs. Nous ne nous attendons pas à ce que la charade soit appelée dans ce cas. La RFC est très claire : si vous ne spécifiez pas de jeu de caractères et que le type MIME est text/* , l'encodage doit être supposé être ISO-8859-1. Cela signifie "ne devinez pas". =)

@lavr : définissez simplement r.encoding sur None , et cela fonctionnera comme prévu (je pense).

Ou faites r.encoding = r.apparent_encoding .

Encore mieux.

Sur r.encoding = None et r.encoding = r.apparent_encoding nous avons perdu les informations du jeu de caractères du serveur.
Ignorer totalement l'en-tête du serveur n'est pas une bonne solution, je pense.

La bonne solution est quelque chose comme ceci:

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

Semble étrange :(

Ou faites ceci :

r = requests.get(...)

if r.encoding is None or r.encoding == 'ISO-8859-1':
    r.encoding = r.apparent_encoding

Je ne pense pas :)

La condition r.encoding is None n'a aucun sens, car r.encoding ne peut jamais être None pour content-type=text/*.

r.encoding == 'ISO-8859-1' ... qu'est-ce que ça veut dire ? Le serveur a envoyé le jeu de caractères='ISO-8859-1' ou le serveur n'a envoyé aucun jeu de caractères ? Si d'abord, je ne devrais pas deviner le jeu de caractères.

@lavr Je charset en utilisant cette condition à la place :

r.encoding == 'ISO-8859-1' and not 'ISO-8859-1' in r.headers.get('Content-Type', '')

@Lukasa
Eh bien, je peux utiliser ce hack.
Et tout le monde en Europe de l'Est et en Asie peut l'utiliser.

Mais et si on le corrigeait dans les requêtes ? ;)
Et si les requêtes pouvaient honnêtement définir enconding=None sur la réponse sans jeu de caractères ?

Comme nous en avons discuté à plusieurs reprises, Requests suit la spécification HTTP à la lettre. Le comportement actuel n'est pas mauvais. =)

Le fait que cela ne soit pas utile pour votre cas d'utilisation est une toute autre histoire. =)

D'accord, c'est assez de discussion à ce sujet. Merci pour les commentaires.

HTTP 1.1 mis à jour rend obsolète le jeu de caractères par défaut ISO-8859-1 : http://tools.ietf.org/html/rfc7231#appendix -B

Nous suivons déjà cela dans #2086. =)

A qui de droit, voici un patch de compatibilité

créez le fichier requests_patch.py avec le code suivant et importez-le, le problème devrait être résolu.

#!/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), encore plus simple que cela, vous pouvez simplement fournir l'encodage vous-même.

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

Ensuite, procédez normalement.

Merci pour cela! Une idée sur la façon de le faire en une seule ligne?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

eromoe picture eromoe  ·  3Commentaires

NoahCardoza picture NoahCardoza  ·  4Commentaires

avinassh picture avinassh  ·  4Commentaires

Matt3o12 picture Matt3o12  ·  3Commentaires

brainwane picture brainwane  ·  3Commentaires