DevOps & Scaling

AWS Lambda + CaptchaAI: solução de CAPTCHA sem servidor

Manter um worker ligado 24 horas por dia só para resolver CAPTCHA raramente compensa: ele fica ocioso na maior parte do tempo e você paga pelo servidor do mesmo jeito. O AWS Lambda elimina esse desperdício executando a chamada à API da CaptchaAI apenas quando uma tarefa chega — via API Gateway, fila SQS ou Step Functions — e cobra por milissegundo de execução, não por instância ligada.

Este guia mostra o handler completo em Python, como guardar a chave no Secrets Manager, como descrever a infraestrutura em um template SAM e como processar lotes de CAPTCHA vindos de uma fila SQS. Faz mais sentido para tráfego irregular e scraping autorizado do que para volume alto e constante, onde um worker dedicado pode sair mais barato.


Handler do Lambda: da requisição ao token

O handler abaixo recebe o method e os params do CAPTCHA (por exemplo, googlekey e pageurl para reCAPTCHA), envia a tarefa ao endpoint in.php da CaptchaAI e faz o polling em res.php até obter o token ou estourar o tempo limite. Como usa apenas urllib — biblioteca padrão do Python — não é preciso empacotar dependências externas nem criar uma camada Lambda:

# lambda_function.py
import json
import os
import time
import urllib.request
import urllib.parse


def lambda_handler(event, context):
    """AWS Lambda handler for CaptchaAI solving."""
    api_key = os.environ["CAPTCHAAI_KEY"]

    # Parse input
    body = json.loads(event.get("body", "{}")) if isinstance(event.get("body"), str) else event

    method = body.get("method", "userrecaptcha")
    params = body.get("params", {})

    try:
        token = solve_captcha(api_key, method, params)
        return {
            "statusCode": 200,
            "body": json.dumps({"token": token}),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({"error": str(e)}),
        }


def solve_captcha(api_key, method, params, timeout=90):
    """Solve CAPTCHA using CaptchaAI API."""
    # Submit task
    submit_data = urllib.parse.urlencode({
        "key": api_key,
        "method": method,
        "json": 1,
        **params,
    }).encode()

    req = urllib.request.Request(
        "https://ocr.captchaai.com/in.php",
        data=submit_data,
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        result = json.loads(resp.read())

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Poll for result
    start = time.time()
    while time.time() - start < timeout:
        time.sleep(5)
        poll_url = (
            f"https://ocr.captchaai.com/res.php"
            f"?key={api_key}&action=get&id={task_id}&json=1"
        )
        with urllib.request.urlopen(poll_url, timeout=15) as resp:
            data = json.loads(resp.read())

        if data["request"] != "CAPCHA_NOT_READY":
            if data.get("status") == 1:
                return data["request"]
            raise RuntimeError(f"Solve error: {data['request']}")

    raise TimeoutError("Solve timeout")

Com o handler pronto, o próximo passo é parar de expor a chave de API em texto puro e mover a leitura para o Secrets Manager.


Chave de API protegida com o Secrets Manager

Colocar a chave da CaptchaAI direto no código ou em uma variável de ambiente sem criptografia é o erro mais comum quando um protótipo vira produção. O Secrets Manager guarda o valor criptografado e o Lambda busca a chave em tempo de execução, sem que ela apareça em logs ou no console do CloudFormation:

import json
import boto3


def get_api_key():
    """Retrieve CaptchaAI key from AWS Secrets Manager."""
    client = boto3.client("secretsmanager")
    response = client.get_secret_value(SecretId="captchaai/api-key")
    secret = json.loads(response["SecretString"])
    return secret["api_key"]

Armazene o segredo:

aws secretsmanager create-secret \
  --name captchaai/api-key \
  --secret-string '{"api_key":"YOUR_API_KEY"}'

Conceda ao papel de execução apenas secretsmanager:GetSecretValue restrito a esse ARN — políticas com * dão acesso a todos os segredos da conta.


Infraestrutura como código com o SAM

O template abaixo declara a função, a rota do API Gateway e a permissão de leitura do segredo em um único arquivo, sem clicar em nada no console. Timeout: 120 dá margem para a maioria dos CAPTCHAs; MemorySize: 256 é suficiente porque o handler só faz chamadas HTTP, sem processamento de imagem local:

# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 120
    MemorySize: 256
    Runtime: python3.11

Resources:
  CaptchaSolverFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda_function.lambda_handler
      Environment:
        Variables:
          CAPTCHAAI_KEY: !Sub "{{resolve:secretsmanager:captchaai/api-key:SecretString:api_key}}"
      Events:
        SolveApi:
          Type: Api
          Properties:
            Path: /solve
            Method: post
      Policies:

        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:captchaai/api-key-*"

Outputs:
  SolveApiUrl:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/solve"

Versione esse arquivo junto do código: mudanças de memória, timeout ou IAM ficam no histórico do Git, não escondidas em um clique no console.


Build, deploy e primeiro teste

Com o SAM CLI instalado, sam build empacota o código e sam deploy --guided cria — ou atualiza — a stack no CloudFormation, perguntando região, nome da stack e confirmação de mudanças de IAM na primeira execução. Depois do deploy, teste o endpoint com uma chamada real:

# Build and deploy
sam build
sam deploy --guided

# Test
curl -X POST https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/Prod/solve \
  -H "Content-Type: application/json" \
  -d '{
    "method": "userrecaptcha",
    "params": {
      "googlekey": "SITE_KEY",
      "pageurl": "https://example.com"
    }
  }'

