DevOps & Scaling

AWS Lambda + CaptchaAI: бессерверное решение CAPTCHA

Сколько стоит решение CAPTCHA через AWS Lambda? Около $0,0001 за вызов на вычисления — по факту вы платите только за само решение CAPTCHA у CaptchaAI. Serverless-схема на Lambda снимает заботу о серверах: функция масштабируется под нагрузку, простаивает бесплатно и подключается к API Gateway, очереди SQS или Step Functions. Ниже — рабочий обработчик, безопасное хранение ключа, SAM-шаблон для деплоя одной командой и разбор ошибок первого запуска.


Обработчик Lambda для решения CAPTCHA

Функция ниже принимает событие от API Gateway, достаёт метод решения и параметры, отправляет задачу в CaptchaAI и опрашивает res.php, пока не получит токен или не истечёт тайм-аут. Библиотека requests не используется намеренно — urllib.request входит в стандартный Python-рантайм Lambda и избавляет от необходимости собирать слой (layer) ради одного HTTP-запроса.

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

Метод по умолчанию — userrecaptcha, но вы передаёте любой поддерживаемый метод в теле запроса. Параметр timeout внутри solve_captcha (90 секунд) — это тайм-аут ожидания решения, а не тайм-аут самой функции Lambda: их нужно настраивать согласованно, иначе Lambda оборвёт выполнение раньше, чем придёт ответ (см. раздел про типичные ошибки ниже).


Храните API-ключ в AWS Secrets Manager

Хранить CAPTCHAAI_KEY открытым текстом в переменных окружения Lambda — рабочий, но не лучший вариант: значение видно всем с доступом на чтение к конфигурации функции. Secrets Manager хранит ключ зашифрованным, разграничивает доступ через IAM и позволяет ротировать секрет без изменения кода.

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

Храните секрет:

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

Для работы этого кода у роли выполнения Lambda должна быть политика secretsmanager:GetSecretValue, ограниченная конкретным ARN секрета, — не давайте функции доступ ко всем секретам аккаунта.


SAM-шаблон: инфраструктура как код одной командой

SAM (AWS Serverless Application Model) — обёртка над CloudFormation: функция, API Gateway и политики IAM описаны в одном YAML-файле, который проходит код-ревью вместе с остальным кодом и разворачивается воспроизводимо. Тайм-аут 120 секунд и память 256 МБ ниже — разумная стартовая точка для решения CAPTCHA: это лёгкая операция ввода-вывода, раздувать память смысла нет.

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

Блок Policies реализует принцип наименьших привилегий: функция получает право читать ровно один секрет по конкретному ARN, а не общий доступ к Secrets Manager. Секция Events сама поднимает API Gateway и маршрут /solve — отдельно настраивать шлюз не нужно.


Деплой и быстрая проверка

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

sam deploy --guided спросит регион интерактивно; для команд, ориентирующихся на латентность до Европы, разумным выбором часто оказывается eu-central-1 или eu-north-1 — CaptchaAI работает одинаково из любого региона, регион влияет только на сетевую задержку. После деплоя команда выведет URL из Outputs, его и используйте в curl.


Пакетная обработка через очередь SQS

Если задачи приходят не по одной, а пачками — например, ночной прогон QA-тестов формы регистрации или обработка отложенной очереди из основного бэкенда, — логичнее не дёргать HTTP-эндпоинт напрямую, а класть задачи в SQS и обрабатывать их отдельным обработчиком с собственной конкурентностью.

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 с тарифным планом CaptchaAI. Например, на плане STANDARD ($30/мес, 15 потоков) выставьте ReservedConcurrentExecutions не выше 15 — иначе лишние параллельные вызовы просто получат ошибку от API вместо решения, а не аккуратный тайм-аут. Для сообщений, которые не удалось обработать после нескольких попыток, настройте dead-letter queue (DLQ) — так неудачные задачи не потеряются и не будут бесконечно перезапускаться.


Тайм-аут, память и конкурентность Lambda

Фактор Значение
Максимальное время ожидания 15 минут (для большинства CAPTCHA установлено значение 2 минуты)
Память Достаточно 256 МБ (без тяжелой обработки)
Параллелизм По умолчанию 1000 одновременно (при необходимости запросите увеличение)
Холодный старт ~500 мс для Python (незначительно по сравнению со временем решения)
Расходы ~$0,0001 за решение (только вычисления)
Зависимости Используйте urllib (встроенный), чтобы избежать слоев Lambda.

