DevOps & Scaling

AWS Lambda + CaptchaAI: Pemecahan CAPTCHA Serverless

Beban scraping jarang datang rata: ramai beberapa jam, lalu sepi. Membayar server yang menyala 24 jam hanya untuk menyelesaikan CAPTCHA sesekali jelas boros — di sinilah AWS Lambda unggul. Anda menaruh satu fungsi yang memanggil API CaptchaAI, membayar hanya saat kode berjalan, dan Lambda menskalakan dari nol ke ratusan eksekusi bersamaan.

Tutorial ini membangun fungsi penyelesai, mengamankannya dengan Secrets Manager, men-deploy lewat SAM, lalu menambahkan jalur batch SQS. Satu catatan penting: tagihan CaptchaAI dihitung per thread — bukan per solve — jadi konkurensi Lambda dan jumlah thread paket harus diselaraskan.


Kapan Lambda cocok untuk penyelesaian CAPTCHA

Sebelum menulis kode, pastikan Lambda memang model yang tepat untuk beban Anda. Penyelesaian CAPTCHA sebagian besar waktunya habis menunggu jawaban dari API — bukan membebani CPU — jadi fungsi yang murah, cepat menskalakan, dan bebas dari server yang menganggur adalah kombinasi yang pas.

Lambda paling masuk akal ketika:

  • Trafik Anda berfluktuasi — misalnya scraping harga atau pemantauan yang ramai pada jam tertentu lalu sepi, sehingga membayar server 24 jam adalah pemborosan.
  • Anda ingin penyelesai CAPTCHA menjadi satu endpoint terpisah yang bisa dipanggil banyak layanan lain di belakang API Gateway.
  • Volume Anda tumbuh tak terduga dan Anda butuh konkurensi otomatis tanpa mengonfigurasi auto-scaling group.

Sebaliknya, jika Anda menjalankan scraping bervolume tinggi yang konstan sepanjang hari, container di ECS atau satu VM kecil bisa lebih hemat karena Anda tidak membayar per pemanggilan. Untuk pola on-demand dan spiky, Lambda hampir selalu menang.


Fungsi handler Lambda

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

Fungsi solve_captcha di atas mengikuti pola empat langkah yang sama untuk setiap tipe CAPTCHA:

  1. Kirim task ke in.php beserta method dan parameter halaman target.
  2. Simpan task ID yang dikembalikan API.
  3. Polling ke res.php sampai statusnya bukan CAPCHA_NOT_READY.
  4. Pakai token terselesaikan pada form atau request yang Anda otomatisasi.

Handler memuat API key dari environment variable dan mengembalikan token dalam respons JSON. Perhatikan bahwa solve_captcha sengaja memakai urllib bawaan Python, bukan requests — dengan begitu Anda tidak perlu membundel dependency tambahan atau mengurus Lambda layer.


Simpan API key di Secrets Manager

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

Untuk produksi, simpan key di Secrets Manager agar terenkripsi dan bisa dirotasi. Simpan secret-nya:

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

Deploy sebagai kode dengan template SAM

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

Template ini menyatukan fungsi, endpoint API, dan izin baca secret dalam satu berkas.


Build dan deploy fungsi

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

Contoh curl memakai region us-east-1, tetapi tim di Indonesia biasanya deploy ke region terdekat dengan target — ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) — untuk memangkas latensi bolak-balik. Cukup ganti region saat sam deploy --guided; kode fungsi tidak berubah. Perintah sam deploy --guided akan menanyakan nama stack, region, dan konfirmasi perubahan sebelum membuat resource.


Proses batch CAPTCHA dari antrean SQS

Untuk permintaan satu per satu, API Gateway sudah cukup. Tetapi saat Anda harus menyelesaikan ribuan CAPTCHA dalam satu proses — misalnya membersihkan backlog scraping semalam — pola berbasis antrean memberi tiga keuntungan:

  • Retry otomatis: task yang gagal kembali ke antrean alih-alih hilang begitu saja.
  • Paralelisme: Lambda menarik banyak pesan sekaligus, dibatasi hanya oleh reserved concurrency dan thread paket Anda.
  • Peredam lonjakan: antrean menahan kelebihan task sehingga API tidak dibanjiri sekaligus.

