Tutorials

Cara Membatasi Laju Permintaan Solve CAPTCHA Anda Sendiri

Saldo CaptchaAI habis dalam semalam padahal traffic tidak naik drastis? Penyebabnya hampir selalu sama: tidak ada batas laju permintaan di sisi klien Anda sendiri.

CaptchaAI sendiri tidak membatasi konkurensi Anda — API-nya menangani ribuan request paralel tanpa masalah — jadi kontrol laju dan anggaran sepenuhnya ada di tangan Anda. Panduan ini menerapkan tiga pola rate limiting sisi klien yang bisa langsung dipasang di depan integrasi CaptchaAI Anda: token bucket, sliding window, dan budget limiter.

Kenapa Perlu Membatasi Diri Sendiri, Bukan Cuma Mengandalkan CaptchaAI

Empat skenario ini adalah alasan paling umum tim developer dan agensi otomatisasi memasang rate limiting sendiri, alih-alih menyerahkan kontrol sepenuhnya ke penyedia:

  • Bug menyebabkan solve loop tanpa henti — tanpa limiter, saldo terkuras cepat; dengan limiter, proses berhenti otomatis di batas yang dikonfigurasi.
  • Beberapa tim berbagi satu API key — tanpa limiter, pemakaian tidak terkoordinasi; dengan limiter, kuota terbagi adil per tim.
  • Situs target memblokir di atas 100 request/menit — tanpa limiter, akun kena block; dengan limiter, laju Anda tetap aman di bawah ambang batas.
  • Anggaran bulanan $50 — tanpa limiter, bisa habis dalam semalam; dengan limiter, batas keras (hard cap) diberlakukan.

Contoh yang sering terjadi di tim scraping kecil: dua developer berbagi satu API key pada plan STANDARD ($30/bulan, 15 thread). Satu orang menjalankan testing intensif sampai memakai hampir semua thread, sementara job produksi milik rekan yang lain jadi antre atau gagal karena kehabisan slot. Solusinya bukan buru-buru upgrade ke ADVANCE ($90/bulan, 50 thread) — cukup pasang rate limiting per developer atau per service di sisi klien, sehingga kuota terbagi rata tanpa menambah biaya bulanan.

Pola 1: Token Bucket — Laju Rata-Rata Stabil dengan Ruang untuk Burst

Token bucket cocok kalau Anda ingin laju rata-rata yang stabil tapi tetap mengizinkan lonjakan singkat (burst). Token diisi ulang dengan kecepatan tetap; setiap request mengonsumsi satu token, dan request baru menunggu kalau bucket-nya kosong.

Implementasi Token Bucket di Python

# token_bucket_solver.py
import os
import time
import threading
import requests

API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")

class TokenBucket:
    """Token bucket rate limiter."""

    def __init__(self, rate, capacity):
        """
        rate: tokens added per second
        capacity: max tokens (burst size)
        """
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity
        self.last_refill = time.monotonic()
        self.lock = threading.Lock()

    def acquire(self, timeout=30):
        """Wait for a token. Returns True if acquired, False on timeout."""
        deadline = time.monotonic() + timeout
        while True:
            with self.lock:
                self._refill()
                if self.tokens >= 1:
                    self.tokens -= 1
                    return True

            if time.monotonic() >= deadline:
                return False
            time.sleep(0.1)

    def _refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
        self.last_refill = now

# Allow 10 solves/minute with burst of 5
limiter = TokenBucket(rate=10/60, capacity=5)

def solve_rate_limited(sitekey, pageurl):
    """Solve with rate limiting."""
    if not limiter.acquire(timeout=60):
        raise Exception("Rate limit: could not acquire token within 60s")

    session = requests.Session()
    resp = session.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": "1",
    })
    result = resp.json()

    if result.get("status") != 1:
        raise Exception(f"Submit failed: {result.get('request')}")

    task_id = result["request"]
    time.sleep(15)

    for _ in range(25):
        poll = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": "1",
        })
        poll_result = poll.json()
        if poll_result.get("status") == 1:
            return poll_result["request"]
        if poll_result.get("request") != "CAPCHA_NOT_READY":
            raise Exception(f"Error: {poll_result.get('request')}")
        time.sleep(5)

    raise Exception("Timeout")

