Laisser l'objet JSON accepter les octets ou laisser urlopen les chaînes de sortie

177

Avec Python 3, je demande un document json à partir d'une URL.

response = urllib.request.urlopen(request)

L' responseobjet est un objet de type fichier avec des méthodes readet readline. Normalement, un objet JSON peut être créé avec un fichier ouvert en mode texte.

obj = json.load(fp)

Ce que je voudrais faire, c'est:

obj = json.load(response)

Cela ne fonctionne cependant pas car urlopen renvoie un objet fichier en mode binaire.

Un contournement est bien sûr:

str_response = response.read().decode('utf-8')
obj = json.loads(str_response)

mais ça fait du mal ...

Existe-t-il un meilleur moyen de transformer un objet de fichier d'octets en un objet de fichier de chaîne? Ou est-ce que je manque des paramètres pour l'un urlopenou l' autre json.loadpour donner un encodage?

Peter Smit
la source
2
Je pense que vous avez une faute de frappe, "readall" devrait être "read"?
Bob Yoplait
@BobYoplait Je suis d'accord.
CaptainNemo

Réponses:

79

HTTP envoie des octets. Si la ressource en question est du texte, le codage des caractères est normalement spécifié, soit par l'en-tête HTTP Content-Type, soit par un autre mécanisme (un RFC, HTML meta http-equiv, ...).

urllib devrait savoir encoder les octets dans une chaîne, mais c'est trop naïf - c'est une bibliothèque horriblement sous-alimentée et non pythonique.

Dive Into Python 3 donne un aperçu de la situation.

Votre "contournement" est bien - même si cela semble mal, c'est la bonne façon de le faire.

Humphrey bogart
la source
6
C'est peut-être la manière "correcte" de le faire, mais s'il y avait une chose que je pourrais annuler à propos de Python 3, ce serait cette merde d'octets / chaînes. On pourrait penser que les fonctions de bibliothèque intégrées sauraient au moins comment gérer les autres fonctions de bibliothèque intégrées. Une partie de la raison pour laquelle nous utilisons python est la simple syntaxe intuitive. Ce changement brise tout cela partout.
ThatAintWorking
4
Consultez la bibliothèque "Requêtes" - elle gère ce genre de choses pour vous automatiquement.
offby1
2
Ce n'est pas un cas où les fonctions de bibliothèque intégrées ont besoin de «savoir comment» traiter d'autres fonctions. JSON est défini comme une représentation UTF-8 d'objets, il ne peut donc pas décoder par magie les octets dont il ne connaît pas l'encodage. Je suis d'accord que urlopendevrait être capable de décoder les octets lui-même car il connaît l'encodage. Quoi qu'il en soit, j'ai publié la solution de bibliothèque standard Python comme réponse - vous pouvez effectuer un décodage en continu des octets à l'aide du codecsmodule.
jbg
1
@ThatAintWorking: Je ne suis pas d'accord. Bien que ce soit une douleur dans le cou de devoir explicitement gérer la différence entre les octets et les chaînes, c'est beaucoup plus pénible que le langage effectue une conversion implicite pour vous. Les conversions implicites de chaînes d'octets <-> sont une source de nombreux bogues, et Python3 est très utile pour signaler les pièges. Mais je conviens que la bibliothèque a des progrès à faire dans ce domaine.
EvertW
@EvertW l'échec, à mon avis, oblige les chaînes à être unicode en premier lieu.
ThatAintWorking
99

La merveilleuse bibliothèque standard de Python à la rescousse…

import codecs

reader = codecs.getreader("utf-8")
obj = json.load(reader(response))

Fonctionne avec py2 et py3.

Documents: Python 2 , Python3

jbg
la source
11
J'ai eu cette erreur en essayant cette réponse, je python 3.4.3ne sais pas pourquoi? L'erreur étaitTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier
9
@AronYsidoro Avez-vous utilisé à la json.loads()place de json.load()?
sleepycal
6
Pour les points de bonus, utilisez l'encodage spécifié dans la réponse, au lieu d'assumer utf-8: response.headers.get_content_charset(). Renvoie Nones'il n'y a pas d'encodage et n'existe pas sur python2.
Phil Frost
5
@PhilFrost C'est élégant. En pratique, il pourrait être utile de faire attention à cela; JSON est toujours UTF-8, UTF-16 ou UTF-32 par définition (et est extrêmement susceptible d'être UTF-8), donc si un autre encodage est renvoyé par le serveur Web, il s'agit probablement d'une mauvaise configuration du logiciel du serveur Web plutôt que JSON véritablement non standard.
jbg
6
quand j'ai utilisé dans python 3.5, l'erreur était "AttributeError: l'objet 'bytes' n'a pas d'attribut 'read'"
Harper Koo
66

