Biarkan objek JSON menerima byte atau membiarkan string keluaran urlopen

177

Dengan Python 3 saya meminta dokumen json dari URL.

response = urllib.request.urlopen(request)

The responseobjek adalah objek file seperti dengan readdan readlinemetode. Biasanya objek JSON dapat dibuat dengan file yang dibuka dalam mode teks.

obj = json.load(fp)

Yang ingin saya lakukan adalah:

obj = json.load(response)

Namun ini tidak berfungsi karena urlopen mengembalikan objek file dalam mode biner.

Pekerjaan di sekitar tentu saja:

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

tapi ini terasa buruk ...

Apakah ada cara yang lebih baik untuk mengubah objek file byte menjadi objek file string? Atau apakah saya kehilangan parameter untuk salah satu urlopenatau json.loadmemberikan pengkodean?

Peter Smit
sumber
2
Saya pikir Anda memiliki kesalahan ketik di sana, "readall" harus "read"?
Bob Yoplait
@ Bobyoplait Saya setuju.
CaptainNemo

Jawaban:

79

HTTP mengirim byte. Jika sumber daya yang dimaksud adalah teks, pengkodean karakter biasanya ditentukan, baik dengan header HTTP Content-Type atau dengan mekanisme lain (RFC, HTML meta http-equiv, ...).

urllib harus tahu cara menyandikan byte ke string, tapi terlalu naif — itu perpustakaan yang sangat kurang bertenaga dan tidak Pythonic.

Dive Into Python 3 memberikan ikhtisar tentang situasi tersebut.

"Kerjakan" Anda baik-baik saja — meskipun rasanya salah, itu cara yang tepat untuk melakukannya.

Humphrey Bogart
sumber
6
Ini mungkin cara yang "benar" untuk melakukannya, tetapi jika ada satu hal yang bisa saya batalkan tentang Python 3, ini akan menjadi byte / string omong kosong ini. Anda akan berpikir fungsi perpustakaan built-in setidaknya akan tahu bagaimana menangani fungsi perpustakaan built-in lainnya. Bagian dari alasan kami menggunakan python adalah sintaksis intuitif sederhana. Perubahan ini merusak semua tempat itu.
ThatAintWorking
4
Lihat perpustakaan "permintaan" - ini menangani hal semacam ini untuk Anda secara otomatis.
offby1
2
Ini bukan kasus fungsi pustaka built-in yang perlu “tahu caranya” untuk menangani fungsi lain. JSON didefinisikan sebagai representasi objek UTF-8, sehingga tidak dapat secara ajaib mendekode byte yang tidak diketahui pengodeannya. Saya setuju bahwa urlopenseharusnya dapat men-decode byte itu sendiri karena ia tahu encoding. Bagaimanapun, saya telah memposting solusi pustaka standar Python sebagai jawaban - Anda dapat melakukan streaming decoding byte menggunakan codecsmodul.
jbg
1
@ThatAintWorking: Saya tidak setuju. Sementara itu adalah rasa sakit di leher untuk secara eksplisit harus mengelola perbedaan antara byte dan string, itu adalah rasa sakit yang jauh lebih besar untuk membuat bahasa melakukan konversi implisit untuk Anda. Bytes implisit <-> konversi string adalah sumber dari banyak bug, dan Python3 sangat membantu dalam menunjukkan jebakan. Tapi saya setuju perpustakaan memiliki ruang untuk perbaikan di bidang ini.
EvertW
@ EvertW kegagalan, menurut pendapat saya, itu memaksa string menjadi unicode di tempat pertama.
ThatAintWorking
99

Perpustakaan standar Python yang luar biasa untuk menyelamatkan ...

import codecs

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

Bekerja dengan kedua py2 dan py3.

Docs: Python 2 , Python3

