Veinticinco líneas convertidas en una, la sesión que va casi tres veces más rápido, y el if respuesta: que parece correcto y se traga los errores.
⏱ 15 min de lectura
La lección anterior acabó con una función de veinticinco líneas para hacer bien lo que requests hace así:
python
requests.get(url, timeout=10).json()
requests no es la biblioteca estándar, y aun así es la respuesta correcta para casi todo. La usan Django, pip y medio Python. Esta lección enseña a usarla y, sobre todo, las tres trampas que tiene precisamente por ser cómoda: cuando algo es fácil, se escribe sin mirar.
params= codifica la consulta por ti, que era la función de la lección anterior. Y la respuesta trae todo lo que hace falta:
python
respuesta.status_code # 200
respuesta.headers # un diccionario que no distingue mayúsculas
respuesta.json()# el cuerpo ya analizado
respuesta.text # el cuerpo como texto
respuesta.content # el cuerpo como bytes
respuesta.url # la dirección final, tras las redirecciones
respuesta.elapsed # cuánto tardó
Esta es la diferencia que más se nota y la que casi nadie usa. Ocho peticiones seguidas a la misma máquina:
Salida en pantalla
urllib 8 peticiones: 3,46 s (433 ms cada una)
requests.get 8 peticiones: 3,27 s (409 ms)
requests.Session 8 peticiones: 1,30 s (162 ms) <- 2,7 veces más rápido
Fíjate en que requests.get suelto no es más rápido que urllib: las dos abren una conexión nueva cada vez, con su negociación de TLS, que es lo que se lleva los 250 milisegundos de diferencia.
Una Session mantiene la conexión abierta y la reutiliza:
python
with requests.Session()as sesion:
sesion.headers['User-Agent']='mi-proyecto/1.0 (contacto@ejemplo.es)'
sesion.headers['Accept']='application/vnd.github+json'for nombre in repositorios:
datos = sesion.get(f'https://api.github.com/repos/{nombre}', timeout=10).json()
Y además de la velocidad, guarda las cabeceras y las cookies, así que las de autenticación se ponen una vez y valen para todas. Eso es lo que hace que la sesión sea el objeto natural cuando escribes un cliente de una API, que es el proyecto de la lección 10.
La regla es sencilla: más de una petición, sesión.
Eso no lanza ninguna excepción. Devuelve una respuesta con status_code 404 y sigue como si nada. requests considera que un 404 es una respuesta legítima del servidor, y tiene razón, y el resultado es que el código que no comprueba nada continúa con un cuerpo de error entre las manos.
La comprobación es una línea:
python
respuesta.raise_for_status()
Salida en pantalla
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/...
Ponla siempre, salvo cuando estés tratando el código a mano.
Las excepciones que sí lanza están bien organizadas:
Lo práctico de esa jerarquía es que todo cuelga de RequestException, así que un guion se protege entero con un solo except:
python
try:
respuesta = sesion.get(url, timeout=(3,10))
respuesta.raise_for_status()except requests.RequestException as e:
registro.error('Falló la petición: %s', e)
Y fíjate en ConnectTimeout, que hereda de las dos cosas a la vez. Eso está pensado: agotar el tiempo al conectar es un problema de conexión y es un tiempo de espera, y así lo atrapan los dos except.
requests no pone tiempo de espera. Igual que urllib. Es la crítica más repetida a la biblioteca y no la van a cambiar, porque rompería código ajeno.
Y aquí es peor, porque es tan cómoda que se escribe requests.get(url) sin pensar, y eso es un programa que se puede quedar colgado para siempre contra un servidor ajeno que acepta la conexión y no contesta.
El parámetro admite dos números, que es lo que conviene usar:
python
requests.get(url, timeout=(3,10))
Salida en pantalla
timeout=(conexión 3, lectura 1) -> ReadTimeout tras 1,3 s
El primero es cuánto esperar a conectar, el segundo cuánto esperar entre trozos de la respuesta. Separarlos permite ser estricto con la conexión, que o va rápido o no va, y generoso con la lectura, que puede tardar si la respuesta es grande.