Is there a way to perform asynchronous requests?

Peticiones Asíncronas en Python: Guía Completa

23/02/2011

Valoración: 4.62 (4767 votos)

En el desarrollo de software moderno, especialmente en aplicaciones web y procesamiento de datos, es común enfrentarse a la necesidad de realizar múltiples peticiones HTTP a servicios externos o APIs. El enfoque tradicional, conocido como síncrono, procesa cada una de estas peticiones de forma secuencial: una debe terminar completamente antes de que la siguiente pueda empezar. Este método, aunque simple de implementar, se convierte en un cuello de botella significativo cuando el número de peticiones aumenta o cuando las APIs tienen tiempos de respuesta variables, resultando en una aplicación lenta y una mala experiencia de usuario. Aquí es donde la programación asíncrona cambia las reglas del juego.

How Asynchronous requests can improve the performance of Python applications?
Asynchronous requests is a very great and powerful way to improve the performance of the Python applications, especially in the situations when we are dealing with multiple external API calls. By using aiohttp python library, we can easily implement non-blocking HTTP requests and then significantly reduce execution time.

Las peticiones asíncronas permiten que tu aplicación envíe múltiples solicitudes de forma casi simultánea, sin tener que esperar la respuesta de una para iniciar la siguiente. Mientras una petición está esperando la respuesta del servidor (un tiempo muerto conocido como I/O wait), el programa puede aprovechar ese tiempo para enviar otras peticiones o realizar otras tareas. Esto conduce a una reducción drástica en el tiempo total de ejecución y a un uso mucho más eficiente de los recursos del sistema. En esta guía completa, exploraremos cómo implementar peticiones HTTP asíncronas en Python utilizando las librerías más populares y efectivas: aiohttp y HTTPX.

Índice de Contenido

¿Por qué y cuándo usar peticiones asíncronas?

La principal ventaja de la asincronía es el rendimiento. Imagina que tienes que descargar 100 imágenes de un servidor. De forma síncrona, si cada descarga tarda 1 segundo, el proceso total tardará 100 segundos. De forma asíncrona, podrías iniciar las 100 descargas a la vez. Aunque cada una siga tardando aproximadamente 1 segundo, el tiempo total será mucho más cercano a 1 segundo (más el pequeño overhead de gestión) que a 100. La diferencia es monumental.

Este paradigma es ideal para tareas "I/O-bound" (limitadas por entrada/salida), que son aquellas donde el programa pasa la mayor parte del tiempo esperando por recursos externos, como:

  • Realizar llamadas a APIs REST.
  • Consultar bases de datos.
  • Leer o escribir archivos en el disco.
  • Comunicarse a través de la red con otros servicios.

Por el contrario, para tareas "CPU-bound" (limitadas por la CPU), como cálculos matemáticos complejos o procesamiento de imágenes, la asincronía no ofrece los mismos beneficios, y otras técnicas como el multiprocessing serían más adecuadas.

Conceptos Fundamentales: `async`, `await` y `asyncio`

Antes de sumergirnos en las librerías, es crucial entender las herramientas que Python nos proporciona de forma nativa desde la versión 3.5 para manejar la asincronía.

  • async def: Esta sintaxis se utiliza para definir una función como una corutina. Una corutina es una función especial que puede ser pausada y reanudada.
  • await: Esta palabra clave se usa dentro de una corutina para pausar su ejecución y ceder el control al bucle de eventos hasta que la operación que se está "esperando" (awaiting) se complete. Solo se puede usar dentro de una función `async def`.
  • asyncio: Es la librería de la biblioteca estándar de Python para escribir código concurrente usando la sintaxis `async/await`. Proporciona la infraestructura subyacente, como el bucle de eventos (event loop) que gestiona y distribuye la ejecución de las diferentes tareas.

Implementación con `aiohttp`

aiohttp es una de las librerías más veteranas y robustas para realizar operaciones de cliente y servidor HTTP de forma asíncrona. Aunque su API es diferente a la popular librería `requests`, es extremadamente potente y eficiente.

