速率限制
Crawlbase 实施的是按 token 的并发限制,而非每分钟请求数。您可以尽情发送,只需将并行进行中的请求数控制在上限以内即可。
默认限制
每个账号开始时都享有同样宽松的默认值。您无需申请,注册的那一刻起,这些默认值就已经应用到您的每个 token 上。
| 限制项 | 默认值 | 作用范围 |
|---|---|---|
| 并发请求 | 20 | 每个 token |
| 每秒请求数 | 约 20(推算) | 每个 token |
| 每月总请求数 | 最高 51,000,000 | 每个 token |
| 单请求超时 | 90 秒 | 每个请求 |
| Crawler 队列大小 | 100,000 个 URL | 每个 Crawler |
"并发"指的是同一时刻正在处理中的请求数。如果每次抓取耗时 2 秒,并保持 20 个并行请求,那么大致可以持续达到 10 req/sec,按这个计算每个 token 每天约可处理 864K 个请求。
不要试图把速率限制为"每秒 X 个请求",只需将您的 worker pool 上限设为 20,让请求延迟来决定吞吐量。这样更简单,也更符合该限制在服务端的实际工作方式。
达到限制时会发生什么
一旦超过并发上限,Crawlbase 会返回 HTTP 429 Too Many Requests。该请求会被拒绝,不会进入队列,因此您应使用退避策略进行重试。
// HTTP/1.1 429 Too Many Requests
// Retry-After: 1
// X-Crawlbase-Concurrency: 20
{ "error": "Concurrency limit reached", "limit": 20 }Retry-After 响应头会告诉您重试前应等待的最少秒数。请始终遵守。
优雅地处理速率限制
正确的模式是带抖动的指数退避。大多数 HTTP 客户端已内置此功能;如果您需要自行实现,下面是最简版本。
import time, random
from crawlbase import CrawlingAPI
api = CrawlingAPI({'token': 'YOUR_TOKEN'})
def crawl_with_retry(url, attempts=5):
for i in range(attempts):
res = api.get(url)
if res['status_code'] != 429:
return res
wait = (2 ** i) + random.random()
time.sleep(wait)
raise RuntimeError('Rate limit exhausted')const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'YOUR_TOKEN' });
async function crawlWithRetry(url, attempts = 5) {
for (let i = 0; i < attempts; i++) {
const res = await api.get(url);
if (res.statusCode !== 429) return res;
const wait = (2 ** i) * 1000 + Math.random() * 1000;
await new Promise(r => setTimeout(r, wait));
}
throw new Error('Rate limit exhausted');
}对于持续触及上限的工作负载,请使用工作池,而不是 fire-and-forget。将池上限设为您的并发限制,您就再也不会看到 429。
突破默认值进行扩展
当 20 并发不够用时,有三种选择:
最佳实践
- 将 worker pool 上限设为您的 token 限制值。不要过度订阅然后依赖 429 来兜底,被拒绝的请求依然消耗一次往返。
- 对慢速目标使用
async=true。长时间运行的 JS 渲染抓取会在整个请求期间占用一个并发槽位。异步模式会立即释放槽位,并通过 webhook 投递结果。 - 始终遵守
Retry-After。忽略它只会产生更多 429,并浪费网络往返。 - 为重试添加抖动。如果 50 个 worker 在瞬时峰值后都恰好在 1 秒时重试,那您只会再制造一次峰值。
- 对持续的 429 比例进行告警。偶发的 429 没问题。但持续 5% 以上的比例意味着您需要更高的并发或更智能的调度。