---
title: "HTTPX, Requests y AIOHTTP: comparativa"
description: "Requests es simple y síncrono, AIOHTTP es asíncrono y HTTPX ofrece los dos modos. Uso de proxy, rendimiento y cuál elegir en cada proyecto, con código."
url: https://proxynet.io/es/blog/httpx-vs-requests-vs-aiohttp
date: 2026-09-13
author: "Acar Diveroli"
category: "Comparativas, Web scraping"
lang: es
---

# HTTPX, Requests y AIOHTTP: comparativa

Para enviar peticiones HTTP en Python destacan tres bibliotecas: **Requests**, **HTTPX** y **AIOHTTP**. Las tres hacen el mismo trabajo básico, pero la diferencia entre ellas no aparece al escribir un script pequeño, sino cuando tienes que gestionar miles de peticiones a la vez. En este artículo comparamos las tres bibliotecas en facilidad de uso, concurrencia, soporte de proxy, gestión de errores y ecosistema, damos ejemplos que funcionan para cada una y mostramos el camino para pasar de Requests a código asíncrono.

El código de los ejemplos se probó con Python 3.13 y las versiones Requests 2.32, HTTPX 0.28 y AIOHTTP 3.13.

> **Nota: Respuesta breve**
>
> Requests para scripts pequeños e integraciones con API; HTTPX para proyectos que hoy empiezan síncronos y mañana pueden pasar a asíncronos, y para HTTP/2; AIOHTTP para trabajos de volumen muy alto diseñados como asíncronos desde el principio. Las tres soportan proxy HTTP de forma nativa.

## Las tres bibliotecas en pocas palabras

- **Requests:** la biblioteca HTTP más extendida de Python. Funciona de forma síncrona, es muy fácil de aprender y encontrarás documentación y ejemplos en todas partes. Recuerda que el programa espera en esa línea hasta que termina cada petición.
- **HTTPX:** ofrece una interfaz muy parecida a la de Requests, tanto síncrona como asíncrona. También soporta HTTP/2. Suele ser la forma menos costosa de llevar código existente de Requests a un modelo asíncrono.
- **AIOHTTP:** una biblioteca diseñada como asíncrona desde el principio. Además del cliente incluye un servidor web. Es una opción madura y muy utilizada para trabajos que necesitan una concurrencia muy alta.

## ¿Qué significan síncrono y asíncrono?

Como este concepto está en el centro de la comparación, lo explicamos brevemente. Un cliente **síncrono** envía la petición y espera hasta que llega la respuesta; mientras tanto, el programa no hace nada más. Un cliente **asíncrono**, en cambio, cede el control al bucle de eventos después de enviar la petición; mientras espera la respuesta, otras peticiones pueden salir.

La mayor parte del tiempo de una petición web transcurre en la red, es decir, esperando a que responda el servidor de destino. En código síncrono esa espera se desperdicia; en código asíncrono, en el mismo tiempo se pueden esperar decenas de peticiones más. La diferencia no se nota al descargar cien páginas; al descargar diez mil, se convierte en la diferencia entre horas y minutos.

El precio de esta ventaja es la complejidad: la sintaxis `async`/`await`, el bucle de eventos y la incompatibilidad con bibliotecas que no son asíncronas. Por eso es un error decir que «lo asíncrono siempre es mejor»; si no lo necesitas, el código síncrono es más legible y más fácil de mantener.

## Las diferencias principales en una tabla

| Característica | Requests | HTTPX | AIOHTTP |
|---|---|---|---|
| Uso síncrono | Sí | Sí | No |
| Uso asíncrono | No | Sí | Sí |
| HTTP/2 | No | Sí (con paquete adicional) | No |
| Proxy (HTTP/HTTPS) | Nativo | Nativo | Nativo |
| Proxy SOCKS | Con `requests[socks]` | Con `httpx[socks]` | Con paquete externo |
| Tiempo de espera por defecto | Ninguno (espera indefinidamente) | 5 segundos | 5 minutos (total) |
| Seguir redirecciones | Activado por defecto | Desactivado por defecto | Activado por defecto |
| Reutilización de conexiones | Con `Session` | Con `Client` | Con `ClientSession` |
| Curva de aprendizaje | Muy fácil | Fácil | Media |
| Trabajo más adecuado | Scripts simples, poco volumen | Proyectos mixtos síncronos y asíncronos | Alta concurrencia |

La fila del tiempo de espera por defecto es la diferencia que más se pasa por alto entre las tres bibliotecas. Si en Requests no indicas un tiempo de espera, la petición puede quedarse esperando para siempre; por eso añadir `timeout=` a cada llamada de Requests debería ser un hábito. La diferencia entre `ConnectTimeout` y `ReadTimeout`, y cómo aparece cada uno en el mensaje de error, la mostramos en [Max retries exceeded with url](/es/blog/max-retries-exceeded-with-url).

