DevOps & Scaling

AWS Lambda et CaptchaAI pour une résolution CAPTCHA serverless

Vous avez des résolutions CAPTCHA à traiter par à-coups, sans vouloir payer un serveur qui tourne en continu ? AWS Lambda répond précisément à ce besoin : vous ne payez que le temps d'exécution réel, le scaling est automatique et l'intégration avec API Gateway, SQS ou Step Functions est immédiate.

La résolution CAPTCHA serverless sur Lambda ne se résume pourtant pas à un appel d'API depuis une fonction. Trois points font la différence entre un prototype et une brique fiable : la gestion de la clé API, le calibrage des timeouts et le choix du déclencheur adapté à votre profil de charge. Ce guide les déroule dans l'ordre, du handler Python jusqu'au traitement par lots via SQS.


Le handler Lambda de résolution CAPTCHA

Le cœur de l'intégration tient dans une seule fonction. Le handler lit la clé API depuis une variable d'environnement, parse la requête entrante, soumet la tâche à l'API CaptchaAI via in.php, puis interroge régulièrement res.php jusqu'à obtenir le token. Le code ci-dessous couvre la soumission et le polling, avec un timeout applicatif par défaut de 90 secondes.

# 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")

Deux détails d'exploitation méritent attention. Le polling interroge le résultat toutes les 5 secondes : c'est un bon compromis entre réactivité et nombre d'appels. Et le timeout applicatif (90 s) doit toujours rester inférieur au timeout configuré sur la fonction Lambda elle-même, sinon AWS coupe l'exécution avant que votre code ne renvoie une erreur exploitable.


Sécuriser la clé API avec AWS Secrets Manager

Ne codez jamais la clé API en dur, et évitez de la laisser en clair dans une variable d'environnement visible depuis la console. AWS Secrets Manager centralise le secret, le chiffre au repos et journalise les accès — un réflexe d'autant plus utile si vous devez documenter vos traitements au titre du RGPD.

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"]

Création du secret :

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

La fonction peut alors récupérer le secret au démarrage. Mieux encore, laissez CloudFormation le résoudre au déploiement (voir le template SAM ci-dessous) : la clé arrive dans la variable d'environnement sans appel réseau supplémentaire à chaque invocation.


Décrire l'infrastructure avec un template SAM

Plutôt que de cliquer dans la console, décrivez toute la pile en Infrastructure as Code. Le template SAM suivant crée la fonction, injecte la clé API depuis Secrets Manager, expose un endpoint POST /solve via API Gateway et attache la politique IAM minimale nécessaire pour lire le secret.

# 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"

Notez le Timeout: 120 défini au niveau global : il laisse à la fonction le temps de terminer un cycle de polling complet, y compris sur les types les plus lents à résoudre.


Déployer et tester la fonction

Deux commandes suffisent pour construire et déployer la pile. L'option --guided de sam deploy vous pose les questions de configuration la première fois, puis mémorise vos réponses dans samconfig.toml pour les déploiements suivants.

# 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"
    }
  }'

L'URL renvoyée en sortie (SolveApiUrl) est votre endpoint de production. Pour un public francophone, déployez de préférence dans une région proche : eu-west-3 (Paris) réduit la latence réseau entre API Gateway et vos clients en France, en Belgique ou en Suisse, sans rien changer au code.


Traitement par lots déclenché par SQS

Pour un gros volume, ne lancez pas la résolution en synchrone depuis API Gateway. Placez une file SQS en entrée : elle découple la soumission des tâches de leur exécution, absorbe les pics et rejoue automatiquement les messages en échec. Chaque message porte une tâche, et le handler les traite par lot.

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}

Ce découplage change aussi le modèle de coût : vous lissez la charge sur la concurrence disponible au lieu de risquer un pic d'invocations simultanées, et vous gardez une file de secours si l'API met plus longtemps que prévu à répondre.


Réglages d'exploitation sur Lambda

Quelques valeurs de référence pour dimensionner la fonction sans sur-provisionner :

Facteur Valeur
Timeout maximum 15 minutes, mais 120 s suffisent souvent
Mémoire 256 Mo suffisent dans la plupart des cas
Concurrence 1 000 exécutions par défaut selon le compte
Cold start Faible impact face au temps de résolution
Coût Très bas par invocation, hors coût de l'API
Dépendances urllib évite d'ajouter des layers inutiles

Côté facturation CaptchaAI, gardez en tête que la tarification est basée sur les threads, pas sur le nombre de résolutions : l'offre BASIC ($15/mois, 5 threads) couvre déjà une charge serverless modérée, et vous montez en gamme uniquement quand vous avez besoin de plus de résolutions en parallèle.


Dépannage

Problème Cause probable Correctif
La fonction expire Timeout Lambda trop court Passez à 120 s ou plus
Accès refusé au secret Politique IAM absente Ajoutez la permission Secrets Manager
Latence au premier appel Cold start visible Activez la provisioned concurrency si nécessaire
Import requests indisponible Dépendance non embarquée Utilisez urllib.request ou ajoutez un layer

FAQ

API Gateway ou SQS : quel mode de déclenchement retenir ?

API Gateway convient aux résolutions unitaires où l'appelant attend le token en réponse directe. SQS s'impose dès que le volume grimpe ou que vous voulez lisser les pics : la file absorbe la charge et rejoue les échecs sans bloquer l'émetteur.

Quelle région AWS privilégier pour un public francophone ?

Déployez dans eu-west-3 (Paris). Vous réduisez la latence réseau pour les utilisateurs en France, en Belgique et en Suisse, et vous restez au plus près de vos autres services européens si votre pipeline y est déjà hébergé.

Le cold start ralentit-il la résolution ?

Marginalement. Le démarrage à froid d'une fonction Python se compte en centaines de millisecondes, négligeable face aux secondes que prend la résolution elle-même. Si vos invocations sont très espacées, activez la provisioned concurrency.

Faut-il embarquer la bibliothèque requests dans un layer ?

Non, ce n'est pas nécessaire. Le module urllib.request, inclus dans la bibliothèque standard, suffit pour l'API HTTP de CaptchaAI et simplifie nettement le package de déploiement.


Guides connexes


Prêt à passer au serverless ? Récupérez votre clé CaptchaAI et branchez-la sur votre pipeline AWS Lambda.

Les commentaires sont désactivés pour cet article.