Como implementar uma fila de requisições para consultas de CPF em lote

Implemente uma fila de requisições para consultar CPFs em lote de forma eficiente. Controle custos de excedentes e processe grandes volumes sem erros.

Lucas Vieira
Lucas Vieira
··7 min de leitura
Como implementar uma fila de requisições para consultas de CPF em lote

Para consultar CPFs em lote com controle de custo, implemente uma fila de requisições que processa as consultas de forma ordenada, espaçando as chamadas à API e tratando falhas automaticamente — evitando disparar centenas de consultas de uma vez e incorrer em cobranças de excedente desnecessárias.

Introdução

Consultar CPFs em lote é uma necessidade frequente em operações de atualização cadastral, migração de dados e validação de bases. Enviar todas as requisições simultaneamente, no entanto, resulta em erros de timeout, consumo acelerado da cota mensal e custos de excedente difíceis de controlar. A solução é implementar uma fila de requisições que processa as consultas de forma ordenada, respeitando o ritmo da API e tratando falhas automaticamente.


Por que usar filas para consultas em lote

O processamento direto sem fila causa problemas previsíveis:

  • Quando os créditos do Grátis terminam, a API pausa com HTTP 403 e a mensagem "Limite de créditos excedido". Nos planos pagos a cobrança extra, se o plano tiver excedente, sai na fatura seguinte (veja a página de preços). Requisições além do limite por minuto recebem HTTP 429 com Retry-After.
  • Timeout em cascata -- muitas conexões simultâneas aumentam a latência de todas as requisições
  • Perda de progresso -- se o script falhar no meio, não há como retomar de onde parou
  • Requisições desperdiçadas -- falhas de rede e timeouts consomem tentativas sem resultado útil
  • Sobrecarga do servidor -- picos de requisições prejudicam a experiência de todos os usuários da API

A fila resolve tudo isso ao serializar e espaçar as chamadas, dando ao sistema previsibilidade de custo e confiabilidade de resultado.


Arquitetura da fila de processamento

A fila deve gerenciar o ciclo de vida completo de cada requisição:

ComponenteResponsabilidadeTecnologia sugerida
ProducerEnfileirar CPFs para consultaScript de importação
QueueArmazenar requisições pendentesRedis / arquivo JSON
WorkerProcessar requisições uma a umaConsumer com rate limit
Rate LimiterControlar frequência de chamadasToken bucket / sleep
Dead Letter QueueArmazenar falhas para reprocessamentoFila secundária
Result StoreSalvar resultados processadosBanco de dados / CSV

Implementação completa com Python

import requests
import json
import time
from collections import deque
from datetime import datetime
from pathlib import Path

class CPFBatchQueue:
    def __init__(self, api_key: str, rate_limit_per_min: float = 30):
        self.api_key = api_key
        self.intervalo = 60.0 / rate_limit_per_min
        self.fila = deque()
        self.resultados = []
        self.falhas = deque()
        self.max_retries = 3
        self.arquivo_progresso = Path("progresso_lote.json")

    def enfileirar(self, cpfs: list):
        for cpf in cpfs:
            cpf_limpo = cpf.replace(".", "").replace("-", "").strip()
            if len(cpf_limpo) == 11:
                self.fila.append({"cpf": cpf_limpo, "tentativas": 0})
        print(f"{len(self.fila)} CPFs enfileirados para processamento")

    def consultar(self, cpf: str) -> dict:
        response = requests.get(
            f"https://api.cpfhub.io/cpf/{cpf}",
            headers={"x-api-key": self.api_key},
            timeout=10
        )

        if response.status_code == 200:
            dados = response.json()
            if dados["success"]:
                return {
                    "status": "OK",
                    "cpf": dados["data"]["cpf"],
                    "nome": dados["data"]["name"],
                    "nascimento": dados["data"]["birthDate"],
                    "genero": dados["data"]["gender"]
                }
            return {"status": "NAO_ENCONTRADO", "cpf": cpf}
        else:
            return {"status": "ERRO", "cpf": cpf, "http": response.status_code}

    def processar(self):
        total = len(self.fila)
        processados = 0

        print(f"Iniciando processamento de {total} CPFs...")
        print(f"Cadência: {60/self.intervalo:.0f} req/min")
        print("=" * 50)

        while self.fila:
            item = self.fila.popleft()
            resultado = self.consultar(item["cpf"])

            if resultado["status"] == "ERRO":
                item["tentativas"] += 1
                if item["tentativas"] < self.max_retries:
                    self.fila.append(item)
                    time.sleep(self.intervalo * 3)  # Backoff
                else:
                    self.falhas.append(item)
            else:
                self.resultados.append(resultado)

            processados += 1
            if processados % 10 == 0:
                self.salvar_progresso()
                print(f"Progresso: {processados}/{total} "
                        f"({(processados/total)*100:.1f}%)")

            time.sleep(self.intervalo)

        self.salvar_progresso()
        self.relatorio_final(total)

    def salvar_progresso(self):
        with open(self.arquivo_progresso, "w") as f:
            json.dump({
                "timestamp": datetime.now().isoformat(),
                "processados": len(self.resultados),
                "pendentes": len(self.fila),
                "falhas": len(self.falhas)
            }, f, indent=2)

    def relatorio_final(self, total):
        ok = sum(1 for r in self.resultados if r["status"] == "OK")
        nao_encontrados = sum(1 for r in self.resultados if r["status"] == "NAO_ENCONTRADO")
        print("\n" + "=" * 50)
        print(f"Total processados: {len(self.resultados)}")
        print(f"Encontrados: {ok}")
        print(f"Não encontrados: {nao_encontrados}")
        print(f"Falhas definitivas:{len(self.falhas)}")