jbg
sumber
11
Saya mendapatkan kesalahan ini ketika mencoba jawaban ini karena python 3.4.3tidak yakin mengapa? Kesalahannya adalahTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier
9
@AronYsidoro Apakah Anda mungkin menggunakannya json.loads()sebagai ganti json.load()?
sleepycal
6
Untuk poin bonus, menggunakan pengkodean ditentukan dalam respon, bukan dengan asumsi utf-8: response.headers.get_content_charset(). Kembali Nonejika tidak ada penyandian, dan tidak ada di python2.
Phil Frost
5
@ PhilFrost Itu licin. Dalam praktik mungkin perlu untuk berhati-hati dengan itu; JSON selalu UTF-8, UTF-16 atau UTF-32 menurut definisi (dan kemungkinan besar adalah UTF-8), jadi jika pengkodean lain dikembalikan oleh server web, itu mungkin salah konfigurasi dari perangkat lunak server web daripada JSON yang benar-benar non-standar.
jbg
6
ketika saya gunakan di python 3.5, kesalahannya adalah "AttributeError: objek 'bytes' tidak memiliki atribut 'baca'"
Harper Koo
66

Saya berpendapat bahwa pertanyaannya adalah jawaban terbaik :)

import json
from urllib.request import urlopen

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

Untuk siapa pun yang mencoba menyelesaikan ini menggunakan requestsperpustakaan:

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
sumber
12
Fungsi ini terintegrasi untuk requests: Anda dapat melakukannyar.json()
jbg
1
Klarifikasi, jika Anda menggunakan metode @ jbg, Anda tidak perlu melakukannya json.loads. Yang harus Anda lakukan adalah r.json()dan objek JSON Anda sudah dimuat ke dalam dict.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs
13

Yang ini berfungsi untuk saya, saya menggunakan perpustakaan 'permintaan' dengan json()memeriksa dokumen dalam permintaan untuk manusia

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
sumber
Ini cara terbaik. Benar-benar mudah dibaca, dan siapa pun yang melakukan sesuatu seperti ini harus memiliki permintaan.
Baldrickk
6

Saya mengalami masalah yang sama menggunakan Python 3.4.3 & 3.5.2 dan Django 1.11.3. Namun, ketika saya memutakhirkan ke Python 3.6.1 masalah hilang.

Anda dapat membaca lebih lanjut tentang ini di sini: https://docs.python.org/3/whatsnew/3.6.html#json

Jika Anda tidak terikat dengan versi Python tertentu, cukup pertimbangkan untuk meningkatkan ke 3.6 atau yang lebih baru.

PaulMest
sumber
3

Jika Anda mengalami masalah ini saat menggunakan mikroframework flask, maka Anda bisa melakukannya:

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

Dari dokumen : "Jika as_text disetel ke True nilai kembalian akan menjadi string unicode yang didekodekan"

cs_stackX
sumber
Saya sampai di halaman ini karena saya mempunyai masalah dengan tes unit Flask - terima kasih telah memposting satu panggilan baris.
sfblackl
1

Solusi Anda sebenarnya baru saja menyelamatkan saya. Saya mengalami banyak masalah saat memproses permintaan menggunakan kerangka kerja Falcon. Ini berhasil untuk saya. req menjadi bentuk permintaan ikal pr httpie

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

Ini akan mengalirkan data byte ke json.

import io

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

io.TextIOWrapper lebih disukai daripada pembaca modul codec. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
sumber
`*** AttributeError: objek 'Response' tidak memiliki atribut 'readable'``
andilabs
*** AttributeError: objek 'bytes' tidak memiliki atribut 'dapat dibaca'
andilabs
Apakah Anda menggunakan urllib atau permintaan? Ini untuk urllib. Jika Anda memiliki objek byte, cukup gunakan json.loads(bytes_obj.decode()).
Collin Anderson
0

Baru saja menemukan metode sederhana ini untuk menjadikan konten HttpResponse sebagai 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"}

Semoga itu bisa membantu Anda

Aditya Kresna Permana
sumber
0

Pada Python 3.6, Anda bisa menggunakan json.loads()deserialize bytesobjek secara langsung (pengkodean harus UTF-8, UTF-16 atau UTF-32). Jadi, hanya menggunakan modul dari perpustakaan standar, Anda dapat melakukan:

import json
from urllib import request

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

Saya menggunakan program di bawah ini untuk menggunakan 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
sumber