Нехай об’єкт JSON приймає байти або нехай urlopen виводить рядки


177

З Python 3 я прошу документ json з URL-адреси.

response = urllib.request.urlopen(request)

responseОб'єкт являє собою файл-подібний об'єкт з readі readlineметоди. Зазвичай об’єкт JSON можна створити з файлом, відкритим у текстовому режимі.

obj = json.load(fp)

Що я хотів би зробити:

obj = json.load(response)

Однак це не працює, оскільки urlopen повертає об’єкт файлу у двійковому режимі.

Робота навколо, звичайно, є:

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

але це погано ...

Чи є кращий спосіб перетворити об'єкт файлу байтів у об'єкт файлового рядка? Або я пропускаю якісь параметри для urlopenабо json.loadдля надання кодування?


2
Я думаю, у вас там помилка друку, "readall" слід "читати"?
Боб Йоплайт

@BobYoplait Я згоден.
CaptainNemo

Відповіді:


79

HTTP надсилає байти. Якщо ресурсом, про який йдеться, є текст, кодування символів зазвичай задається або заголовком HTTP-типу контенту, або іншим механізмом (RFC, HTML meta http-equiv, ...).

urllib Ви повинні знати, як кодувати байти до рядка, але це занадто наївно - це жахливо малопотужна та непітонічна бібліотека.

Dive Into Python 3 надає огляд ситуації.

Ваша «обхідна робота» прекрасна - хоча вона почувається неправильно, це правильний спосіб зробити це.


6
Це може бути "правильний" спосіб зробити це, але якби було одне, що я міг би скасувати про Python 3, було б це байт / рядок лайно. Ви думаєте, що вбудовані функції бібліотеки принаймні знають, як поводитися з іншими вбудованими функціями бібліотеки. Частина причини, по якій ми використовуємо python, - простий інтуїтивний синтаксис. Ця зміна порушує всюди.
ThatAintWorking

4
Перегляньте бібліотеку "запитів" - вона обробляє такі речі для вас автоматично.
offby1

2
Це не випадок вбудованих функцій бібліотеки, які потребують «вміння» поводитися з іншими функціями. JSON визначається як UTF-8 представлення об'єктів, тому він не може магічно декодувати байти, про які не знає кодування. Я погоджуюся, що urlopenслід мати можливість самому розкодувати байти, оскільки він знає кодування. У будь-якому випадку я опублікував стандартне рішення бібліотеки Python як відповідь - ви можете робити потокове декодування байтів за допомогою codecsмодуля.
jbg

1
@ThatAintWorking: Я б не погодився. Хоча це біль у шиї, коли явно доведеться керувати різницею між байтами та рядками, набагато більшим є біль, щоб мова зробила для вас якесь неявне перетворення. Неявні байтові <-> перетворення рядків є джерелом багатьох помилок, і Python3 дуже корисний у визначенні підводних каменів. Але я згоден, що бібліотека має можливість вдосконалитись у цій галузі.
EvertW

@ Зверніть провал, на мою думку, це змушує внизу рядки бути однокодовим.
ThatAintWorking

99

Чудова стандартна бібліотека Python на допомогу ...

import codecs

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

Працює з py2 та py3.

Документи: Python 2 , Python3


11
Я отримав цю помилку при спробі цієї відповіді, python 3.4.3не знаючи чому? Помилка булаTypeError: the JSON object must be str, not 'StreamReader'
Аарон Лелев'є

9
@AronYsidoro Ви, можливо, використовували json.loads()замість цього json.load()?
сонливий

6
Для отримання бонусних очок, використовуйте кодування , зазначену у відповідь, замість того , припускаючи , UTF-8: response.headers.get_content_charset(). Повертається, Noneякщо немає кодування та не існує на python2.
Філ Мороз

5
@PhilFrost Це хитро. На практиці може бути обережним з цим; JSON - це завжди UTF-8, UTF-16 або UTF-32 за визначенням (і переважна ймовірність, що це UTF-8), тому якщо інше кодування повертається веб-сервером, можливо, це неправильна конфігурація програмного забезпечення веб-сервера, а не справді нестандартний JSON.
jbg

6
коли я використовував у python 3.5, помилка була "AttributeError:" байт "об'єкта не має атрибута" читати ""
Harper Koo

66

Я прийшов до думки, що питання найкраща відповідь :)

import json
from urllib.request import urlopen

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

18

Для всіх, хто намагається вирішити це за допомогою requestsбібліотеки:

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
Ця функціональність вбудована в requests: ви можете просто зробитиr.json()
jbg

1
Пояснення, якщо ви використовуєте метод @ jbg, цього робити не потрібно json.loads. Все, що вам потрібно зробити, - це r.json()і ваш об’єкт JSON вже завантажений у дикт.
Blairg23

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

13

Цей для мене працює, я використав бібліотеку "запит", щоб json()перевірити документ у запитах для людей

import requests

url = 'here goes your url'

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

Це найкращий спосіб. Дійсно читабельна, і кожен, хто робить щось подібне, повинен мати запити.
Baldrickk

6

У мене виникли подібні проблеми за допомогою Python 3.4.3 & 3.5.2 та Django 1.11.3. Однак, коли я перейшов на Python 3.6.1, проблеми пішли.

Більше про це можна прочитати тут: https://docs.python.org/3/whatsnew/3.6.html#json

Якщо ви не прив'язані до певної версії Python, просто подумайте про оновлення до 3.6 або пізнішої версії.


3

Якщо у вас виникає ця проблема під час використання мікрорамки колби, ви можете просто зробити:

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

З документів : "Якщо для параметра as_text встановлено значення True, значенням повернення буде декодована рядок Unicode"


Я потрапив на цю сторінку, тому що у мене виникли проблеми з тестами на флаконі - дякую за повідомлення про дзвінок на одній лінії.
sfblackl

1

Ваше вирішення насправді просто врятувало мене. У мене виникло багато проблем з обробкою запиту за допомогою Falcon. Це працювало для мене. req - форма запиту curl pr httpie

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

1

Це передасть дані байта в json.

import io

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

io.TextIOWrapper є кращим для зчитування модулів кодека. https://www.python.org/dev/peps/pep-0400/


`*** AttributeError: Об'єкт 'Response' не має атрибута 'читабельний' ''
andilabs

*** AttributeError: об’єкт 'bytes' не має атрибута 'читабельний'
andilabs

Використовуєте urllib або запити? Це для urllib. Якщо у вас є об’єкт байт, просто використовуйте json.loads(bytes_obj.decode()).
Колін Андерсон

0

Щойно знайшов цей простий метод зробити вміст HttpResponse як 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"}

Сподіваюся, що вам допоможе


0

Станом на Python 3.6, ви можете використовувати json.loads()для деріаріалізації bytesоб'єкта безпосередньо (кодування має бути UTF-8, UTF-16 або UTF-32). Отже, використовуючи лише модулі зі стандартної бібліотеки, ви можете:

import json
from urllib import request

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

-2

Я використав програму нижче для використання 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.