Deje que el objeto JSON acepte bytes o permita que urlopen cadenas de salida

177

Con Python 3 solicito un documento json desde una URL.

response = urllib.request.urlopen(request)

El responseobjeto es un objeto tipo archivo con ready readlinemétodos. Normalmente, un objeto JSON se puede crear con un archivo abierto en modo de texto.

obj = json.load(fp)

Lo que me gustaría hacer es:

obj = json.load(response)

Sin embargo, esto no funciona ya que urlopen devuelve un objeto de archivo en modo binario.

Una solución es, por supuesto:

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

pero esto se siente mal ...

¿Hay una mejor manera de transformar un objeto de archivo de bytes en un objeto de archivo de cadena? ¿O me faltan parámetros para urlopeno json.loadpara dar una codificación?

Peter Smit
fuente
2
Creo que tiene un error tipográfico allí, "readall" debería ser "leído"?
Bob Yoplait
@BobYoplait Estoy de acuerdo.
CaptainNemo

Respuestas:

79

HTTP envía bytes. Si el recurso en cuestión es texto, la codificación de caracteres normalmente se especifica, ya sea por el encabezado HTTP Content-Type o por otro mecanismo (un RFC, HTML meta http-equiv, ...).

urllib debería saber cómo codificar los bytes en una cadena, pero es demasiado ingenuo, es una biblioteca horriblemente poco poderosa y poco pitónica.

Sumérgete en Python 3 proporciona una visión general sobre la situación.

Su "solución" está bien, aunque se siente mal, es la forma correcta de hacerlo.

Humphrey Bogart
fuente
66
Esta puede ser la forma "correcta" de hacerlo, pero si hubiera una cosa que pudiera deshacer sobre Python 3, sería esta basura de bytes / cadenas. Se podría pensar que las funciones de biblioteca integradas al menos sabrían cómo tratar con otras funciones de biblioteca integradas. Parte de la razón por la que usamos python es la sintaxis intuitiva simple. Este cambio rompe eso en todo el lugar.
ThatAintWorking
44
Echa un vistazo a la biblioteca de "solicitudes" : maneja este tipo de cosas automáticamente.
offby1
2
Este no es un caso de las funciones de biblioteca incorporadas que necesitan "saber cómo" manejar otras funciones. JSON se define como una representación UTF-8 de objetos, por lo que no puede decodificar mágicamente bytes de los que no conoce la codificación. Estoy de acuerdo en que urlopendebería poder decodificar los bytes, ya que conoce la codificación. De todos modos, he publicado la solución de biblioteca estándar de Python como respuesta: puede hacer la decodificación de bytes de transmisión mediante el codecsmódulo.
jbg
1
@ThatAintWorking: No estaría de acuerdo. Si bien es una molestia tener que manejar explícitamente la diferencia entre bytes y cadenas, es mucho más difícil que el lenguaje haga una conversión implícita para usted. Las conversiones de cadena de bytes implícitos <-> son una fuente de muchos errores, y Python3 es muy útil para señalar las trampas. Pero estoy de acuerdo en que la biblioteca tiene margen de mejora en esta área.
EvertW
@EvertW el fallo, en mi opinión, obliga a las cadenas a ser unicode en primer lugar.
ThatAintWorking
99

La maravillosa biblioteca estándar de Python al rescate ...

import codecs

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

Funciona con py2 y py3.

Documentos: Python 2 , Python3

jbg
fuente
11
Recibí este error al intentar esta respuesta, ¿ python 3.4.3no estoy seguro de por qué? El error fueTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier
9
@AronYsidoro ¿Usaste posiblemente en json.loads()lugar de json.load()?
sleepycal
66
Para los puntos de bonificación, utilizar la codificación especificada en la respuesta, en lugar de asumir UTF-8: response.headers.get_content_charset(). Devuelve Nonesi no hay codificación y no existe en python2.
Phil Frost
55
@PhilFrost Eso es resbaladizo. En la práctica, podría pagar tener cuidado con eso; JSON siempre es UTF-8, UTF-16 o UTF-32 por definición (y es muy probable que sea UTF-8), por lo que si el servidor web devuelve otra codificación, posiblemente sea una configuración incorrecta del software del servidor web en lugar de JSON genuinamente no estándar.
jbg
66
cuando lo usé en python 3.5, el error fue "AttributeError: el objeto 'bytes' no tiene el atributo 'read'"
Harper Koo
66

He llegado a la opinión de que la pregunta es la mejor respuesta :)

import json
from urllib.request import urlopen

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

Para cualquier otra persona que intente resolver esto usando la requestsbiblioteca:

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
fuente
12
Esta funcionalidad está integrada en requests: simplemente puede hacerr.json()
jbg
1
La aclaración, si usa el método de @ jbg, no necesita hacerlo json.loads. Todo lo que tienes que hacer es que r.json()ya tienes tu objeto JSON cargado en un dict.
Blairg23
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs
13

Este funciona para mí, utilicé la biblioteca 'request' con json()el documento en solicitudes para humanos

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
fuente
Esta es la mejor manera. Realmente legible, y cualquiera que esté haciendo algo como esto debería tener solicitudes.
Baldrickk
6

Me encontré con problemas similares usando Python 3.4.3 y 3.5.2 y Django 1.11.3. Sin embargo, cuando actualicé a Python 3.6.1 los problemas desaparecieron.

Puede leer más sobre esto aquí: https://docs.python.org/3/whatsnew/3.6.html#json

Si no está vinculado a una versión específica de Python, solo considere actualizar a 3.6 o posterior.

PaulMest
fuente
3

Si tiene este problema mientras usa el matraz de matraz, entonces puede hacer lo siguiente:

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

De los documentos : "Si as_text se establece en True, el valor de retorno será una cadena unicode decodificada"

cs_stackX
fuente
Llegué a esta página porque tenía un problema con las pruebas unitarias de Flask. Gracias por publicar la llamada de una sola línea.
sfblackl
1

Su solución en realidad solo me salvó. Estaba teniendo muchos problemas para procesar la solicitud usando el marco Falcon. Esto funcionó para mí. req siendo el formulario de solicitud curl pr httpie

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

Esto transmitirá los datos de bytes a json.

import io

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

Se prefiere io.TextIOWrapper al lector de módulos del códec. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
fuente
`*** AttributeError: el objeto 'Response' no tiene atributo 'legible' ''
andilabs
*** AttributeError: el objeto 'bytes' no tiene atributo 'legible'
andilabs
¿Estás usando urllib o solicitudes? Esto es para urllib. Si tiene un objeto de bytes, simplemente use json.loads(bytes_obj.decode()).
Collin Anderson
0

Acabo de encontrar este método simple para hacer contenido HttpResponse como 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"}

Espero que te ayude

Aditya Kresna Permana
fuente
0

A partir de Python 3.6, puede usar json.loads()para deserializar un bytesobjeto directamente (la codificación debe ser UTF-8, UTF-16 o UTF-32). Entonces, usando solo módulos de la biblioteca estándar, puede hacer:

import json
from urllib import request

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

Utilicé el siguiente programa para usar 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
fuente