DevOps & Skalierung

AWS Lambda + CaptchaAI: Serverlose CAPTCHA-Lösung

CAPTCHA-Last kommt selten gleichmäßig: mal keine Anfrage, dann Hunderte in kurzer Zeit. Für dieses Profil ist AWS Lambda ideal – Sie zahlen nur beim Lösen und skalieren automatisch von null auf Hunderte parallele Aufrufe.

Dieser Leitfaden betreibt CaptchaAI serverlos hinter API Gateway und SQS. Die Beispiele lösen reCAPTCHA v2, gelten aber ebenso für v3, Turnstile, GeeTest v3 und Bild-CAPTCHAs.


Wann sich der serverlose Ansatz lohnt

Serverlos ist nicht für jede Last die beste Wahl: Bei konstant hohem, gleichmäßigem Durchsatz kann ein dauerhaft laufender Worker günstiger sein. Seine Stärken spielt Lambda genau dann aus, wenn Ihr Traffic schwankt:

  • Unregelmäßige Spitzen – Scraping-Jobs, die nur nachts laufen, oder Formular-Flows mit saisonalen Ausschlägen.
  • Keine Grundlast – Sie zahlen nichts, solange keine Anfrage eintrifft, statt einen VPS im Leerlauf zu finanzieren.
  • Schnelle Skalierung – von null auf Hunderte parallele Aufrufe in Sekunden, ohne Kapazität vorzuhalten.
  • Wenig Betrieb – kein Patchen, kein Autoscaling-Setup, keine eigenen Health-Checks.

Der eigentliche Engpass ist am Ende nicht Lambda, sondern Ihr CaptchaAI-Thread-Kontingent – dazu weiter unten mehr.


Der Lambda-Handler: Aufgabe übermitteln, Token abfragen

Der Handler übermittelt die Aufgabe an in.php und fragt anschließend res.php ab, bis das Token vorliegt. Für diesen einfachen HTTP-Ablauf genügt urllib aus der Python-Standardbibliothek – das erspart Ihnen zusätzliche Abhängigkeiten und Lambda-Layer. Der Handler liest method und params aus dem Request-Body, sodass sich derselbe Code für jeden unterstützten CAPTCHA-Typ wiederverwenden lässt.

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

API-Schlüssel sicher verwalten mit Secrets Manager

In Produktion gehört der Schlüssel nicht als Klartext in eine Umgebungsvariable, sondern in den AWS Secrets Manager – dort ist der Zugriff per IAM steuerbar, rotierbar und auditierbar. Die Funktion liest das Secret erst zur Laufzeit; der Schlüssel taucht weder im Repository noch im Deployment-Log auf.

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

Secret anlegen:

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

Infrastruktur als Code: das SAM-Template

Das SAM-Template beschreibt Funktion, API-Route und Berechtigungen in einer einzigen, versionierbaren Datei. Hier setzen Sie den Timeout auf 120 Sekunden, die POST-Route /solve und eine eng gefasste IAM-Richtlinie, die ausschließlich Lesezugriff auf genau dieses eine Secret erlaubt.

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

Deployment mit sam build und sam deploy

sam build verpackt den Code, sam deploy --guided fragt beim ersten Mal Region, Stack-Namen und Bestätigungen ab und legt die Werte in samconfig.toml ab – danach genügt ein schlichtes sam deploy. Der abschließende curl-Aufruf prüft die Route direkt gegen Ihr API Gateway.

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

Stapelverarbeitung über SQS

Für Massenverarbeitung entkoppelt eine SQS-Warteschlange das Einreichen vom Lösen: Lambda zieht Aufgaben in Batches, verarbeitet sie parallel und schreibt die Ergebnisse zurück. Fehlgeschlagene Nachrichten wandern über eine Dead-Letter-Queue in einen erneuten Versuch, statt verloren zu gehen – praktisch für Scraping-Pipelines mit tausenden Formularen.

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}

Lambda richtig dimensionieren: Timeout, Speicher, Parallelität

