Consenti all'oggetto JSON di accettare byte o lasciare le stringhe di output urlopen


177

Con Python 3 sto richiedendo un documento JSON da un URL.

response = urllib.request.urlopen(request)

L' responseoggetto è un oggetto simile a un file con reade readlinemetodi. Normalmente un oggetto JSON può essere creato con un file aperto in modalità testo.

obj = json.load(fp)

Quello che vorrei fare è:

obj = json.load(response)

Ciò tuttavia non funziona poiché urlopen restituisce un oggetto file in modalità binaria.

Una soluzione è ovviamente:

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

ma questo sembra male ...

Esiste un modo migliore per trasformare un oggetto file byte in un oggetto file stringa? O mi manca qualche parametro per uno urlopeno json.loadper dare una codifica?


2
Penso che tu abbia un refuso lì, "readall" dovrebbe essere "read"?
Bob Yoplait,

@BobYoplait Sono d'accordo.
Capitano Nemo,

Risposte:


79

HTTP invia byte. Se la risorsa in questione è il testo, la codifica dei caratteri viene normalmente specificata, dall'intestazione HTTP Content-Type o da un altro meccanismo (RFC, HTML meta http-equiv, ...).

urllib dovrebbe sapere come codificare i byte in una stringa, ma è troppo ingenuo: è una libreria terribilmente sottodimensionata e non pitonica.

Dive Into Python 3 offre una panoramica della situazione.

Il tuo "aggirare" va bene, anche se sembra sbagliato, è il modo corretto di farlo.


6
Questo potrebbe essere il modo "corretto" per farlo, ma se ci fosse una cosa che potrei annullare su Python 3 sarebbe questa merda di byte / stringhe. Penseresti che le funzioni di libreria integrate saprebbero almeno come gestire altre funzioni di libreria integrate. Parte del motivo per cui usiamo Python è la semplice sintassi intuitiva. Questo cambiamento lo spezza dappertutto.
ThatAintWorking,

4
Dai un'occhiata alla libreria "richieste" : gestisce questo genere di cose per te automagicamente.
offby1

2
Questo non è un caso delle funzioni di libreria integrate che devono "sapere come" gestire altre funzioni. JSON è definito come una rappresentazione UTF-8 di oggetti, quindi non può decodificare magicamente byte di cui non conosce la codifica. Concordo sul fatto che urlopendovrebbe essere in grado di decodificare i byte stessi poiché conosce la codifica. Ad ogni modo, ho pubblicato la soluzione di libreria standard Python come risposta: puoi eseguire lo streaming di decodifica di byte usando il codecsmodulo.
jbg

1
@ThatAintWorking: non sarei d'accordo. Mentre è un dolore al collo dover gestire esplicitamente la differenza tra byte e stringhe, è molto più difficile avere il linguaggio che fa una conversione implicita per te. I byte impliciti <-> conversioni di stringhe sono fonte di molti bug e Python3 è molto utile per evidenziare le insidie. Ma sono d'accordo che la biblioteca ha margini di miglioramento in questo settore.
EvertW

@EvertW il fallimento, secondo me, ha costretto le stringhe a essere unicode in primo luogo.
ThatAintWorking,

99

La meravigliosa libreria standard di Python in soccorso ...

import codecs

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

Funziona con py2 e py3.

Documenti: Python 2 , Python3


11
Ho ricevuto questo errore quando ho provato questa risposta python 3.4.3non so perché? L'errore eraTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier il

9
@AronYsidoro Forse hai usato json.loads()invece di json.load()?
sleepycal,

6
Per i punti bonus, utilizzare la codifica specificata nella risposta, invece di assumere utf-8: response.headers.get_content_charset(). Restituisce Nonese non c'è codifica e non esiste su python2.
Phil Frost,

5
@PhilFrost È slick. In pratica, potrebbe essere prudente; JSON è sempre UTF-8, UTF-16 o UTF-32 per definizione (ed è estremamente probabile che sia UTF-8), quindi se un'altra codifica viene restituita dal server Web, è probabilmente una configurazione errata del software del server Web piuttosto che JSON veramente non standard.
jbg

6
quando ho usato in Python 3.5, l'errore era "AttributeError: l'oggetto" bytes "non ha alcun attributo" read ""
Harper Koo,

66

Sono giunto a ritenere che la domanda sia la migliore risposta :)

import json
from urllib.request import urlopen

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

18

Per chiunque cerchi di risolverlo usando la requestslibreria:

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

12
Questa funzionalità è integrata in requests: puoi semplicemente farlor.json()
jbg

1
Il chiarimento, se usi il metodo di @ jbg, non devi farlo json.loads. Tutto quello che devi fare è r.json()e hai già il tuo oggetto JSON caricato in un dict.
Blairg23,

*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs

13

Questo funziona per me, ho usato la libreria 'request' con json()check out the doc nelle richieste per umani

import requests

url = 'here goes your url'

obj = requests.get(url).json() 

Questo è il modo migliore Davvero leggibile, e chiunque stia facendo qualcosa del genere dovrebbe avere delle richieste.
Baldrickk,

6

Ho riscontrato problemi simili usando Python 3.4.3 e 3.5.2 e Django 1.11.3. Tuttavia, quando ho eseguito l'aggiornamento a Python 3.6.1 i problemi sono scomparsi.

Puoi leggere di più qui: https://docs.python.org/3/whatsnew/3.6.html#json

Se non sei legato a una versione specifica di Python, prendi in considerazione l'aggiornamento a 3.6 o versioni successive.


3

Se si riscontra questo problema durante l'uso del microframework della beuta, allora puoi semplicemente fare:

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

Dai documenti : "Se as_text è impostato su True, il valore restituito sarà una stringa unicode decodificata"


Sono arrivato a questa pagina perché avevo un problema con i test di unità Flask, grazie per aver pubblicato la chiamata a linea singola.
sfblackl,

1

La tua soluzione alternativa mi ha appena salvato. Avevo molti problemi nell'elaborare la richiesta usando il framework Falcon. Questo ha funzionato per me. req è il modulo di richiesta curl pr httpie

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

1

In questo modo i dati byte verranno trasmessi in json.

import io

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

io.TextIOWrapper è preferito al lettore di moduli del codec. https://www.python.org/dev/peps/pep-0400/


`*** AttributeError: l'oggetto 'Response' non ha alcun attributo 'leggibile' ''
andilabs

*** AttributeError: l'oggetto 'bytes' non ha alcun attributo 'leggibile'
andilabs

Stai utilizzando urllib o richieste? Questo è per urllib. Se hai un oggetto byte, basta usare json.loads(bytes_obj.decode()).
Collin Anderson,

0

Ho appena trovato questo semplice metodo per rendere il contenuto HttpResponse come un 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"}

Spero che ti aiuti


0

A partire da Python 3.6, è possibile utilizzare json.loads()per deserializzare bytesdirettamente un oggetto (la codifica deve essere UTF-8, UTF-16 o UTF-32). Quindi, usando solo i moduli della libreria standard, puoi fare:

import json
from urllib import request

response = request.urlopen(url).read()
data = json.loads(response)

-2

Ho usato sotto il programma per usare 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.