Для латентно-чувствительных сценариев — например, QA-проверки формы с CAPTCHA на портале записи на визу вроде BLS, где пользователь ждёт быстрого отклика, — холодный старт в 500 мс может быть заметен на фоне остального флоу. В таких случаях включите provisioned concurrency: Lambda будет держать нужное число «прогретых» экземпляров и не тратить время на инициализацию рантайма при каждом всплеске трафика.


Частые ошибки при развёртывании

Проблема Причина Исправить
Время ожидания функции истекло Тайм-аут лямбды <время решения Установите тайм-аут на 120 с+.
Разрешение отклонено по секрету Отсутствует политика IAM Добавить политику чтения SecretsManager
Холодный старт увеличивает задержку Редкие вызовы Использовать подготовленный параллелизм
Ошибка импорта запросов Не входит в комплект Lambda Используйте urllib.request (встроенный) или добавьте слой.

Первые две строки таблицы — самые частые причины обращений в поддержку по этой интеграции: тайм-аут функции короче, чем реальное время решения конкретного типа CAPTCHA, и забытая политика на чтение секрета после первого деплоя через sam deploy --guided.


Мониторинг и логи в CloudWatch

Каждый вызов lambda_handler и sqs_handler автоматически пишет логи в CloudWatch Logs — этого достаточно для отладки, но для продакшена стоит настроить метрики отдельно. Заведите алармы CloudWatch на долю ошибок (Errors) и длительность выполнения (Duration), приближающуюся к тайм-ауту, — это сигнал раньше, чем начнут падать реальные запросы пользователей. Для цепочек из нескольких функций (например, Lambda → SQS → Step Functions) включите AWS X-Ray: он показывает, на каком именно шаге теряется время, если решение CAPTCHA внезапно замедлилось.

Отдельная гигиена логов: не пишите в CloudWatch сам токен CAPTCHA целиком и тем более персональные данные из тела запроса — усечения token[:50] в примере выше достаточно для отладки. Это не столько требование CaptchaAI, сколько разумная практика логирования, которая заодно снижает риски по 152-ФЗ «О персональных данных» и аналогичным требованиям для команд, работающих с данными пользователей из ЕС.


Часто задаваемые вопросы

Сколько стоит эксплуатация Lambda-обработчика CaptchaAI в месяц?

Сама Lambda обходится в доли цента — около $0,0001 за вызов при 256 МБ и 60 секундах выполнения. Реальная статья расходов — тарифный план CaptchaAI (потоки), а не вычисления AWS: серверная часть решения почти ничего не добавляет к счёту.

Какой тайм-аут ставить в Lambda, чтобы не словить обрыв на 15 минутах?

До лимита в 15 минут дело почти никогда не доходит: большинство CAPTCHA решаются за 10–60 секунд. Ставьте тайм-аут функции 120 секунд, а для тяжёлых типов вроде reCAPTCHA Enterprise — 180 секунд, с запасом сверх ожидаемого времени решения.

Как согласовать зарезервированную конкурентность Lambda с тарифным планом CaptchaAI?

Зарезервированная конкурентность Lambda не должна превышать число потоков вашего тарифа CaptchaAI. Если у вас, например, ADVANCE ($90/мес, 50 потоков), а Lambda пытается запустить 200 параллельных вызовов, лишние 150 просто получат ошибку от API, а не встанут в очередь.

Нужен ли Lambda в VPC для вызова API CaptchaAI?

Нет, в большинстве случаев не нужен. API CaptchaAI — публичный HTTPS-эндпоинт, и функция без привязки к VPC достигает его напрямую через интернет-шлюз Lambda. Помещать функцию в VPC имеет смысл, только если ей отдельно нужен доступ к ресурсам внутри вашей сети (например, к RDS), а не ради самого вызова CaptchaAI.

Как логировать ошибки решения CAPTCHA, не храня токены и персональные данные?

Логируйте task_id, код ошибки и, при необходимости, первые символы токена для сверки, но не сам токен целиком и не параметры, содержащие пользовательские данные из формы. Такого объёма достаточно, чтобы найти сбойный вызов в CloudWatch, не превращая логи в отдельное хранилище чувствительных данных.


Связанные руководства


Переходите на serverless — получите ключ CaptchaAI и разверните первый обработчик уже сегодня.

Комментарии для этой статьи отключены.