## Requests: simple y síncrono

Instalación:

```bash
pip install requests
```

Una petición a través de un proxy:

```python
import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"
proxies = {"http": PROXY, "https": PROXY}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=20)
print(response.status_code, response.json())
```

La clave `https` del diccionario `proxies` es el proxy que se usa al ir a direcciones HTTPS. Que el valor empiece por `http://` es correcto: el tráfico HTTPS se tuneliza dentro de la conexión establecida con el proxy.

Si vas a enviar varias peticiones, usa `Session`: las conexiones se reutilizan, las cookies se conservan y el proxy se configura una sola vez:

```python
import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"

with requests.Session() as session:
    session.proxies.update({"http": PROXY, "https": PROXY})
    session.headers.update({"User-Agent": "data-collector-bot/1.0"})
    for path in ["ip", "headers", "user-agent"]:
        r = session.get(f"https://httpbin.org/{path}", timeout=20)
        print(path, r.status_code)
```

El límite de Requests es la concurrencia. Si descargas 1.000 páginas una tras otra, el tiempo total se acerca a la suma del tiempo de cada petición. Es posible aliviarlo con `concurrent.futures.ThreadPoolExecutor`, pero con volúmenes muy altos una biblioteca asíncrona trabaja con más eficiencia. Los ejemplos de esta sección solo envían peticiones GET; en [POST con JSON en Python Requests: equivalencias de cURL](/es/blog/python-requests-post-json) explicamos cómo escribir en Requests peticiones POST con cuerpo JSON, parámetros de consulta con `params=` y cabeceras.

## HTTPX: entre los dos mundos

Instalación:

```bash
pip install httpx
```

El uso síncrono es casi idéntico al de Requests:

```python
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"

with httpx.Client(proxy=PROXY, timeout=20) as client:
    response = client.get("https://httpbin.org/ip")
    print(response.json())
```

> **Nota**
>
> En la versión 0.28 de HTTPX se eliminó el antiguo parámetro `proxies=`. Para un único proxy usa `proxy=`; si necesitas proxies distintos para direcciones distintas, define transportes con `mounts=`. Por eso muchos ejemplos antiguos de internet dan error.

La versión asíncrona del mismo código envía varias peticiones a la vez:

```python
import asyncio
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"
URLS = ["https://httpbin.org/ip"] * 10

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        responses = await asyncio.gather(*(client.get(url) for url in URLS))
        print([r.status_code for r in responses])

asyncio.run(main())
```

Aquí las 10 peticiones no salen una tras otra, sino al mismo tiempo. El tiempo total se acerca al de la petición más lenta.

Para usar HTTP/2 basta con instalar `pip install "httpx[http2]"` y pasar `http2=True` al cliente. Cuando envías muchas peticiones al mismo servidor, la multiplexación sobre una sola conexión reduce el costo de abrir conexiones. Con un proxy, HTTP/2 funciona dentro del túnel `CONNECT`, entre tú y el sitio de destino; el proxy no necesita soportar HTTP/2.

## AIOHTTP: para alta concurrencia

Instalación:

```bash
pip install aiohttp
```

```python
import asyncio
import aiohttp

PROXY = "http://pr.proxynet.io:8000"
AUTH = aiohttp.BasicAuth("user", "pass")
URLS = ["https://httpbin.org/ip"] * 10

async def fetch(session, url):
    async with session.get(url, proxy=PROXY, proxy_auth=AUTH) as response:
        return response.status

async def main():
    async with aiohttp.ClientSession() as session:
        statuses = await asyncio.gather(*(fetch(session, url) for url in URLS))
        print(statuses)

asyncio.run(main())
```

En AIOHTTP el proxy no se define en la sesión, sino en cada petición con el parámetro `proxy=`. Puedes pasar las credenciales por separado con `proxy_auth` o escribirlas dentro de la dirección (`http://user:pass@...`). Con este diseño resulta natural usar un proxy distinto en cada petición; si quieres rotar tu propia lista de IP, AIOHTTP lo pone fácil.

Otra diferencia de AIOHTTP es que el cuerpo de la respuesta debe leerse dentro del gestor de contexto. Llamar a `await response.text()` después de salir del bloque `async with` da error; lee el cuerpo dentro del bloque y guárdalo en una variable.

## Limitar la concurrencia

