Programación asincrona con aiohttp
En este ejemplo vemos como realizar peticiones de manera asíncrona a una API externa. En concreto veremos como usar la API de Github para obtener el número de seguidores de diferentes cuentas. Este tipo de peticiones se puede realizar de dos formas:
- 🐌 Sincrona: Enviamos una petición y no enviamos la siguiente hasta que la anterior no ha acabado. Suele ser lo más lento ya que mientras el servidor nos contesta nos quedamos parados sin hacer nada.
- 🏎️ Asíncrona: Enviamos todas las peticiones a la vez y esperamos a que nos devuelven respuesta. Suele se mas rápido ya que realizamos el procesado en paralelo.
🐌 La forma mas sencilla es hacerlo de manera síncrona usando requests
. Simplemente hacemos una petición HTTP a la API y tomamos el campo followers
. Hemos simplificado la gestión de errores. Dependiendo de tu caso tal vez quieras manejas las excepciones.
import requests
url_base = "https://api.github.com/users/{}"
def get_seguidores(usuario):
url = url_base.format(usuario)
r = requests.get(url)
return usuario, r.json()["followers"]
Y ahora podemos obtener los followers de tres usuarios distintos. Puedes de hecho acceder al mismo contenido si pones lo siguiente en tu navegador:
api.github.com/users/python
api.github.com/users/google
api.github.com/users/firebase
for usuario in ["python", "google", "firebase"]:
usuario, followers = get_seguidores(usuario)
print(f"Followers de {usuario}: {followers}")
# Followers de python: 22840
# Followers de google: 46406
# Followers de firebase: 4113
Pero esta solución hace todas las peticiones de manera secuencial. Hace petición, espera, siguiente petición, espera, etc. Esto hace que no sea eficiente. Con cientos de usuarios sería muy lento.
🏎️ Para mejorarlo podemos hacerlo de manera asíncrona con asyncio
y aiohttp
. De esta manera podemos lanzar varias peticiones a la vez y después esperar. Esto es mas eficiente porque las peticiones se realizan en paralelo.
import asyncio
import aiohttp
url_base = "https://api.github.com/users/{}"
usuarios = ["python", "google", "firebase"]
async def get_seguidores(session, usuario):
url = url_base.format(usuario)
async with session.get(url) as r:
return usuario, int((await r.json())["followers"])
async def get_todos(usuarios):
async with aiohttp.ClientSession() as s:
tasks = [get_seguidores(s, u) for u in usuarios]
return await asyncio.gather(*tasks)
r = asyncio.run(get_todos(usuarios))
for usuario, rr in r:
print(f"Followers de {usuario}: {rr}")
Si pruebas de las dos maneras veras como la forma asíncrona es mas rápida. Y cuantos mas grande sea usuarios
mas se notará la diferencia.
✏️ Ejercicios:
- Modifica
fetch_all
para que procese un máximo deMAX_TASKS
a la vez. Esto puede ser necesario ya que si tenemos millones de usuarios no es buena idea lanzar millones de funciones asíncronas a la vez. - Gestiona todas las excepciones que puedan ocurrir en el caso asíncrono. Si por ejemplo una petición falla, gestiónalo de tal forma que no se eche todo a perder.