Handler SQS berikut memproses setiap pesan dalam batch dan mengembalikan status per task:

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}

Menyelaraskan konkurensi Lambda dengan thread CaptchaAI

Ini titik yang paling sering terlewat, dan penting terutama bagi tim yang sensitif terhadap biaya. Lambda menskalakan konkurensi hampir tanpa batas, tetapi CaptchaAI menagih per thread bersamaan — bukan per solve. Jika Lambda menjalankan 100 fungsi sekaligus sementara paket Anda hanya menyediakan lebih sedikit thread, sebagian task akan mengantre di sisi CaptchaAI, bukan gagal.

Selaraskan keduanya dengan langkah berikut:

  • Perkirakan konkurensi puncak yang benar-benar Anda butuhkan, lalu pilih paket dengan jumlah thread yang sepadan — misalnya PREMIUM ($170/bulan, 100 thread) untuk sekitar 100 penyelesaian bersamaan.
  • Batasi reserved concurrency pada fungsi Lambda agar tidak pernah melampaui jumlah thread paket Anda, sehingga tagihan tetap dapat diprediksi.
  • Untuk beban batch, biarkan SQS menahan kelebihan task; antrean menyerap lonjakan tanpa Anda perlu menaikkan paket hanya demi puncak sesaat.

Karena harga CaptchaAI datar per thread dengan penyelesaian tak terbatas, model ini cocok untuk tim scraping dan otomatisasi yang volumenya naik-turun — Anda membayar kapasitas, bukan setiap solve.


Batas dan pengaturan Lambda yang perlu diperhatikan

Beberapa angka default Lambda perlu Anda sesuaikan agar cocok dengan karakter beban penyelesaian CAPTCHA yang menunggu jaringan, bukan membebani CPU:

Faktor Nilai
Batas waktu maksimal 15 menit (setel ke 2 menit untuk sebagian besar CAPTCHA)
Memori 256 MB sudah cukup (beban ringan, bukan komputasi berat)
Konkurensi Default 1000 eksekusi bersamaan (ajukan kenaikan bila perlu)
Cold start ~500ms untuk Python (dapat diabaikan dibanding waktu penyelesaian)
Biaya ~$0.0001 per penyelesaian (hanya komputasi)
Dependency Pakai urllib (bawaan) agar tidak perlu Lambda layer

Masalah umum dan solusinya

Masalah Penyebab Solusi
Fungsi kena timeout Batas waktu Lambda < waktu penyelesaian Setel batas waktu ke 120 detik atau lebih
Izin ditolak saat baca secret Kebijakan IAM belum ada Tambahkan kebijakan baca Secrets Manager
Cold start menambah latensi Pemanggilan jarang Aktifkan provisioned concurrency
Error impor untuk requests Tidak ikut terbundel di Lambda Pakai urllib.request (bawaan) atau tambahkan layer

Pertanyaan yang sering muncul

Region AWS mana yang sebaiknya dipakai dari Indonesia?

Pilih region terdekat dengan situs target. Untuk target regional, ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) biasanya lebih baik daripada region Amerika. API CaptchaAI bisa diakses dari region mana pun; yang menentukan hanya jarak jaringan.

Sebaiknya pakai pemicu API Gateway atau SQS?

Pakai API Gateway saat butuh hasil seketika dalam satu respons HTTP, misalnya di tengah alur login. Pakai SQS untuk batch besar yang asinkron dengan retry bawaan.

Apakah Lambda hemat biaya untuk penyelesaian CAPTCHA?

Ya. Dengan sekitar $0.0001 per pemanggilan, biaya komputasi Lambda dapat diabaikan dibanding biaya API CaptchaAI, dan Anda tidak membayar apa pun saat fungsi menganggur. Yang tetap menentukan total biaya adalah paket thread CaptchaAI Anda, bukan Lambda.

Bisakah satu fungsi Lambda menangani berbagai tipe CAPTCHA?

Bisa. Parameter method yang Anda kirim menentukan tipe yang diproses, jadi fungsi yang sama dapat menangani reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3, hingga CAPTCHA gambar/OCR — cukup ubah method dan parameter halaman target per permintaan tanpa deploy ulang.


Bacaan terkait


Mulai serverless — ambil API key CaptchaAI Anda hari ini.

Komentar dinonaktifkan untuk artikel ini.