Al trabajar de forma asíncrona, no dejes la concurrencia sin límite. Lanzar diez mil URL de golpe con `asyncio.gather` fuerza el límite de descriptores de archivo de tu propio sistema y, además, lleva al sitio de destino a aplicar un límite de velocidad de inmediato. Limitar con `asyncio.Semaphore` el número de peticiones abiertas a la vez protege a ambas partes:

```python
import asyncio
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"
URLS = [f"https://httpbin.org/get?i={i}" for i in range(100)]
LIMIT = asyncio.Semaphore(10)

async def fetch(client, url):
    async with LIMIT:
        r = await client.get(url)
        return r.status_code

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        results = await asyncio.gather(*(fetch(client, u) for u in URLS))
        print(sum(1 for s in results if s == 200), "correctas")

asyncio.run(main())
```

En este ejemplo se ponen en cola cien peticiones, pero como mucho diez están abiertas al mismo tiempo. Ajusta el límite a la tolerancia del sitio de destino y al número de conexiones simultáneas que permite tu plan de proxy.

## Gestión de errores y reintentos

En las tres bibliotecas los errores de red llegan como excepciones; sin embargo, los códigos de error HTTP (404, 429, 500) **no** son excepciones por defecto. Comprobar la respuesta es tarea tuya:

- En Requests y HTTPX, la llamada `response.raise_for_status()` lanza una excepción con las respuestas 4xx y 5xx.
- En AIOHTTP se consigue lo mismo con `response.raise_for_status()` o con `raise_for_status=True` al abrir la sesión.

Estos son los códigos que verás con más frecuencia al trabajar con proxies:

| Código | Significado | ¿Qué hacer? |
|---|---|---|
| 407 | Falló la autenticación del proxy | Revisa el usuario, la contraseña y la codificación de caracteres especiales |
| 429 | El sitio de destino aplica un límite de velocidad | Reduce la concurrencia, espera y reintenta, reparte más las IP |
| 403 | El sitio de destino rechaza la petición | Revisa el User-Agent y el tipo de IP |
| 502 / 504 | El proxy no pudo llegar al destino | Reintenta con otra IP de salida |

Escribe la lógica de reintentos con espera exponencial: un segundo en el primer intento, luego dos, luego cuatro. Insistir a intervalos fijos puede terminar en el bloqueo total de una IP que ya recibió un 429.

## Rendimiento: ¿cuál es más rápida?

Al elegir una biblioteca, la pregunta «¿cuál es más rápida?» suele mirar en el lugar equivocado. La mayor parte del tiempo de una petición web transcurre en la red; el tiempo de procesamiento de la propia biblioteca es pequeño en comparación.

Lo decisivo es **cuántas peticiones puedes esperar al mismo tiempo**:

- Un cliente síncrono espera cada petición por turno.
- Un cliente asíncrono puede esperar cientos de peticiones a la vez.

Es decir, para un trabajo de 10 páginas Requests es suficiente. Para uno de 10.000 páginas, el cliente asíncrono de HTTPX o AIOHTTP acorta el tiempo de forma considerable. Dentro de la misma categoría (entre HTTPX asíncrono y AIOHTTP) hay diferencias medibles y AIOHTTP suele ir por delante en rendimiento bruto; aun así, en la mayoría de los proyectos esa diferencia queda eclipsada por el tiempo de respuesta del sitio de destino y la latencia del proxy.

## ¿Cómo combinar proxy y concurrencia?

Las bibliotecas asíncronas pueden enviar muchas peticiones a la vez; pero si todas salen de una sola IP, el sitio de destino no tarda en aplicar un límite de velocidad. A medida que aumentas la concurrencia, también tienes que pensar en cómo repartir las IP:

