Позвольте объекту 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" должен быть "read"?
Боб
@BobYoplait Я согласен.
CaptainNemo

Ответы:

79

HTTP отправляет байты. Если рассматриваемым ресурсом является текст, кодировка символов обычно указывается либо с помощью HTTP-заголовка Content-Type, либо с помощью другого механизма (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
@EvertW ошибка, на мой взгляд, она заставляет строки быть в кодировке Unicode.
ThatAintWorking
99

Прекрасная стандартная библиотека Python для спасения ...

import codecs

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

Работает как с py2, так и с py3.

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

JBG
источник
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: у объекта« bytes »нет атрибута« read »»
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 в dict.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
Andilabs
13

Этот работает для меня, я использовал библиотеку 'request' с 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 или более поздней.

PaulMest
источник
3

Если вы столкнулись с этой проблемой при использовании микрорамки с колбой, вы можете просто:

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

Из документов : «Если as_text установлен в True, возвращаемое значение будет декодированной строкой Юникода»

cs_stackX
источник
Я попал на эту страницу, потому что у меня возникла проблема с модульными тестами Flask - спасибо за публикацию однострочного вызова.
sfblackl
1

Твой обходной путь фактически спас меня. У меня было много проблем с обработкой запроса с использованием платформы Falcon. Это сработало для меня. требуется форма запроса curl pr httpie

json.loads(req.stream.read().decode('utf-8'))
thielyrics
источник
1

Это будет поток данных байта в JSON.

import io

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

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

Коллин Андерсон
источник
`*** AttributeError: у объекта 'Response' нет атрибута 'readable'``
andilabs
*** AttributeError: у объекта 'bytes' нет атрибута 'readable'
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)
Jayesh
источник