Puedes resolver un CAPTCHA sin mantener un solo servidor encendido: una función de AWS Lambda recibe la petición, llama a la API de CaptchaAI, devuelve el token y se apaga hasta la siguiente invocación. No pagas por tiempo de inactividad, la concurrencia crece sola cuando llegan más solicitudes y la lógica de resolución vive en un único archivo que puedes desplegar en minutos.
Ese modelo encaja especialmente bien con cargas irregulares. Una agencia en Madrid o en Ciudad de México que hace QA sobre formularios protegidos por CAPTCHA rara vez tiene un volumen constante: hay picos cuando se ejecuta la suite de pruebas y silencio el resto del día. Con Lambda pagas por invocación en USD, un costo predecible frente a un servidor encendido las 24 horas, y esta guía te muestra el handler, el disparo por SQS, la gestión segura de la clave y el despliegue con SAM.
El handler de Lambda que resuelve el CAPTCHA
El núcleo es una sola función. Recibe el evento (venga de API Gateway como cuerpo JSON o como invocación directa), extrae el method y los params, envía la tarea a in.php, sondea res.php hasta obtener el token y devuelve una respuesta HTTP limpia. Todo con urllib de la biblioteca estándar, sin dependencias externas que empaquetar.
# 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")
El patrón de dos pasos (enviar y sondear) es idéntico para reCAPTCHA v2/v3, Cloudflare Turnstile o GeeTest v3: solo cambian el method y los params. Cada solicitud en curso ocupa un thread de tu plan de CaptchaAI, así que la concurrencia de Lambda y tus threads contratados deben ir de la mano.
Protege tu API key con Secrets Manager
Nunca dejes la clave escrita en el código ni en un repositorio. Guárdala en AWS Secrets Manager y recupérala en tiempo de ejecución, o inyéctala como variable de entorno resuelta desde el secreto (lo verás en la plantilla SAM más abajo).
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"]
Crea el secreto una sola vez desde la terminal:
aws secretsmanager create-secret \
--name captchaai/api-key \
--secret-string '{"api_key":"YOUR_API_KEY"}'
Infraestructura como código con una plantilla SAM
Define la función, el endpoint HTTP y los permisos en un solo archivo template.yaml. Fíjate en cómo el valor de CAPTCHAAI_KEY se resuelve directamente desde Secrets Manager: la clave nunca aparece en texto plano y la política de IAM concede solo el permiso de lectura sobre ese secreto.
# 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"
Despliegue y prueba de la función
Con la plantilla lista, sam build empaqueta el código y sam deploy --guided lo publica y te devuelve la URL del endpoint. Después, una llamada con curl confirma que todo el circuito responde de extremo a extremo.
# 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"
}
}'
Procesamiento por lotes con SQS
Cuando no necesitas una respuesta inmediata, desacopla la resolución con una cola. Publica cada tarea en SQS y deja que Lambda las procese en lotes: absorbe los picos, reintenta lo que falla y mantiene bajo control cuántas solicitudes golpean la API a la vez.
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}
Límites y costes de Lambda que conviene conocer
Antes de llevar esto a producción, ten presentes las cifras que definen el comportamiento de la función. El tiempo de espera y la memoria son las palancas que más importan; el arranque en frío es despreciable frente al tiempo que tarda un CAPTCHA en resolverse.
| Factor | Valor |
|---|---|
| Tiempo de espera máximo | 15 minutos (configurado en 2 minutos para la mayoría de los CAPTCHA) |
| Memoria | 256 MB suficientes (sin procesamiento pesado) |
| Simultaneidad | Predeterminado 1000 simultáneos (solicita un aumento si lo necesitas) |
| Arranque en frío | ~500 ms para Python (insignificante frente al tiempo de resolución) |
| Costo | ~$0.0001 por solución (solo cálculo) |
| Dependencias | Usa urllib (integrado) para evitar capas Lambda |
Errores comunes y cómo resolverlos
Casi todos los fallos en producción caen en cuatro categorías: el tiempo de espera, los permisos de IAM, la latencia del arranque en frío y las dependencias que Lambda no trae de serie.
| Problema | Causa | Solución |
|---|---|---|
| La función se agota | Tiempo de espera de Lambda < tiempo de resolución | Sube el tiempo de espera a 120 s o más |
| Permiso denegado en el secreto | Falta la política de IAM | Añade el permiso de lectura de SecretsManager |
| El arranque en frío añade latencia | Invocaciones poco frecuentes | Usa simultaneidad aprovisionada |
Error de importación de requests |
No viene incluida en Lambda | Usa urllib.request (integrado) o añade una capa |
Preguntas frecuentes
¿Cuántos threads de CaptchaAI necesito para la concurrencia de Lambda?
Depende del pico real, no del límite de 1000 invocaciones de Lambda. Cada CAPTCHA en curso ocupa un thread, así que si esperas 15 resoluciones simultáneas, el plan STANDARD ($30/mes, 15 threads) las cubre; para picos mayores, ADVANCE ($90/mes, 50 threads) da margen. Empieza por tu concurrencia observada y escala el plan desde ahí.
¿Cómo evito que un pico de tráfico dispare demasiadas llamadas a la API?
Usa SQS entre la entrada y la función y limita la concurrencia reservada de Lambda a un número acorde a tus threads. Así la cola actúa de amortiguador: las tareas esperan su turno en vez de saturar la API y agotar tus threads de golpe.
¿Qué method y params paso para cada tipo de CAPTCHA?
El handler es agnóstico: reenvía lo que reciba. Para reCAPTCHA usa userrecaptcha con googlekey y pageurl; para Cloudflare Turnstile usa turnstile; para GeeTest v3 usa geetest. Los nombres exactos están en la documentación de la API de CaptchaAI.
¿El arranque en frío ralentiza la resolución?
Apenas. El arranque en frío de Python ronda los 500 ms, un margen insignificante frente a los 10–60 segundos que tarda una resolución típica. Solo si tus invocaciones son muy esporádicas y quieres latencia constante conviene activar la simultaneidad aprovisionada.
Guías relacionadas
Pasa a serverless: obtén tu API key de CaptchaAI hoy.