- **Una IP distinta en cada petición:** con [Proxies rotativos](https://proxynet.io/es/rotating-proxy) usas una sola dirección de entrada y obtienes una IP de salida distinta en cada petición. No hace falta cambiar el código; todos los ejemplos anteriores funcionan tal cual.
- **La misma IP durante toda la sesión:** en flujos con estado, como un inicio de sesión o un carrito, [Proxies de sesión fija](https://proxynet.io/es/sticky-proxy) mantiene la misma IP durante un tiempo determinado.
- **Rotar tu propia lista:** si tienes una lista fija de IP, puedes hacer la rotación en el código; tienes un ejemplo paso a paso en nuestro artículo [¿Cómo rotar proxies en Python?](/es/blog/how-to-rotate-proxies-in-python).
- **Tipo de IP:** en destinos protegidos, el tipo de IP pesa más que la concurrencia; explicamos por qué en [Proxies residenciales y de centro de datos: diferencias](/es/blog/residential-vs-datacenter-proxy).

## Pasar de Requests a HTTPX

Si quieres llevar un proyecto existente de Requests a un modelo asíncrono, HTTPX es el camino más corto. Estas son las diferencias a tener en cuenta en la migración:

| Requests | HTTPX | Nota |
|---|---|---|
| `requests.get(url)` | `httpx.get(url)` | Igual |
| `proxies={"http": p, "https": p}` | `proxy=p` | Un solo parámetro |
| `timeout=None` por defecto | `timeout=5` por defecto | En HTTPX el tiempo de espera viene activado |
| Sigue redirecciones por defecto | Requiere `follow_redirects=True` | HTTPX no las sigue por defecto |
| `Session()` | `Client()` / `AsyncClient()` | Misma lógica |
| `response.json()` | `response.json()` | Igual |

La mayor parte del código funciona sin cambios; las diferencias se concentran en las cinco filas de arriba. Migrar primero con el `Client` síncrono y ejecutar las pruebas, y solo después pasar a `AsyncClient`, reduce el riesgo.

## ¿Cuál deberías elegir?

- **Elige Requests** para scripts pequeños, tareas de automatización, integraciones con API y trabajos en los que la concurrencia no importa.
- **Elige HTTPX** si hoy empiezas en síncrono y es probable que mañana pases a asíncrono, si necesitas HTTP/2 o si quieres los dos modos en el mismo proyecto.
- **Elige AIOHTTP** para trabajos de recogida de datos de volumen muy alto diseñados como asíncronos desde el principio, y cuando la misma aplicación necesita cliente y servidor.

Si con descargar las páginas por HTTP no basta, es decir, si el contenido se carga con JavaScript, estas bibliotecas por sí solas no son suficientes y necesitas automatizar un navegador. Tratamos esta distinción en [Web scraping: ¿JavaScript o Python?](/es/blog/web-scraping-javascript-vs-python). Para la automatización de navegadores en Python, consulta nuestras guías sobre [Selenium](/es/blog/selenium) y [SeleniumBase con proxy](/es/blog/how-to-use-proxy-with-seleniumbase).

## Preguntas frecuentes

### ¿Es difícil pasar de Requests a HTTPX?

Normalmente no. Escribir `httpx.get` en lugar de `requests.get` funciona directamente en la mayoría del código sencillo. Las diferencias están en detalles como que el tiempo de espera viene activado por defecto, que las redirecciones no se siguen por defecto y el nombre del parámetro del proxy. La tabla de migración de arriba recoge estas diferencias.

### ¿Cómo se usa un proxy SOCKS5?

Instala `pip install "requests[socks]"` para Requests o `pip install "httpx[socks]"` para HTTPX y usa el esquema `socks5://` en la dirección del proxy. Si quieres que la resolución DNS también se haga en el lado del proxy, escribe `socks5h://` en Requests. Para las diferencias entre protocolos, consulta nuestro artículo [Proxy SOCKS o HTTP: cuál elegir](/es/blog/socks-vs-http-proxy).

### ¿El código asíncrono siempre es mejor?

No. El código asíncrono es más complejo y sus errores son más difíciles de depurar. Para un trabajo que no se beneficia de verdad de la concurrencia, el código síncrono es más legible y más fácil de mantener.

### ¿Usar hilos con Requests es una alternativa a lo asíncrono?

Para volúmenes medios, sí. Con `ThreadPoolExecutor` puedes ejecutar decenas de peticiones en paralelo y el código sigue siendo síncrono. Con cientos de peticiones simultáneas aumenta el costo de memoria de los hilos; a partir de ahí un cliente asíncrono es más eficiente.

### ¿Tengo que escribir las credenciales del proxy en el código?

No. Las tres bibliotecas leen las variables de entorno `HTTP_PROXY` y `HTTPS_PROXY` (en HTTPX `trust_env` está activado por defecto). Guardar las credenciales en una variable de entorno reduce el riesgo de filtrarlas al subir el código a un repositorio.

### ¿Cuántas peticiones debería abrir a la vez?

No hay un número correcto único. Lo determinan la tolerancia del sitio de destino, las conexiones simultáneas que permite tu plan de proxy y los límites de tu propia máquina. Empezar con diez y subir mientras no aparezcan respuestas 429 es un método seguro.

## En resumen

Las tres bibliotecas hacen bien su trabajo; la elección depende de la escala del proyecto. Requests destaca en trabajos sencillos, HTTPX en flexibilidad y preparación para el futuro, y AIOHTTP en alta concurrencia. Elijas la que elijas, define un tiempo de espera, limita la concurrencia y escribe los reintentos con espera exponencial. A medida que crece el volumen, la estrategia de IP importa tanto como la biblioteca. Para una infraestructura de recogida de datos a gran escala, consulta nuestras [soluciones de extracción de datos](/es/data-scraping).
