Como fazer cache de respostas de uma API em Python
Chamar repetidamente a mesma API é lento e gasta o teu limite de pedidos sem necessidade. Fazer cache de respostas de uma API em Python resolve o problema: a resposta fica guardada localmente e, no pedido seguinte, é lida do disco em milissegundos. Com a biblioteca requests-cache consegues isto sem alterar praticamente nada no código que já tens.
Pré-requisitos
- Python 3.8 ou superior instalado.
- Saber o básico da biblioteca
requests(fazer umGET). - Uma API REST pública para testar — usamos
https://api.github.comcomo exemplo.
Passo 1: Instalar o requests-cache
A instalação é feita com pip. O requests-cache é um complemento do requests, por isso instalamos os dois.
pip install requests requests-cache
Passo 2: Criar uma CachedSession
Em vez de usares requests.get() diretamente, crias uma CachedSession. Ela funciona exatamente como uma sessão normal, mas guarda cada resposta num ficheiro SQLite. O parâmetro expire_after define durante quantos segundos a cópia é considerada válida.
from requests_cache import CachedSession
session = CachedSession(
cache_name="api_cache", # cria o ficheiro api_cache.sqlite
backend="sqlite",
expire_after=3600, # a resposta é válida durante 1 hora
)
resposta = session.get("https://api.github.com/repos/psf/requests")
print(resposta.status_code, resposta.from_cache)
Na primeira execução vês 200 False: a resposta veio da rede. Na segunda, vês 200 True — veio do cache.
Passo 3: Medir o ganho de tempo
Vale a pena confirmar o efeito com um exemplo simples. A diferença entre uma chamada de rede e uma leitura do cache costuma ser de centenas de milissegundos para poucos milissegundos.
import time
for i in range(2):
inicio = time.perf_counter()
r = session.get("https://api.github.com/repos/psf/requests")
duracao = (time.perf_counter() - inicio) * 1000
print(f"chamada {i+1}: from_cache={r.from_cache} ({duracao:.0f} ms)")
Passo 4: Controlar o que é guardado
Guardar tudo é raramente o que queres. Um erro comum é ficar com uma resposta de erro presa no cache. Restringe o cache a respostas 200 e a métodos GET, e define tempos diferentes por tipo de endpoint.
session = CachedSession(
cache_name="api_cache",
expire_after=300, # 5 minutos por omissão
allowable_codes=[200], # só guarda respostas com sucesso
allowable_methods=["GET"], # nunca guarda POST/PUT
stale_if_error=True, # se a API falhar, usa a cópia antiga
urls_expire_after={
"*/rate_limit": 0, # este endpoint nunca é guardado
"*/repos/*": 86400 # dados de repositórios: 1 dia
},
)
A opção stale_if_error=True é a que mais te vai salvar: se a API estiver em baixo ou devolver erro 429, o teu script continua a funcionar com a última resposta boa em vez de rebentar.
Passo 5: Forçar atualização e limpar o cache
Às vezes precisas mesmo do valor mais recente, ou queres arrumar o ficheiro. Podes ignorar o cache num único pedido com refresh=True e apagar entradas com os métodos do objeto session.cache.
from requests_cache import CachedSession
session = CachedSession("api_cache", expire_after=3600)
# ignora o cache apenas neste pedido e atualiza a cópia guardada
r = session.get("https://api.github.com/repos/psf/requests", refresh=True)
# remove só as entradas já expiradas
session.cache.delete(expired=True)
# apaga tudo
session.cache.clear()
Verificar o resultado
Três sinais confirmam que ficou bem feito:
- Existe um ficheiro
api_cache.sqlitena pasta do teu script. - A segunda chamada ao mesmo URL devolve
r.from_cache == True. - O tempo da segunda chamada é uma ordem de grandeza menor.
print(len(session.cache.responses)) # nº de respostas guardadas
print(list(session.cache.urls())[:5]) # URLs em cache
Se from_cache continuar a dar False, confirma que estás a usar a session (e não requests.get) e que o expire_after não é 0.
Conclusão
Com meia dúzia de linhas passaste a poupar chamadas, a fugir a erros 429 e a tornar o teu script muito mais rápido em desenvolvimento. O passo seguinte natural é levar este cache para produção: troca o backend SQLite por Redis (backend="redis") para o partilhares entre vários processos, e afina o expire_after por endpoint. Uma pergunta para fechar: quais dos endpoints que usas mudam mesmo de minuto a minuto — e quais é que podias guardar durante um dia inteiro sem ninguém dar por isso?