La consulta para obtener resultados de CAPTCHA vincula los subprocesos y crea un estrecho acoplamiento entre su raspador y el proceso de resolución. AWS SNS (Servicio de notificación simple) desacopla estas preocupaciones: CaptchaAI envía resultados a su devolución de llamada, que publica en SNS, y cualquier número de consumidores intermedios reaccionan de forma independiente.
Descripción general de la arquitectura
[Scraper] → Submit CAPTCHA → [CaptchaAI API]
↓
Solve completes
↓
Callback → [API Gateway + Lambda]
↓
Publish → [SNS Topic]
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
[SQS Queue] [Lambda Logger] [Email Alert]
(result store) (audit trail) (on failure)
SNS proporciona distribución: un resultado CAPTCHA activa múltiples consumidores sin que el controlador de devolución de llamada los sepa.
Paso 1: crear el tema de SNS
CLI de AWS
aws sns create-topic --name captcha-results --output text
# Returns: arn:aws:sns:us-east-1:123456789:captcha-results
Python (boto3)
import boto3
sns = boto3.client("sns", region_name="us-east-1")
response = sns.create_topic(Name="captcha-results")
topic_arn = response["TopicArn"]
print(f"Topic ARN: {topic_arn}")
Paso 2: construir el receptor de devolución de llamada
Esta función Lambda recibe los resultados de la devolución de llamada CaptchaAI y los publica en SNS.
Python (controlador Lambda)
import json
import os
import boto3
sns = boto3.client("sns")
TOPIC_ARN = os.environ["SNS_TOPIC_ARN"]
def lambda_handler(event, context):
"""Receive CaptchaAI callback and publish to SNS."""
# Parse query parameters from API Gateway
params = event.get("queryStringParameters", {}) or {}
task_id = params.get("id", "")
solution = params.get("code", "")
if not task_id or not solution:
return {"statusCode": 400, "body": "Missing id or code"}
# Publish to SNS
message = {
"task_id": task_id,
"solution": solution,
"status": "solved"
}
sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps(message),
Subject="captcha-solved",
MessageAttributes={
"task_id": {
"DataType": "String",
"StringValue": task_id
}
}
)
return {"statusCode": 200, "body": "OK"}
JavaScript (controlador Lambda)
const { SNSClient, PublishCommand } = require("@aws-sdk/client-sns");
const sns = new SNSClient({ region: "us-east-1" });
const TOPIC_ARN = process.env.SNS_TOPIC_ARN;
exports.handler = async (event) => {
const params = event.queryStringParameters || {};
const taskId = params.id;
const solution = params.code;
if (!taskId || !solution) {
return { statusCode: 400, body: "Missing id or code" };
}
const message = {
task_id: taskId,
solution: solution,
status: "solved",
};
await sns.send(
new PublishCommand({
TopicArn: TOPIC_ARN,
Message: JSON.stringify(message),
Subject: "captcha-solved",
MessageAttributes: {
task_id: { DataType: "String", StringValue: taskId },
},
})
);
return { statusCode: 200, body: "OK" };
};
Paso 3: envíe CAPTCHA con la URL de devolución de llamada
Apunte pingback de CaptchaAI a su punto final de API Gateway:
Python
import os
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
CALLBACK_URL = os.environ["CALLBACK_GATEWAY_URL"] # API Gateway URL
def submit_captcha(sitekey, pageurl):
"""Submit CAPTCHA with SNS-backed callback."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"pingback": CALLBACK_URL,
"json": 1
})
data = resp.json()
if data.get("status") == 1:
return data["request"] # task_id
raise RuntimeError(f"Submit failed: {data.get('request')}")
Paso 4: suscribir a los consumidores
Cola SQS (almacenamiento de resultados)
# Subscribe an SQS queue to receive all results
sqs_arn = "arn:aws:sqs:us-east-1:123456789:captcha-results-queue"
sns.subscribe(
TopicArn=topic_arn,
Protocol="sqs",
Endpoint=sqs_arn
)
Lambda (registrador de auditoría)
# Subscribe a Lambda for audit logging
lambda_arn = "arn:aws:lambda:us-east-1:123456789:function:captcha-audit-logger"
sns.subscribe(
TopicArn=topic_arn,
Protocol="lambda",
Endpoint=lambda_arn
)
Correo electrónico (alertas de fallos)
# Subscribe email for error notifications with filter
sns.subscribe(
TopicArn=topic_arn,
Protocol="email",
Endpoint="ops@example.com"
)
Paso 5: Consumir los resultados de SQS
Su raspador lee soluciones de SQS en lugar de sondear CaptchaAI:
Python
import json
import boto3
sqs = boto3.client("sqs", region_name="us-east-1")
QUEUE_URL = os.environ["SQS_QUEUE_URL"]
def get_solved_captcha(timeout=30):
"""Wait for a CAPTCHA solution from the SQS queue."""
response = sqs.receive_message(
QueueUrl=QUEUE_URL,
MaxNumberOfMessages=1,
WaitTimeSeconds=min(timeout, 20) # Long polling (max 20s)
)
messages = response.get("Messages", [])
if not messages:
return None
msg = messages[0]
# SNS wraps the message — unwrap it
sns_envelope = json.loads(msg["Body"])
result = json.loads(sns_envelope["Message"])
# Delete message after processing
sqs.delete_message(
QueueUrl=QUEUE_URL,
ReceiptHandle=msg["ReceiptHandle"]
)
return result
JavaScript
const {
SQSClient,
ReceiveMessageCommand,
DeleteMessageCommand,
} = require("@aws-sdk/client-sqs");
const sqs = new SQSClient({ region: "us-east-1" });
const QUEUE_URL = process.env.SQS_QUEUE_URL;
async function getSolvedCaptcha(timeout = 30) {
const response = await sqs.send(
new ReceiveMessageCommand({
QueueUrl: QUEUE_URL,
MaxNumberOfMessages: 1,
WaitTimeSeconds: Math.min(timeout, 20),
})
);
const messages = response.Messages || [];
if (messages.length === 0) return null;
const msg = messages[0];
const snsEnvelope = JSON.parse(msg.Body);
const result = JSON.parse(snsEnvelope.Message);
await sqs.send(
new DeleteMessageCommand({
QueueUrl: QUEUE_URL,
ReceiptHandle: msg.ReceiptHandle,
})
);
return result;
}
Filtrado de mensajes SNS
Enrute diferentes resultados a diferentes consumidores:
# Only send failures to the ops queue
sns.subscribe(
TopicArn=topic_arn,
Protocol="sqs",
Endpoint=failure_queue_arn,
Attributes={
"FilterPolicy": json.dumps({
"status": ["failed", "error"]
})
}
)
Solución de problemas
| Problema | causa | Solución |
|---|---|---|
| La devolución de llamada devuelve 403 | Bloqueo de autenticación de API Gateway CaptchaAI | Deshabilite la autenticación en la ruta de devolución de llamada; utilice la validación basada en tokens en su lugar |
| Los mensajes SQS no llegan | Falta el permiso SNS -> SQS | Agregue el permiso sns:Publish a la política de cola SQS |
| Resultados duplicados procesados | SNS envía al menos una vez | Implementar idempotencia: verifique task_id antes de procesar |
| El arranque en frío Lambda retrasa la devolución de llamada | Simultaneidad aprovisionada no establecida | Habilite la simultaneidad aprovisionada para la devolución de llamada Lambda |
Preguntas frecuentes
¿Por qué utilizar SNS en lugar de procesar los resultados directamente en la devolución de llamada Lambda?
SNS desacopla el controlador de devolución de llamada de la lógica descendente. Puede agregar nuevos consumidores (registro, alertas, análisis) sin modificar la devolución de llamada Lambda. La devolución de llamada sigue siendo simple y rápida.
¿Cuál es la latencia adicional de la capa SNS?
SNS agrega entre 10 y 50 ms por mensaje. Dado que CAPTCHA se resuelve en entre 5 y 30 segundos, esta sobrecarga es insignificante.
¿Puedo utilizar SNS FIFO para el procesamiento de pedidos?
Sí. Utilice un tema SNS FIFO con la cola SQS FIFO si necesita resultados ordenados. Establezca MessageGroupId en el ID de tarea para realizar pedidos por tarea.
Artículos relacionados
- Construir pipelines de CAPTCHA de clientes con CaptchaAI
- Seguridad de webhook: validación de callbacks
Crea una solución CAPTCHA basada en eventos: obtén tu API key de CaptchaAI y conéctala a tu canal de eventos de AWS.
Guías relacionadas:
- AWS Lambda + CaptchaAI serverless
- Seguridad de webhook: validación de callbacks