Instalación

Para comenzar, necesitas instalar la librería. Puedes hacerlo fácilmente a través de pip:

pip install aiohttp

El Rol de `ClientSession`

Un componente central de aiohttp es el objeto `ClientSession`. En lugar de crear una nueva conexión para cada petición, `ClientSession` gestiona un pool de conexiones, reutilizándolas entre múltiples solicitudes al mismo host. Esto reduce significativamente la sobrecarga (overhead) de establecer y cerrar conexiones TCP, mejorando aún más el rendimiento.

Ejemplo Práctico con `aiohttp`

Veamos un ejemplo donde consultamos tres APIs diferentes de forma concurrente para obtener datos.

import aiohttp import asyncio import time async def obtener_datos(session, url): print(f"Iniciando tarea para: {url}") try: async with session.get(url) as response: # Lanza una excepción para códigos de error HTTP (4xx o 5xx) response.raise_for_status() datos = await response.json() print(f"Tarea completada para: {url}") return datos except aiohttp.ClientError as e: print(f"Error al obtener datos de {url}: {e}") return None async def main(): urls = [ 'https://api.github.com/', 'https://api.spacexdata.com/v4/launches/latest', 'https://jsonplaceholder.typicode.com/todos/1' ] async with aiohttp.ClientSession() as session: # Creamos una lista de tareas (corutinas) para ejecutar tareas = [obtener_datos(session, url) for url in urls] # asyncio.gather ejecuta todas las tareas concurrentemente y espera a que todas terminen resultados = await asyncio.gather(*tareas) print("\n--- Resultados Obtenidos ---") for resultado in resultados: if resultado: print(f"{str(resultado)[:80]}...") # Imprimimos una parte del resultado if __name__ == '__main__': tiempo_inicio = time.time() # asyncio.run inicia el bucle de eventos y ejecuta la corutina main asyncio.run(main()) tiempo_fin = time.time() print(f"\nTiempo total de ejecución: {tiempo_fin - tiempo_inicio:.2f} segundos")

En este código, `asyncio.gather` es la clave. Recibe una serie de tareas y las ejecuta concurrentemente. El programa no esperará a que la petición a GitHub termine para iniciar la de SpaceX. Todas se lanzan a la vez, y el tiempo total de ejecución será cercano al de la petición más lenta, no a la suma de todas ellas.

Una Alternativa Moderna: `HTTPX`

HTTPX es una librería más reciente que ha ganado una enorme popularidad por una razón principal: ofrece una API prácticamente idéntica a la de `requests`, pero con soporte tanto para operaciones síncronas como asíncronas. Esto la convierte en la opción ideal para proyectos que ya usan `requests` y quieren migrar a un modelo asíncrono con el mínimo esfuerzo, o para nuevos proyectos que desean la flexibilidad de usar ambos paradigmas.

Is there a way to perform asynchronous requests?
Unfortunately, as far as I know, the requests library is not equipped for performing asynchronous requests. You can wrap async/await syntax around requests, but that will make the underlying requests no less synchronous. If you want true async requests, you must use other tooling that provides it. One such solution is aiohttp (Python 3.5.3+).

Instalación

pip install httpx

Ejemplo Práctico con `HTTPX`

Reescribamos el ejemplo anterior usando `HTTPX` para ver las similitudes.

import httpx import asyncio import time async def obtener_datos(client, url): print(f"Iniciando tarea para: {url}") try: response = await client.get(url) response.raise_for_status() datos = response.json() print(f"Tarea completada para: {url}") return datos except httpx.RequestError as e: print(f"Error al obtener datos de {url}: {e}") return None async def main(): urls = [ 'https://api.github.com/', 'https://api.spacexdata.com/v4/launches/latest', 'https://jsonplaceholder.typicode.com/todos/1' ] async with httpx.AsyncClient() as client: tareas = [obtener_datos(client, url) for url in urls] resultados = await asyncio.gather(*tareas) print("\n--- Resultados Obtenidos ---") for resultado in resultados: if resultado: print(f"{str(resultado)[:80]}...") if __name__ == '__main__': tiempo_inicio = time.time() asyncio.run(main()) tiempo_fin = time.time() print(f"\nTiempo total de ejecución: {tiempo_fin - tiempo_inicio:.2f} segundos")