Pola 2: Sliding Window — Batasi Jumlah Request per Jangka Waktu

Sliding window menghitung berapa banyak request yang masuk dalam rentang waktu tertentu, misalnya per 5 menit. Lebih sederhana daripada token bucket, tapi tidak punya kontrol burst — begitu limit tercapai, request berikutnya harus menunggu sampai request paling lama keluar dari jendela waktu.

Implementasi Sliding Window di JavaScript

// sliding_window_solver.js
const axios = require('axios');

const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';

class SlidingWindowLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.timestamps = [];
  }

  async acquire(timeoutMs = 60000) {
    const deadline = Date.now() + timeoutMs;

    while (Date.now() < deadline) {
      // Remove expired timestamps
      const cutoff = Date.now() - this.windowMs;
      this.timestamps = this.timestamps.filter(t => t > cutoff);

      if (this.timestamps.length < this.maxRequests) {
        this.timestamps.push(Date.now());
        return true;
      }

      // Wait until the oldest request exits the window
      const waitMs = Math.min(
        this.timestamps[0] + this.windowMs - Date.now() + 10,
        deadline - Date.now()
      );
      if (waitMs > 0) await new Promise(r => setTimeout(r, waitMs));
    }
    return false;
  }
}

// Allow 20 solves per 5 minutes
const limiter = new SlidingWindowLimiter(20, 5 * 60 * 1000);

async function solveRateLimited(sitekey, pageurl) {
  const acquired = await limiter.acquire(60000);
  if (!acquired) throw new Error('Rate limit exceeded');

  const submit = await axios.get('https://ocr.captchaai.com/in.php', {
    params: {
      key: API_KEY, method: 'userrecaptcha',
      googlekey: sitekey, pageurl, json: '1',
    },
  });

  if (submit.data.status !== 1) throw new Error(submit.data.request);
  await new Promise(r => setTimeout(r, 15000));

  for (let i = 0; i < 25; i++) {
    const poll = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
    });
    if (poll.data.status === 1) return poll.data.request;
    if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error('Timeout');
}

Pola 3: Budget Limiter — Pembatas Berbasis Anggaran

Kalau prioritas Anda murni kontrol biaya harian dan bukan laju request per detik, budget limiter lebih langsung ke sasaran: tetapkan anggaran harian, lalu hentikan proses begitu tercapai.

# budget_limiter.py
import os
import time
from datetime import date

class BudgetLimiter:
    """Limit daily CAPTCHA spending."""

    def __init__(self, daily_budget, cost_per_solve=0.003):
        self.daily_budget = daily_budget
        self.cost_per_solve = cost_per_solve
        self.daily_spend = 0.0
        self.current_date = date.today()

    def can_solve(self):
        """Check if budget allows another solve."""
        if date.today() != self.current_date:
            self.daily_spend = 0.0
            self.current_date = date.today()

        return self.daily_spend + self.cost_per_solve <= self.daily_budget

    def record_solve(self):
        """Record a successful solve against the budget."""
        self.daily_spend += self.cost_per_solve

    @property
    def remaining_budget(self):
        return max(0, self.daily_budget - self.daily_spend)

    @property
    def remaining_solves(self):
        return int(self.remaining_budget / self.cost_per_solve)

# $5/day budget
budget = BudgetLimiter(daily_budget=5.00, cost_per_solve=0.003)

def solve_with_budget(sitekey, pageurl):
    if not budget.can_solve():
        raise Exception(
            f"Daily budget exhausted. Remaining: ${budget.remaining_budget:.2f}"
        )

    # ... solve logic ...
    token = "..."  # actual solve
    budget.record_solve()
    return token

Pola Mana yang Paling Cocok untuk Kebutuhan Anda?

Ringkasan cepat sebelum Anda memilih:

  1. Token bucket — kompleksitas sedang, paling cocok kalau Anda butuh kontrol laju halus sekaligus mengizinkan burst.
  2. Sliding window — kompleksitas rendah, cukup untuk menghitung jumlah request sederhana per jangka waktu.
  3. Budget limiter — kompleksitas rendah, fokus murni pada kontrol biaya per hari, minggu, atau bulan.
  4. Gabungan (laju + anggaran) — kompleksitas sedang, ini yang sebaiknya dipakai untuk sistem produksi.