Da der Solver fast nur wartet, reichen 256 MB – der Timeout ist die wichtigste Stellschraube.

Faktor Wert
Max. Timeout 15 Minuten (für die meisten CAPTCHAs auf 2 Minuten eingestellt)
Speicher 256 MB ausreichend (keine schwere Verarbeitung)
Parallelität Standardmäßig 1000 gleichzeitig (bei Bedarf Erhöhung anfordern)
Kaltstart ~500 ms für Python (vernachlässigbar im Vergleich zur Lösungszeit)
Kosten ~0,0001 $ pro Lösung (nur Berechnung)
Abhängigkeiten Verwenden Sie urllib (integriert), um Lambda-Ebenen zu vermeiden

DACH-Kontext: Ein dauerhaft laufender Hetzner- oder netcup-VPS kostet auch im Leerlauf – Lambda skaliert dagegen auf null. Wichtiger als die AWS-Rechnung ist aber die Thread-basierte Abrechnung von CaptchaAI: Sie zahlen pro gleichzeitigem Thread, nicht pro Lösung. Ihre effektive Parallelität sollte deshalb zur Thread-Anzahl Ihres Plans passen – etwa BASIC (15 $/Monat, 5 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Mit Reserved Concurrency auf der Lambda-Funktion starten Sie nie mehr parallele Aufrufe, als Ihr Plan an Threads bereitstellt.


Häufige Fehler und ihre Lösung

Problem Ursache Lösung
Funktion läuft ab Lambda-Timeout < Lösungszeit Stellen Sie den Timeout auf 120s+ ein
Zugriff auf Secret verweigert Fehlende IAM-Richtlinie Fügen Sie die SecretsManager-Leserichtlinie hinzu
Kaltstart erhöht die Latenz Seltene Aufrufe Nutzen Sie Provisioned Concurrency
Importfehler für requests Nicht in Lambda gebündelt Verwenden Sie urllib.request (integriert) oder fügen Sie eine Ebene hinzu

Häufige Fragen

Wie viele Lambda-Aufrufe darf ich parallel laufen lassen?

So viele, wie Ihr CaptchaAI-Plan an Threads bereitstellt – BASIC bietet 5, ADVANCE 50 gleichzeitige Solves. Lambda erlaubt standardmäßig 1000 parallele Ausführungen; der Engpass ist Ihr Thread-Kontingent.

Wie speichere ich meinen API-Schlüssel sicher?

Über den AWS Secrets Manager, nicht als Klartext. Das SAM-Template löst ihn zur Laufzeit auf; eine IAM-Richtlinie beschränkt den Zugriff.

Funktioniert derselbe Handler für reCAPTCHA v3 und Turnstile?

Ja. Ändern Sie nur method und paramsuserrecaptcha für reCAPTCHA v2/v3, turnstile für Turnstile, geetest für GeeTest v3. Die Poll-Logik bleibt identisch.

Wie reduziere ich Kaltstarts bei seltenem Traffic?

Mit Provisioned Concurrency halten Sie Instanzen warm, sodass der Kaltstart von rund 500 ms bei Python entfällt. Bei gelegentlichen Jobs fällt er gegenüber der Lösungszeit kaum ins Gewicht – dann ist Provisioned Concurrency den Aufpreis meist nicht wert.

Zählt jeder Lambda-Aufruf als eigener CaptchaAI-Thread?

Nur, solange er tatsächlich gleichzeitig läuft. Ein Thread ist eine in Arbeit befindliche Lösung; sobald ein Solve fertig ist, wird der Thread frei und nimmt die nächste Aufgabe. Begrenzen Sie die Reserved Concurrency Ihrer Funktion auf Ihre Thread-Anzahl, um Fehler durch überschrittene Parallelität zu vermeiden.


Verwandte Leitfäden


Bereit für Serverless? Sichern Sie sich Ihren CaptchaAI-Schlüssel und lösen Sie Ihr erstes CAPTCHA in Lambda.

Kommentare sind für diesen Artikel deaktiviert.