Como puedes observar, la estructura es casi idéntica. La principal diferencia radica en el uso de `httpx.AsyncClient` en lugar de `aiohttp.ClientSession`, y en cómo se manejan las respuestas y los errores, que es más cercano al estilo de `requests`.

Tabla Comparativa: `aiohttp` vs. `HTTPX`

CaracterísticaaiohttpHTTPX
APIAPI propia, diseñada específicamente para asyncio.API dual (síncrona y asíncrona) muy similar a `requests`.
Facilidad de transiciónCurva de aprendizaje si vienes de `requests`.Muy fácil de adoptar para usuarios de `requests`.
Soporte HTTP/2Sí, aunque puede requerir configuración.Soporte nativo y sencillo de usar.
EcosistemaMaduro y robusto, con un framework web completo.En crecimiento, enfocado en ser el mejor cliente HTTP.

Manejo de Errores: Clave para la Robustez

En un sistema concurrente, es casi una certeza que algunas peticiones fallarán por diversas razones (errores de red, timeouts, errores del servidor, etc.). Un buen manejo de errores es fundamental para que la aplicación no se detenga por completo ante un fallo aislado. Como se muestra en los ejemplos, el uso de bloques `try...except` dentro de la corutina que realiza la petición individual es la estrategia correcta. Esto permite capturar la excepción, registrar el error y permitir que las demás tareas continúen su ejecución sin interrupciones.

Preguntas Frecuentes (FAQ)

¿Cuándo debería seguir usando peticiones síncronas?

Para scripts sencillos con una o muy pocas peticiones, o cuando la lógica del programa es estrictamente secuencial y no se puede beneficiar del paralelismo, el código síncrono con `requests` sigue siendo más simple y perfectamente válido.

¿Es `HTTPX` el nuevo estándar en lugar de `requests`?

`HTTPX` se posiciona como el sucesor espiritual de `requests`, ofreciendo todo lo que `requests` hace bien, más el soporte asíncrono. Para nuevos proyectos, es una elección excelente y muy recomendada.

¿Qué sucede si una de mis 100 peticiones asíncronas falla?

Si has implementado un manejo de errores adecuado (con `try...except` en cada tarea), solo esa tarea fallará. `asyncio.gather` continuará esperando a que las otras 99 terminen y te devolverá los resultados de las exitosas y la excepción (o el valor que retornes en el bloque `except`) de la fallida.

¿Necesito ser un experto en `asyncio` para usar estas librerías?

No. Un conocimiento básico de `async def`, `await` y cómo iniciar el programa con `asyncio.run` es suficiente para empezar a aprovechar los beneficios. Las librerías como `aiohttp` y `HTTPX` abstraen gran parte de la complejidad del bucle de eventos.

Conclusión

Adoptar peticiones asíncronas en Python es una de las formas más efectivas de mejorar drásticamente el rendimiento de aplicaciones que dependen de operaciones de red. Librerías como aiohttp y HTTPX hacen que la implementación de este poderoso paradigma sea accesible y manejable. Mientras que `aiohttp` es una opción robusta y probada en batalla, `HTTPX` ofrece una transición increíblemente suave para aquellos familiarizados con `requests`, junto con características modernas. Al dominar estas herramientas, estarás equipado para construir aplicaciones en Python mucho más rápidas, eficientes y escalables.

Si quieres conocer otros artículos parecidos a Peticiones Asíncronas en Python: Guía Completa puedes visitar la categoría Juegos.

Subir