Если баланс тает быстрее, чем должен, или сайт банит после сотни запросов в минуту — CaptchaAI тут ни при чём: сервис сам не режет скорость запросов, лимит нужно завести на своей стороне. Три рабочих шаблона клиентского rate limiting для CaptchaAI: token bucket, sliding window и лимит по бюджету, с кодом на Python и JavaScript.
Типичный кейс — агентство парсит объявления на нескольких сайтах силами трёх-четырёх воркеров, которые делят один API-ключ. Без общего лимита каждый воркер тратит бюджет независимо: к вечеру баланс может уйти в ноль, а IP-адреса — попасть под блокировку целевого сайта за резкий всплеск запросов. Три шаблона ниже решают эту проблему на разных уровнях: от точного контроля скорости до жёсткого потолка расходов.
Зачем ограничивать скорость самостоятельно
| Сценарий | Без лимита | С лимитом |
|---|---|---|
| Баг зациклил решение | Баланс сгорает за минуты | Останов на пределе |
| Один ключ на несколько команд | Расходы не координируются | Честная доля каждой |
| Сайт банит при > 100 req/мин | Аккаунты блокируются | Ниже порога |
| Бюджет $50/мес | Превышается за день | Предел соблюдается |
Способ 1: token bucket (ведро токенов)
Token bucket допускает всплески, но удерживает среднюю скорость: токены пополняются с фиксированной частотой, каждый запрос списывает один токен.
Token bucket на 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")
Совет: начните с
capacity, равного ожидаемому всплеску (например, числу одновременно открытых вкладок браузера), а не с произвольного числа — так проще подобратьrateпод реальный трафик, а не под интуицию.
Когда token bucket подходит лучше всего:
- нагрузка неравномерна, а всплески — нормальный режим работы;
- решения ставятся в очередь пачками (например, при пакетном парсинге);
- нужен запас на кратковременные пики без потери задач в очереди.
Способ 2: скользящее окно (sliding window)
Sliding window считает количество запросов за фиксированный интервал времени. Проще, чем token bucket, но не даёт управлять пакетными всплесками.
Sliding window на 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');
}
Как подключить sliding window за три шага:
- Определите
maxRequestsиwindowMsпо реальному лимиту целевого сайта или по тарифу CaptchaAI, а не наугад. - Вызывайте
limiter.acquire()до обращения кin.php, а не после — иначе лишний запрос уже уйдёт. - Логируйте случаи
Rate limit exceeded, чтобы вовремя скорректироватьmaxRequestsпод реальную нагрузку.
Способ 3: лимит по бюджету
Дневной бюджет — и стоп, когда он исчерпан:
# 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
cost_per_solve — внутренняя оценка, не реальный тариф: CaptchaAI берёт плату по потокам (BASIC — $15/мес, 5 потоков), решения не ограничены.
Пример: агентство с бюджетом $150/мес на троих клиентов может завести отдельный BudgetLimiter на каждого — тогда расход одного проекта не съедает лимит соседнего, а по каждому клиенту автоматически получается отчёт о фактических тратах без ручной сверки.
Пример из практики: три воркера, один API-ключ
Агентство собирает данные с нескольких сайтов объявлений силами трёх воркеров на разных серверах — условно в Москве, Алматы и Киеве. Ключ API общий, тариф — BASIC ($15/мес, 5 потоков). Каждый воркер запускает собственный процесс, поэтому локальный TokenBucket в памяти каждого из них считает независимо: суммарная частота запросов трёх воркеров легко превышает и лимит целевого сайта, и число оплаченных потоков.
Рабочее решение — вынести состояние лимитера в Redis (redis-rate-limiter для Python или rate-limiter-flexible для Node.js) и делить один общий счётчик токенов между процессами. Перед отправкой в in.php каждый воркер спрашивает разрешение у Redis, а не считает токены сам — тогда общий лимит соблюдается независимо от того, сколько машин фактически запущено в моменте.
Поиск неисправностей
| Проблема | Причина | Что делать |
|---|---|---|
| Запросы копятся в очереди | Частота лимитера ниже нагрузки | Увеличьте частоту/окно |
| Бюджет сбрасывается среди дня | Сбились часы или рестарт | Храните расход в файле/БД |
| Token bucket пустеет за всплеск | capacity мал для нагрузки |
Увеличьте capacity |
| Лимитер блокирует опрос | Лимит стоит и на опросе тоже | Ограничивайте только in.php |
Как выбрать подходящий шаблон
| Шаблон | Когда применять | Сложность |
|---|---|---|
| Token bucket | Контроль с поддержкой всплесков | Средняя |
| Sliding window | Подсчёт запросов за интервал | Низкая |
| Бюджетный лимитер | Расходы за день/неделю/месяц | Низкая |
| Комбинация | Продакшн-системы | Средняя |
Частые вопросы
Нужно ли ограничивать и отправку, и опрос задачи?
Только отправку (in.php). Опрос (res.php) не создаёт задачу и не тратит баланс — лимит на него лишь всё замедлит.
Как синхронизировать лимиты между несколькими воркерами?
Локальный лимитер в памяти каждого процесса не годится — переносите счётчик в Redis, как описано выше, и опрашивайте его перед каждой отправкой в in.php.
CaptchaAI сам режет частоту моих запросов?
Нет: сервис выдерживает высокий параллелизм и не ограничивает скорость в рамках оплаченных потоков — лимит работает только у вас, на клиенте.
Что делать, если после включения лимитера всё ещё приходит 429?
Проверьте, не упираетесь ли вы в число потоков по тарифу (BASIC — 5 потоков): лишние запросы ждут свободный поток независимо от лимитера, а лимит должен проверяться до отправки, не постфактум.
Нужен ли отдельный лимитер для каждого типа CAPTCHA?
Нет: CaptchaAI тарифицирует по потокам, а не по типу CAPTCHA, поэтому один лимитер на уровне аккаунта — на все запросы к in.php независимо от параметра method — корректно покрывает и reCAPTCHA, и Cloudflare Turnstile, и GeeTest v3 одновременно.