# Uso
fila = CPFBatchQueue("SUA_CHAVE_AQUI")  # 30 req/min, teto padrão
fila.enfileirar(["12345678909", "98765432100", "11122233344"])
fila.processar()

Os 50 créditos do Grátis, ao se esgotarem, pausam o serviço: HTTP 403, "Limite de créditos excedido". Se o plano for pago e prever excedente, o crédito extra é faturado depois, como na página de preços. O teto de chamadas por minuto devolve HTTP 429 e pede espera pelo Retry-After. Espaçar as requisições com a fila é a forma mais eficiente de manter o custo sob controle e evitar surpresas na fatura.


Estratégias de retry e backoff

Quando a API retorna erro, a estratégia de retry determina a eficiência da recuperação:

  • Retry imediato -- apenas para erros de rede transitórios (timeout, connection reset)
  • Backoff linear -- aumentar o intervalo em incrementos fixos (1s, 2s, 3s) para erros persistentes
  • Backoff exponencial -- dobrar o intervalo a cada tentativa (1s, 2s, 4s, 8s) para erros recorrentes
  • Jitter aleatório -- adicionar variação aleatória ao intervalo para evitar thundering herd
  • Dead letter queue -- após N tentativas, mover o item para uma fila separada de análise manual

Otimizações para grandes volumes

Para processamentos com milhares ou milhões de CPFs:

  • Deduplicação prévia -- remova CPFs duplicados antes de enfileirar para economizar cota
  • Workers paralelos -- execute múltiplos workers respeitando um budget de requisições global compartilhado
  • Checkpoint periódico -- salve o estado da fila a cada N processamentos para permitir retomada
  • Horários estratégicos -- execute lotes grandes em períodos de menor tráfego para minimizar latência
  • Priorização -- ordene a fila por criticidade, processando primeiro os CPFs mais urgentes

Perguntas frequentes

Por que usar uma fila em vez de disparar todas as consultas de uma vez?

Os 50 créditos do Grátis, ao se esgotarem, pausam o serviço: HTTP 403, "Limite de créditos excedido". Se o plano for pago e prever excedente, o crédito extra é faturado depois, como na página de preços. O teto de chamadas por minuto devolve HTTP 429 e pede espera pelo Retry-After. A fila serializa as chamadas em um ritmo controlado, tornando o custo previsível e o processo retomável em caso de falha.

A API CPFHub.io bloqueia requisições em lote?

No plano Grátis, sim: ao acabar os 50 créditos do mês as consultas pausam e a API responde HTTP 403 com a mensagem "Limite de créditos excedido". Nos planos pagos pode haver excedente, cobrado na fatura seguinte pelo valor da página de preços. O limite por minuto é separado e devolve HTTP 429 com o header Retry-After. A fila não serve para evitar bloqueio — serve para controlar o custo dessas cobranças extras.

Como retomar um lote interrompido no meio?

Salve o progresso periodicamente em um arquivo JSON com a lista de CPFs já processados e os ainda pendentes. Ao reiniciar, carregue o checkpoint e enfileire apenas os pendentes. O exemplo Python acima já implementa essa lógica com salvar_progresso() a cada 10 consultas.

Qual cadência de requisições é recomendada para lotes grandes?

Depende do volume e do prazo. No limite padrão de 30 requisições por minuto, 1.000 CPFs em chamadas individuais levam cerca de 34 minutos. Para 10.000 CPFs, use a consulta em lote: um envio conta como uma requisição da Consulta Simples.


Conclusão

Uma fila de requisições bem implementada transforma consultas caóticas em um processo ordenado, previsível e econômico. Com cadência controlada, retry inteligente e checkpoint de progresso, você processa grandes volumes de CPFs sem desperdiçar cota nem gerar cobranças de excedente desnecessárias. Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e valide CPFs em lote com custo totalmente sob controle.

CPFHub.io

Pronto para integrar a API?

50 créditos gratuitos para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

WhatsAppFale conosco via WhatsApp