Menggabungkan Token Bucket dan Budget Limiter

Untuk sistem produksi, jangan pilih salah satu — pakai keduanya sekaligus. Token bucket menjaga laju request tetap halus dan mencegah burst yang bisa memicu ban dari situs target, sementara budget limiter jadi jaring pengaman terakhir kalau ada bug atau job yang lupa dimatikan. Panggil budget.can_solve() sebelum limiter.acquire() pada setiap request: kalau anggaran harian sudah habis, permintaan ditolak lebih awal tanpa perlu menunggu giliran token sama sekali.

Masalah Umum saat Menerapkan Rate Limiting

Empat masalah ini paling sering muncul saat limiter baru dipasang:

  • Semua request menumpuk di antrean, tidak ada yang jalan. Penyebabnya rate diset terlalu rendah untuk volume permintaan. Perbaikannya: naikkan rate atau perbesar ukuran window.
  • Budget limiter reset di tengah hari. Penyebabnya restart aplikasi atau perubahan jam sistem. Perbaikannya: simpan pengeluaran harian ke file atau database, jangan hanya di memori.
  • Token bucket cepat habis. Penyebabnya kapasitas terlalu kecil untuk pola traffic Anda. Perbaikannya: naikkan parameter capacity.
  • Limiter ikut memblokir request polling. Penyebabnya limiter diterapkan juga ke polling. Perbaikannya: batasi hanya request submit, biarkan polling bebas seperti dijelaskan di FAQ berikut.

Pertanyaan yang Sering Diajukan

Sebaiknya limiter dipasang di request submit atau polling?

Batasi hanya request submit ke in.php. Polling ke res.php boleh berjalan bebas karena tidak membuat task baru dan tidak menambah biaya — membatasi polling justru cuma memperlambat Anda mendapatkan hasil.

Berapa laju request yang wajar untuk plan BASIC ($15/bulan, 5 thread)?

Batas praktisnya mengikuti jumlah thread, bukan angka request per menit yang tetap. Dengan 5 thread, maksimal 5 request bisa diproses bersamaan — request keenam otomatis antre sampai salah satu selesai. Kalau limiter Anda sering menunggu lama, itu tanda beban sudah melebihi kapasitas thread, dan saatnya mempertimbangkan plan yang lebih besar seperti STANDARD atau ADVANCE, bukan menaikkan angka di limiter.

Apakah pembatasan sendiri berarti CaptchaAI tidak stabil menangani banyak request sekaligus?

Tidak. CaptchaAI menangani konkurensi tinggi dengan baik — rate limiting di panduan ini murni kontrol dari sisi Anda: mengelola biaya, mencegah bug yang menyebabkan solve loop tanpa henti, dan membagi kuota secara adil antar tim atau developer.

Bagaimana cara menyinkronkan limiter kalau saya menjalankan beberapa worker atau server?

Pembatas di memori (in-memory) seperti pada contoh kode di atas hanya berlaku per proses. Untuk beberapa worker atau server, gunakan rate limiting berbasis Redis — pustaka seperti redis-rate-limiter (Python) atau rate-limiter-flexible (Node.js) mendukung pembatasan terdistribusi dengan operasi atomik, sehingga semua worker berbagi satu hitungan yang sama.

Apakah rate limiting sendiri juga membantu menghindari pemblokiran dari situs target?

Bisa. Kalau situs target menerapkan ambang batas seperti pada skenario ketiga di daftar sebelumnya, menjaga laju request tetap konsisten dan tidak meledak dalam sekejap membantu Anda tetap di bawah ambang tersebut. Mulai dari rate yang konservatif, lalu naikkan bertahap sambil memantau respons error dari situs target.

Bacaan Lanjutan

Langkah Selanjutnya

Kontrol laju dan anggaran solve CAPTCHA Anda mulai sekarang — ambil API key CaptchaAI Anda dan pasang salah satu pola di atas di depan integrasi Anda.

Komentar dinonaktifkan untuk artikel ini.