CaptchaAI 处理高并发,但您可能想限制自己。原因:成本控制、避免错误造成的意外峰值、团队之间公平的资源共享或匹配目标站点设置的速率限制。本指南实现了三种客户端速率限制模式CaptchaAI。
为什么要限制客户端速率
| 设想 | 无限制 | 有限制 |
|---|---|---|
| Bug 导致无限求解循环 | 燃烧平衡 | 在配置的上限处停止 |
| 多个团队共享一个 API 密钥 | 支出不协调 | 每队公平分配 |
| 目标网站禁止数量超过 100 个 req/min | 帐户被封锁 | 保持在阈值以下 |
| 预算为 $50/month | 一下午可能会超过 | 强制实施硬上限 |
模式一:令牌桶
令牌桶允许突发,同时强制执行平均速率。代币以固定速率补充;每个请求消耗一个令牌。
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")
模式2:滑动窗口计数器
跟踪固定时间窗口内的请求数量。比令牌桶简单,但没有突发控制。
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');
}
模式 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
选择模式
| 图案 | 最适合 | 复杂 |
|---|---|---|
| 令牌桶 | 具有突发容限的平滑速率控制 | 中等的 |
| 推拉窗 | 每个时间窗口的简单请求计数 | 低的 |
| 预算限制 | 每天成本控制/week/month | 低的 |
| 合并(费率+预算) | 生产系统 | 中等的 |
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 所有请求排队,没有执行 | 利率太低,无法满足需求 | 增加速率或窗口大小 |
| 预算限制器在中午重置 | 更改系统时钟或重新启动 | 将每日支出保存到文件或数据库 |
| 令牌桶突然耗尽 | 容量对于工作流程来说太小 | 增加容量参数 |
| 速率限制器阻止轮询请求 | 限制器也适用于民意调查 | 只限制提交请求,不限制投票 |
常问问题
我应该对提交请求或投票请求进行速率限制吗?
仅对 提交 (in.php) 请求进行速率限制。轮询(res.php)应该自由运行,因为它不会创建新任务或花钱。
如何在多个工作人员之间共享速率限制?
使用 Redis 支持的速率限制。 redis-rate-limiter (Python) 或 rate-limiter-flexible (Node.js) 等库支持原子操作的分布式速率限制。
CaptchaAI 可以告诉我我目前的使用率吗?
使用余额端点来跟踪支出。有关详细用法,请实现您自己的计数器,如本指南中所示。
相关文章
下一步
控制您的验证码解决率和预算 –”获取您的 CaptchaAI API 密钥。
相关指南: