Pozwól obiektowi JSON akceptować bajty lub zostaw urlopen ciągi wyjściowe

177

W Pythonie 3 żądam dokumentu json z adresu URL.

response = urllib.request.urlopen(request)

responseObiekt jest obiektem plikopodobnym z readi readlinemetody. Zwykle obiekt JSON można utworzyć za pomocą pliku otwartego w trybie tekstowym.

obj = json.load(fp)

Chciałbym:

obj = json.load(response)

To jednak nie działa, ponieważ urlopen zwraca obiekt pliku w trybie binarnym.

Obejście to oczywiście:

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

ale to jest złe ...

Czy istnieje lepszy sposób na przekształcenie obiektu pliku bajtów w obiekt pliku ciągu? A może brakuje mi jakichkolwiek parametrów dla jednego z nich urlopenlub json.loaddla podania kodowania?

Peter Smit
źródło
2
Myślę, że masz tam literówkę, „czytaj wszystko” powinno być „czytać”?
Bob Yoplait
@BobYoplait Zgadzam się.
CaptainNemo

Odpowiedzi:

79

HTTP wysyła bajty. Jeśli dany zasób jest tekstem, kodowanie znaków jest zwykle określane przez nagłówek HTTP Content-Type lub inny mechanizm (RFC, HTML meta http-equiv, ...).

urllib Powinien wiedzieć, jak zakodować bajty do łańcucha, ale jest to zbyt naiwne - to strasznie niedopasowana i nie-Pythonowa biblioteka.

Dive Into Python 3 zawiera przegląd sytuacji.

Twoje „obejście” jest w porządku - chociaż wydaje się niewłaściwe, jest to właściwy sposób, aby to zrobić.

Humphrey Bogart
źródło
6
Może to być „poprawny” sposób, aby to zrobić, ale jeśli jest jedna rzecz, którą mógłbym cofnąć w Pythonie 3, byłaby to bzdura dotycząca bajtów / łańcuchów. Można by pomyśleć, że wbudowane funkcje biblioteczne przynajmniej wiedziałyby, jak radzić sobie z innymi wbudowanymi funkcjami bibliotecznymi. Jednym z powodów, dla których używamy Pythona, jest prosta, intuicyjna składnia. Ta zmiana niszczy to wszędzie.
ThatAintWorking
4
Zapoznaj się z biblioteką „wniosków” - obsługuje ona tego typu sprawy automagicznie.
offby 1
2
Nie jest to przypadek, gdy wbudowane funkcje biblioteczne muszą „wiedzieć, jak” radzić sobie z innymi funkcjami. JSON jest zdefiniowany jako reprezentacja obiektów w formacie UTF-8, więc nie może magicznie dekodować bajtów, których nie zna kodowania. Zgadzam się, że urlopenpowinien być w stanie dekodować same bajty, ponieważ zna kodowanie. W każdym razie, jako odpowiedź zamieściłem rozwiązanie biblioteki standardowej Pythona - za pomocą codecsmodułu możesz wykonać strumieniowe dekodowanie bajtów .
jbg
1
@ThatAintWorking: Nie zgadzam się. Chociaż jawne zarządzanie różnicą między bajtami a ciągami znaków jest utrapieniem, o wiele większy ból sprawia, że ​​język dokonuje za nas niejawnej konwersji. Niejawne konwersje ciągów bajtów <-> są źródłem wielu błędów, a Python3 jest bardzo pomocny we wskazywaniu pułapek. Ale zgadzam się, że biblioteka ma miejsce na ulepszenia w tej dziedzinie.
EvertW
@EvertW awaria, moim zdaniem, wymusza na stringach w pierwszej kolejności unicode.
ThatAintWorking
99

Z pomocą przychodzi wspaniała standardowa biblioteka Pythona…

import codecs

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

Działa z py2 i py3.

Dokumenty: Python 2 , Python3

jbg
źródło
11
Otrzymałem ten błąd, próbując odpowiedzieć, python 3.4.3nie wiem dlaczego? Błąd był następującyTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier
9
@AronYsidoro Czy prawdopodobnie użyłeś json.loads()zamiast json.load()?
Sleepycal
6
Punktów bonusowych użyciu kodowania określonego w odpowiedzi zamiast przyjmować UTF-8 response.headers.get_content_charset(). Zwraca, Nonejeśli nie ma kodowania i nie istnieje w python2.
Phil Frost
5
@PhilFrost To sprytne. W praktyce warto zachować ostrożność; JSON jest zawsze z definicji UTF-8, UTF-16 lub UTF-32 (i przeważnie prawdopodobnie będzie to UTF-8), więc jeśli inne kodowanie jest zwracane przez serwer sieciowy, prawdopodobnie jest to błędna konfiguracja oprogramowania serwera WWW, a nie prawdziwie niestandardowy JSON.
jbg
6
kiedy użyłem w Pythonie 3.5, błąd brzmiał: „AttributeError: obiekt 'bytes' nie ma atrybutu 'read'”
Harper Koo
66

Doszedłem do wniosku, że pytanie jest najlepszą odpowiedzią :)

import json
from urllib.request import urlopen

response = urlopen("site.com/api/foo/bar").read().decode('utf8')
obj = json.loads(response)
SergO
źródło
18

Dla każdego, kto próbuje rozwiązać ten problem za pomocą requestsbiblioteki:

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
źródło
12
Ta funkcjonalność jest wbudowana w requests: możesz po prostu zrobićr.json()
jbg
1
Wyjaśnij, jeśli używasz metody @ jbg, nie musisz tego robić json.loads. Wszystko, co musisz zrobić, to r.json()załadować już swój obiekt JSON do dyktu.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs
13

Ten działa dla mnie, użyłem biblioteki „request”, aby json()sprawdzić dokument w prośbach dla ludzi

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
źródło
To najlepszy sposób. Naprawdę czytelne, a każdy, kto robi coś takiego, powinien mieć prośby.
Baldrickk
6

Napotkałem podobne problemy używając Pythona 3.4.3 i 3.5.2 oraz Django 1.11.3. Jednak po uaktualnieniu do Pythona 3.6.1 problemy zniknęły.

Możesz przeczytać więcej na ten temat tutaj: https://docs.python.org/3/whatsnew/3.6.html#json

Jeśli nie jesteś przywiązany do konkretnej wersji Pythona, rozważ uaktualnienie do wersji 3.6 lub nowszej.

PaulMest
źródło
3

Jeśli masz ten problem podczas korzystania z microframework flask, możesz po prostu:

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

Z dokumentacji : „Jeśli as_text ma wartość True, wartość zwracana będzie zdekodowanym ciągiem znaków Unicode”

cs_stackX
źródło
Dotarłem do tej strony, ponieważ miałem problem z testami jednostkowymi Flask - dzięki za wysłanie połączenia jednowierszowego.
sfblackl
1

Twoje obejście właśnie mnie uratowało. Miałem wiele problemów z przetwarzaniem żądania przy użyciu frameworka Falcon. To zadziałało dla mnie. req to formularz zapytania curl pr httpie

json.loads(req.stream.read().decode('utf-8'))
thielyrics
źródło
1

Spowoduje to przesłanie danych bajtowych do json.

import io

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

io.TextIOWrapper jest preferowany w stosunku do czytnika modułu kodeka. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
źródło
`*** AttributeError: obiekt 'Response' nie ma atrybutu 'readable' ''
andilabs
*** AttributeError: obiekt „bytes” nie ma atrybutu „readable”
andilabs
Czy używasz urllib lub żądań? To jest dla urllib. Jeśli masz obiekt bajtów, po prostu użyj json.loads(bytes_obj.decode()).
Collin Anderson,
0

Właśnie znalazłem tę prostą metodę tworzenia zawartości HttpResponse jako pliku 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"}

Mam nadzieję, że ci to pomoże

Aditya Kresna Permana
źródło
0

Począwszy od Pythona 3.6, możesz użyć json.loads()do bytesbezpośredniej deserializacji obiektu (kodowanie musi być UTF-8, UTF-16 lub UTF-32). Tak więc, używając tylko modułów z biblioteki standardowej, możesz:

import json
from urllib import request

response = request.urlopen(url).read()
data = json.loads(response)
Eugene Yarmash
źródło
-2

Użyłem poniższego programu do korzystania z 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
źródło