Cómo cachear respuestas de una API en Python: paso a paso
Llamar repetidamente a la misma API es lento y consume tu límite de peticiones sin necesidad. Hacer caché de respuestas de una API en Python resuelve el problema: la respuesta se guarda localmente y, en la siguiente llamada, se lee del disco en milisegundos. Con la librería requests-cache lo consigues sin cambiar prácticamente nada del código que ya tienes.
Requisitos previos
- Python 3.8 o superior instalado.
- Conocer lo básico de la librería
requests(hacer unGET). - Una API REST pública para probar — usamos
https://api.github.comcomo ejemplo.
Paso 1: Instalar requests-cache
La instalación se hace con pip. requests-cache es un complemento de requests, así que instalamos ambos.
pip install requests requests-cache
Paso 2: Crear una CachedSession
En vez de usar requests.get() directamente, creas una CachedSession. Funciona igual que una sesión normal, pero guarda cada respuesta en un fichero SQLite. El parámetro expire_after define durante cuántos segundos la copia se considera válida.
from requests_cache import CachedSession
session = CachedSession(
cache_name="api_cache", # crea el fichero api_cache.sqlite
backend="sqlite",
expire_after=3600, # la respuesta es válida durante 1 hora
)
respuesta = session.get("https://api.github.com/repos/psf/requests")
print(respuesta.status_code, respuesta.from_cache)
En la primera ejecución verás 200 False: la respuesta vino de la red. En la segunda verás 200 True — vino de la caché.
Paso 3: Medir la ganancia de tiempo
Merece la pena confirmar el efecto con un ejemplo sencillo. La diferencia entre una llamada de red y una lectura de la caché suele ser de cientos de milisegundos frente a unos pocos milisegundos.
import time
for i in range(2):
inicio = time.perf_counter()
r = session.get("https://api.github.com/repos/psf/requests")
duracion = (time.perf_counter() - inicio) * 1000
print(f"llamada {i+1}: from_cache={r.from_cache} ({duracion:.0f} ms)")
Paso 4: Controlar qué se guarda
Guardarlo todo rara vez es lo que quieres. Un error habitual es acabar con una respuesta de error atrapada en la caché. Limita la caché a respuestas 200 y a métodos GET, y define tiempos distintos por tipo de endpoint.
session = CachedSession(
cache_name="api_cache",
expire_after=300, # 5 minutos por defecto
allowable_codes=[200], # solo guarda respuestas correctas
allowable_methods=["GET"], # nunca guarda POST/PUT
stale_if_error=True, # si la API falla, usa la copia antigua
urls_expire_after={
"*/rate_limit": 0, # este endpoint nunca se guarda
"*/repos/*": 86400 # datos de repositorios: 1 día
},
)
stale_if_error=True es la opción que más veces te va a salvar: si la API está caída o devuelve un error 429, tu script sigue funcionando con la última respuesta buena en lugar de romperse.
Paso 5: Forzar una actualización y limpiar la caché
A veces necesitas de verdad el valor más reciente, o quieres ordenar el fichero. Puedes ignorar la caché en una única petición con refresh=True y borrar entradas con los métodos del objeto session.cache.
from requests_cache import CachedSession
session = CachedSession("api_cache", expire_after=3600)
# ignora la caché solo en esta petición y actualiza la copia guardada
r = session.get("https://api.github.com/repos/psf/requests", refresh=True)
# elimina solo las entradas ya expiradas
session.cache.delete(expired=True)
# borra todo
session.cache.clear()
Verificar el resultado
Tres señales confirman que está bien hecho:
- Existe un fichero
api_cache.sqliteen la carpeta de tu script. - La segunda llamada a la misma URL devuelve
r.from_cache == True. - La segunda llamada tarda un orden de magnitud menos.
print(len(session.cache.responses)) # nº de respuestas guardadas
print(list(session.cache.urls())[:5]) # URLs en caché
Si from_cache sigue devolviendo False, comprueba que estás usando session (y no requests.get) y que expire_after no es 0.
Conclusión
Con media docena de líneas ya ahorras llamadas, evitas errores 429 y haces tu script mucho más rápido durante el desarrollo. El siguiente paso natural es llevar esta caché a producción: cambia el backend SQLite por Redis (backend="redis") para compartirla entre varios procesos, y ajusta el expire_after por endpoint. Una pregunta para cerrar: ¿cuáles de los endpoints que usas cambian realmente de minuto a minuto — y cuáles podrías guardar durante un día entero sin que nadie lo notara?