Lassen Sie das JSON-Objekt Bytes akzeptieren oder lassen Sie die Ausgabezeichenfolgen urlopen

176

Mit Python 3 fordere ich ein JSON-Dokument von einer URL an.

response = urllib.request.urlopen(request)

Das responseObjekt ist ein dateiähnliches Objekt mit readund readlineMethoden. Normalerweise kann ein JSON-Objekt mit einer im Textmodus geöffneten Datei erstellt werden.

obj = json.load(fp)

Was ich tun möchte ist:

obj = json.load(response)

Dies funktioniert jedoch nicht, da urlopen ein Dateiobjekt im Binärmodus zurückgibt.

Eine Umgehung ist natürlich:

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

aber das fühlt sich schlecht an ...

Gibt es eine bessere Möglichkeit, ein Byte-Dateiobjekt in ein String-Dateiobjekt umzuwandeln? Oder fehlen mir Parameter für eine urlopenoder json.loadeine Kodierung?

Peter Smit
quelle
2
Ich denke du hast dort einen Tippfehler, "readall" sollte "read" sein?
Bob Yoplait
@ BobYoplait Ich stimme zu.
CaptainNemo

Antworten:

79

HTTP sendet Bytes. Wenn es sich bei der fraglichen Ressource um Text handelt, wird die Zeichenkodierung normalerweise entweder durch den HTTP-Header vom Inhaltstyp oder durch einen anderen Mechanismus (RFC, HTML meta http-equiv, ...) angegeben.

urllib sollte wissen, wie man die Bytes in einen String codiert, aber es ist zu naiv - es ist eine schrecklich unterversorgte und nicht pythonische Bibliothek.

Dive Into Python 3 bietet einen Überblick über die Situation.

Ihr "Workaround" ist in Ordnung - obwohl es sich falsch anfühlt, ist es der richtige Weg, dies zu tun.

Humphrey Bogart
quelle
6
Dies mag der "richtige" Weg sein, aber wenn es eine Sache gäbe, die ich an Python 3 rückgängig machen könnte, wäre es dieser Bytes / Strings-Mist. Sie würden denken, die integrierten Bibliotheksfunktionen würden zumindest wissen, wie sie mit anderen integrierten Bibliotheksfunktionen umgehen sollen. Ein Grund, warum wir Python verwenden, ist die einfache intuitive Syntax. Diese Änderung bricht das überall.
ThatAintWorking
4
Schauen Sie sich die Bibliothek "Anfragen" an - sie erledigt solche Dinge automatisch für Sie.
offby1
2
Dies ist kein Fall für die integrierten Bibliotheksfunktionen, die wissen müssen, wie sie mit anderen Funktionen umgehen sollen. JSON ist als UTF-8-Darstellung von Objekten definiert, sodass Bytes, deren Codierung nicht bekannt ist, nicht auf magische Weise decodiert werden können. Ich bin damit einverstanden, dass urlopenin der Lage sein sollte, die Bytes selbst zu decodieren, da es die Codierung kennt. Wie auch immer, ich habe die Python-Standardbibliothekslösung als Antwort veröffentlicht - Sie können mithilfe des codecsModuls die Streaming-Dekodierung von Bytes durchführen .
jbg
1
@ThatAintWorking: Ich würde nicht zustimmen. Während es ein Schmerz im Nacken ist, den Unterschied zwischen Bytes und Strings explizit verwalten zu müssen, ist es ein viel größerer Schmerz, wenn die Sprache eine implizite Konvertierung für Sie vornimmt. Implizite Bytes <-> String-Konvertierungen sind eine Quelle vieler Fehler, und Python3 ist sehr hilfreich, um auf die Fallstricke hinzuweisen. Ich stimme jedoch zu, dass die Bibliothek in diesem Bereich Verbesserungspotenzial bietet.
EvertW
@EvertW der Fehler, meiner Meinung nach, zwingt es Strings, in erster Linie Unicode zu sein.
ThatAintWorking
99

Pythons wunderbare Standardbibliothek zur Rettung…

import codecs

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

Funktioniert sowohl mit py2 als auch mit py3.

Dokumente : Python 2 , Python3