Je suis arrivé à l'opinion que la question est la meilleure réponse :)

import json
from urllib.request import urlopen

response = urlopen("site.com/api/foo/bar").read().decode('utf8')
obj = json.loads(response)
SergO
la source
18

Pour quiconque essaie de résoudre ce problème en utilisant la requestsbibliothèque:

import json
import requests

r = requests.get('http://localhost/index.json')
r.raise_for_status()
# works for Python2 and Python3
json.loads(r.content.decode('utf-8'))
Luke Yeager
la source
12
Cette fonctionnalité est intégrée à requests: vous pouvez simplement fairer.json()
jbg
1
La clarification, si vous utilisez la méthode de @ jbg, vous n'avez pas besoin de le faire json.loads. Tout ce que vous avez à faire est r.json()que votre objet JSON est déjà chargé dans un dict.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs
13

Celui-ci fonctionne pour moi, j'ai utilisé la bibliothèque de `` demande '' avec json()vérifier le document dans les demandes pour les humains

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
la source
C'est la meilleur façon. Vraiment lisible, et quiconque fait quelque chose comme ça devrait avoir des demandes.
Baldrickk le
6

J'ai rencontré des problèmes similaires en utilisant Python 3.4.3 & 3.5.2 et Django 1.11.3. Cependant, lorsque j'ai mis à niveau vers Python 3.6.1, les problèmes ont disparu.

Vous pouvez en savoir plus ici: https://docs.python.org/3/whatsnew/3.6.html#json

Si vous n'êtes pas lié à une version spécifique de Python, envisagez simplement de passer à la version 3.6 ou ultérieure.

PaulMest
la source
3

Si vous rencontrez ce problème lors de l'utilisation du microframework flask, vous pouvez simplement faire:

data = json.loads(response.get_data(as_text=True))

D'après la documentation : "Si as_text est défini sur True, la valeur de retour sera une chaîne Unicode décodée"

cs_stackX
la source
Je suis arrivé à cette page parce que j'avais un problème avec les tests unitaires Flask - merci d'avoir publié l'appel sur une seule ligne.
sfblackl
1

Votre solution de contournement vient de me sauver. J'avais beaucoup de problèmes à traiter la requête en utilisant le framework Falcon. Cela a fonctionné pour moi. req étant le formulaire de demande curl pr httpie

json.loads(req.stream.read().decode('utf-8'))
thielyrics
la source
1

Cela diffusera les données d'octet dans json.

import io

obj = json.load(io.TextIOWrapper(response))

io.TextIOWrapper est préférable au lecteur de module du codec. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
la source
`` *** AttributeError: l'objet 'Response' n'a pas d'attribut 'readable' ''
andilabs
*** AttributeError: l'objet 'bytes' n'a pas d'attribut 'readable'
andilabs
Utilisez-vous urllib ou des requêtes? Ceci est pour urllib. Si vous avez un objet bytes, utilisez simplement json.loads(bytes_obj.decode()).
Collin Anderson
0

Je viens de trouver cette méthode simple pour créer du contenu HttpResponse en tant que json

import json

request = RequestFactory() # ignore this, this just like your request object

response = MyView.as_view()(request) # got response as HttpResponse object

response.render() # call this so we could call response.content after

json_response = json.loads(response.content.decode('utf-8'))

print(json_response) # {"your_json_key": "your json value"}

J'espère que cela vous aide

Aditya Kresna Permana
la source
0

Depuis Python 3.6, vous pouvez utiliser json.loads()pour désérialiser un bytesobjet directement (l'encodage doit être UTF-8, UTF-16 ou UTF-32). Ainsi, en utilisant uniquement les modules de la bibliothèque standard, vous pouvez faire:

import json
from urllib import request

response = request.urlopen(url).read()
data = json.loads(response)
Eugène Yarmash
la source
-2

J'ai utilisé le programme ci-dessous pour utiliser json.loads()

import urllib.request
import json
endpoint = 'https://maps.googleapis.com/maps/api/directions/json?'
api_key = 'AIzaSyABbKiwfzv9vLBR_kCuhO7w13Kseu68lr0'
origin = input('where are you ?').replace(' ','+')
destination = input('where do u want to go').replace(' ','+')
nav_request = 'origin={}&destination={}&key={}'.format(origin,destination,api_key)
request = endpoint + nav_request
response = urllib.request.urlopen(request).read().decode('utf-8')
directions = json.loads(response)
print(directions)
Jayesh
la source