Se a resposta vier com token, o pipeline funciona ponta a ponta: API Gateway, Lambda e API da CaptchaAI.


Processando picos de volume com SQS

Chamar o Lambda direto pelo API Gateway funciona bem para tarefas isoladas, mas em scraping autorizado com centenas de CAPTCHAs por minuto, uma fila SQS entre a origem e o Lambda absorve os picos sem estourar a concorrência da conta. O handler abaixo processa um lote de mensagens, resolve cada CAPTCHA e devolve o resultado por tarefa — incluindo as que falharam, para você decidir se reenvia ou move para uma fila de mensagens não entregues:

import json
import os
import time
import urllib.request
import urllib.parse


def sqs_handler(event, context):
    """Process CAPTCHA tasks from SQS queue."""
    api_key = os.environ["CAPTCHAAI_KEY"]
    results = []

    for record in event["Records"]:
        task = json.loads(record["body"])
        try:
            token = solve_captcha(
                api_key,
                task["method"],
                task["params"],
            )
            results.append({
                "task_id": task.get("id"),
                "status": "success",
                "token": token[:50],
            })
        except Exception as e:
            results.append({
                "task_id": task.get("id"),
                "status": "error",
                "error": str(e),
            })

    return {"results": results}

Rodar os workers em sa-east-1 (São Paulo) reduz o RTT para tráfego originado no Brasil, o que ajuda quando o tempo de resolução já está perto do timeout. Se os logs guardarem dados de usuários, considere a LGPD.


O que considerar antes de ir para produção

Antes de apontar tráfego de produção para essa função, revise os números que definem o comportamento de uma arquitetura serverless — mudam pouco entre projetos, mas custam caro quando ignorados:

Fator Valor
Tempo limite máximo 15 minutos (defina 2 minutos para a maioria dos CAPTCHAs)
Memória 256 MB são suficientes (sem processamento pesado)
Simultaneidade Padrão de 1000 execuções simultâneas (solicite aumento se precisar)
Cold start ~500 ms para Python (insignificante perto do tempo de resolução)
Custo ~US$ 0,0001 por resolução (somente o cálculo do Lambda)
Dependências Use urllib (nativo) para evitar camadas Lambda

Nenhum desses limites é específico da CaptchaAI — valem para qualquer função Lambda com chamada HTTP de saída. A variável que muda por tipo de CAPTCHA é o tempo de resolução, que define o timeout mínimo seguro.


Erros comuns e como resolver

A maior parte dos problemas cai em uma destas quatro causas:

Problema Causa Correção
A função expira Tempo limite do Lambda menor que o tempo de resolução Defina o tempo limite para 120 s ou mais
Permissão negada no segredo Política do IAM ausente Adicione uma política de leitura do Secrets Manager
Cold start adiciona latência Invocações pouco frequentes Use concorrência provisionada
Erro de importação de requests Biblioteca não empacotada no Lambda Use urllib.request (nativo) ou adicione uma camada

Perguntas frequentes

Vale a pena usar Lambda em vez de um worker fixo para resolver CAPTCHA?

Para volume irregular, sim: você paga por invocação (cerca de US$ 0,0001, 256 MB, 60 s), não por hora ociosa de servidor. Para volume alto e constante, compare com o custo de um worker dedicado — a diferença some a partir de um certo patamar de chamadas por segundo.

Como evito que o timeout do Lambda derrube uma resolução em andamento?

Configure o timeout para pelo menos 120 segundos — a maioria dos tipos resolve em 10 a 60 segundos. Para tipos mais lentos, como reCAPTCHA Enterprise, use 180 segundos e confirme a duração real no CloudWatch antes de reduzir a margem.

Preciso de uma VPC para o Lambda chamar a API da CaptchaAI?

Não. Chamadas HTTPS de saída para ocr.captchaai.com funcionam a partir de um Lambda padrão, fora de VPC. Configure VPC só se a função também precisar de recursos internos, como um banco privado — isso adiciona latência de cold start que vale medir antes.

Como lidar com picos de volume sem estourar a concorrência do Lambda?

Coloque uma fila SQS entre a origem das tarefas e a função: o SQS controla a taxa de invocação e absorve picos, em vez de disparar milhares de execuções simultâneas de uma vez. Ajuste o tamanho do lote do trigger conforme o throughput que sua conta suporta.

Faz sentido rodar as funções na região sa-east-1 (São Paulo)?

Se a maior parte do tráfego de origem está no Brasil, sim: reduz o RTT até a função. A chamada à API da CaptchaAI sai pela internet pública — o ganho fica no trecho até o Lambda, não na chamada externa.


Guias relacionados

Para expandir além do Lambda:


Vá sem servidor — obtenha sua chave da CaptchaAI hoje.

Os comentários estão desativados para este artigo.