jbg
quelle
11
Ich habe diesen Fehler erhalten, als ich diese Antwort versuchte. Ich bin python 3.4.3mir nicht sicher, warum? Der Fehler warTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier
9
@AronYsidoro Hast du möglicherweise json.loads()statt verwendet json.load()?
Sleepycal
6
Verwenden Sie für Bonuspunkte die in der Antwort angegebene Codierung, anstatt utf-8 anzunehmen : response.headers.get_content_charset(). Gibt zurück, Nonewenn keine Codierung vorhanden ist und auf python2 nicht vorhanden ist.
Phil Frost
5
@PhilFrost Das ist glatt. In der Praxis könnte es sich lohnen, damit vorsichtig zu sein. JSON ist per Definition immer UTF-8, UTF-16 oder UTF-32 (und es ist überwiegend UTF-8). Wenn also eine andere Codierung vom Webserver zurückgegeben wird, liegt möglicherweise eine Fehlkonfiguration der Webserversoftware vor wirklich nicht standardmäßiger JSON.
jbg
6
Als ich in Python 3.5 verwendet habe, war der Fehler "AttributeError: 'Bytes'-Objekt hat kein Attribut' read '"
Harper Koo
66

Ich bin zu dem Schluss gekommen, dass die Frage die beste Antwort ist :)

import json
from urllib.request import urlopen

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

Für alle anderen, die versuchen, dies mithilfe der requestsBibliothek zu lösen :

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
quelle
12
Diese Funktionalität ist integriert in requests: Sie können einfach tunr.json()
jbg
1
Wenn Sie die Methode von @ jbg verwenden, müssen Sie dies nicht tun json.loads. Sie müssen r.json()lediglich Ihr JSON-Objekt bereits in ein Diktat geladen haben.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
Andilabs
13

Dieser funktioniert für mich. Ich habe die 'Anforderungs'-Bibliothek verwendet json(), um das Dokument in Anfragen für Menschen auszuchecken

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
quelle
Dies ist der beste Weg. Wirklich lesbar, und jeder, der so etwas tut, sollte Anfragen haben.
Baldrickk
6

Ich bin mit Python 3.4.3 & 3.5.2 und Django 1.11.3 auf ähnliche Probleme gestoßen. Als ich jedoch auf Python 3.6.1 aktualisierte, verschwanden die Probleme.

Weitere Informationen finden Sie hier: https://docs.python.org/3/whatsnew/3.6.html#json

Wenn Sie nicht an eine bestimmte Version von Python gebunden sind, sollten Sie ein Upgrade auf 3.6 oder höher in Betracht ziehen.

PaulMest
quelle
3

Wenn dieses Problem bei der Verwendung des Kolben-Mikroframeworks auftritt, können Sie Folgendes tun:

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

Aus den Dokumenten : "Wenn as_text auf True gesetzt ist, ist der Rückgabewert eine decodierte Unicode-Zeichenfolge."

cs_stackX
quelle
Ich bin auf diese Seite gekommen, weil ich ein Problem mit Flask-Unit-Tests hatte - danke, dass Sie den einzeiligen Anruf gesendet haben.
sfblackl
1

Ihre Problemumgehung hat mich gerade gerettet. Ich hatte viele Probleme bei der Verarbeitung der Anfrage mit dem Falcon-Framework. Das hat bei mir funktioniert. req ist das Anfrageformular curl pr httpie

json.loads(req.stream.read().decode('utf-8'))
Thielyrics
quelle
1

Dadurch werden die Byte-Daten in json gestreamt.

import io

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

io.TextIOWrapper wird dem Modulleser des Codecs vorgezogen. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
quelle
`*** AttributeError: 'Response'-Objekt hat kein Attribut' readable'``
andilabs
*** AttributeError: '
Bytes'
Verwenden Sie urllib oder Anfragen? Dies ist für Urllib. Wenn Sie ein Byte-Objekt haben, verwenden Sie einfach json.loads(bytes_obj.decode()).
Collin Anderson
0

Ich habe gerade diese einfache Methode gefunden, um HttpResponse-Inhalte als JSON zu erstellen

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"}

Hoffe das hilft dir

Aditya Kresna Permana
quelle
0

Ab Python 3.6 können Sie json.loads()ein bytesObjekt direkt deserialisieren (die Codierung muss UTF-8, UTF-16 oder UTF-32 sein). Wenn Sie also nur Module aus der Standardbibliothek verwenden, können Sie Folgendes tun:

import json
from urllib import request

response = request.urlopen(url).read()
data = json.loads(response)
Eugene Yarmash
quelle
-2

Ich habe unten Programm verwendet, um zu verwenden 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
quelle