币安 API 限频的核心机制是 权重桶(Weight Bucket):每个 IP 每分钟最多消耗 6000 权重(Spot),每个接口按复杂度扣除 1-100 不等的权重分,超限返回 429 Too Many Requests,重复违规会升级为 418 并 IP 封禁 2 分钟至 3 天。本文按「权重计算 → 响应头监控 → 客户端限频 → WebSocket 替代」四步给出完整代码,让你的策略稳定运行不触发风控。尚未持有 API 密钥的用户请先在 币安官网 完成 KYC;没有账号的可以 免费注册。
一、限频的三个维度
币安对同一个 IP / API Key 施加三种独立限制:
| 维度 | Spot 上限 | Futures 上限 | 违规响应 |
|---|---|---|---|
| 请求权重(REQUEST_WEIGHT) | 6000 / 分钟 | 2400 / 分钟 | 429 |
| 下单次数(ORDERS) | 100 / 10 秒;200000 / 天 | 300 / 10 秒;1200 / 分钟 | 429 |
| 连接数(RAW_REQUESTS) | 61000 / 5 分钟 | 61000 / 5 分钟 | 429 |
| IP 封禁 | 连续违规 | 连续违规 | 418 |
关键认知:下单接口权重虽然只有 1,但会同时消耗 ORDERS 桶,两个桶任一达到上限都会被限。
二、查询当前权重配额
通过 GET /api/v3/exchangeInfo 的 rateLimits 字段获取当前账户的实时配额:
curl -s "https://api.binance.com/api/v3/exchangeInfo" | \
jq '.rateLimits'
返回:
[
{"rateLimitType": "REQUEST_WEIGHT", "interval": "MINUTE", "intervalNum": 1, "limit": 6000},
{"rateLimitType": "ORDERS", "interval": "SECOND", "intervalNum": 10, "limit": 100},
{"rateLimitType": "ORDERS", "interval": "DAY", "intervalNum": 1, "limit": 200000},
{"rateLimitType": "RAW_REQUESTS", "interval": "MINUTE", "intervalNum": 5, "limit": 61000}
]
VIP 等级高的账户可以申请提升权重上限,但标准账户 6000 已能满足 90% 策略需要。
三、常见接口的权重对照表
| 接口 | 权重 | 说明 |
|---|---|---|
| GET /api/v3/ping | 1 | 连通测试 |
| GET /api/v3/time | 1 | 服务器时间 |
| GET /api/v3/exchangeInfo | 20 | 交易规则(缓存 1 小时即可) |
| GET /api/v3/ticker/price (单 symbol) | 1 | 单品种价格 |
| GET /api/v3/ticker/price (全部) | 4 | 一次拿全部行情 |
| GET /api/v3/ticker/24hr (单 symbol) | 1 | 单品种 24h 统计 |
| GET /api/v3/ticker/24hr (全部) | 80 | 全部交易对统计 |
| GET /api/v3/depth limit=5/10/20/50/100 | 1 | 深度 |
| GET /api/v3/depth limit=500 | 5 | 深度 |
| GET /api/v3/depth limit=1000 | 10 | 深度 |
| GET /api/v3/depth limit=5000 | 50 | 深度(慎用) |
| GET /api/v3/klines | 2 | K 线 |
| GET /api/v3/historicalTrades | 5 | 历史成交 |
| GET /api/v3/account | 20 | 账户余额 |
| GET /api/v3/openOrders (单 symbol) | 6 | 当前挂单 |
| GET /api/v3/openOrders (全部) | 80 | 所有挂单 |
| GET /api/v3/allOrders | 20 | 历史订单 |
| POST /api/v3/order | 1 | 下单 |
| DELETE /api/v3/order | 1 | 撤单 |
| DELETE /api/v3/openOrders | 1 | 全撤 |
性能陷阱:不要循环调用单 symbol 的 ticker/24hr,直接用不带参数的一次性接口,权重从 300 个 symbol × 1 = 300 降到 80。
四、读取响应头做自适应限频
每次 REST 请求响应都会返回当前已用权重,客户端应该读取并动态调整:
import requests, time
BASE_URL = "https://api.binance.com"
class RateLimiter:
def __init__(self, max_weight=6000, safety_ratio=0.8):
self.max_weight = max_weight
self.safety = safety_ratio # 只用 80% 预防冷启动误差
self.used_weight = 0
def update_from_headers(self, headers: dict):
used = headers.get("X-MBX-USED-WEIGHT-1m")
if used:
self.used_weight = int(used)
def should_wait(self) -> float:
"""返回建议等待秒数,0 代表可直接调用"""
threshold = self.max_weight * self.safety
if self.used_weight >= threshold:
# 距下一分钟 reset 的秒数估算
return 60 - (int(time.time()) % 60)
return 0
limiter = RateLimiter()
def safe_get(path, params=None):
wait = limiter.should_wait()
if wait > 0:
print(f"[限频] 已用 {limiter.used_weight} 权重,暂停 {wait}s")
time.sleep(wait)
r = requests.get(f"{BASE_URL}{path}", params=params, timeout=10)
limiter.update_from_headers(r.headers)
return r.json()
# 使用
data = safe_get("/api/v3/ticker/24hr")
print(f"当前权重占用:{limiter.used_weight}/6000")
五、令牌桶限频(客户端主动控制)
比被动看响应头更稳妥的做法是 客户端令牌桶:
import time, threading
class TokenBucket:
def __init__(self, capacity=6000, refill_per_sec=100):
self.capacity = capacity
self.tokens = capacity
self.refill = refill_per_sec # 6000/60 = 100 /秒
self.last = time.time()
self.lock = threading.Lock()
def acquire(self, cost=1):
with self.lock:
now = time.time()
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill)
self.last = now
if self.tokens < cost:
wait = (cost - self.tokens) / self.refill
time.sleep(wait)
self.tokens = 0
else:
self.tokens -= cost
bucket = TokenBucket(capacity=6000, refill_per_sec=100)
def call(path, weight):
bucket.acquire(weight)
return requests.get(f"{BASE_URL}{path}").json()
# 下单(权重 1)前 + 查余额(权重 20)配合使用
call("/api/v3/ticker/price", 1)
call("/api/v3/account", 20)
六、遇到 429 和 418 的正确处理
1. 收到 429
def request_with_retry(method, url, **kwargs):
for attempt in range(3):
r = requests.request(method, url, **kwargs)
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", 60))
print(f"触发限频,休眠 {retry_after}s")
time.sleep(retry_after)
continue
if r.status_code == 418:
print("IP 已封禁,应用程序需要停止!")
raise SystemExit(1)
return r
raise Exception("超过最大重试次数")
Retry-After 响应头会给出 精确的等待秒数,直接 sleep 即可。
2. 收到 418
418 是严重警告:重复在 429 后继续打请求会被封 IP 2 分钟起步,极端情况 3 天。一旦收到必须立刻停止所有请求,等待至少 Retry-After 的时间后才能恢复。
七、WebSocket 替代:几乎 0 权重消耗
REST 拉取行情每次消耗权重,WebSocket 订阅只在建立连接时消耗一次,实时推送不占权重:
import json, websocket
def on_message(ws, message):
data = json.loads(message)
print(f"{data['s']} 最新价 {data['c']}, 24h 量 {data['v']}")
ws = websocket.WebSocketApp(
"wss://stream.binance.com:9443/stream?streams=btcusdt@ticker/ethusdt@ticker",
on_message=on_message
)
ws.run_forever()
换算成本:订阅 10 个交易对的实时 ticker,REST 轮询 1 秒一次 = 600 次/分钟 × 权重 1 = 600 权重;WebSocket 订阅 = 0 权重。
八、权重优化实战建议
- 缓存 exchangeInfo:1 小时更新一次足够,频率提高只是浪费 20 权重 / 次
- 批量查询优先:
/ticker/24hr不带参数一次拿全部比循环单个省 90% 权重 - 深度只取需要的 limit:限价单只需要 limit=20 即可(权重 1),不要拉 5000 层
- 订单状态监听用 userDataStream:比定时
GET /order高效且零权重 - 撤全单用批量接口:
DELETE /openOrders?symbol=BTCUSDT权重 1,比逐个撤单更节省 - 分时段限频:UTC 0 点、UTC 8 点、UTC 16 点是结算窗口,权重消耗更紧张,策略可以错峰
九、常见问题 FAQ
Q1: 权重是按 IP 算还是按 API Key 算?
A: 按 IP 为主。同一 IP 下多个 Key 共享权重桶;切换 IP 可以绕开 IP 级权重但下单限制仍按账户算。公网代理动态切 IP 规避权重会被币安识别,不推荐。
Q2: 响应头 X-MBX-USED-WEIGHT 和 X-MBX-USED-WEIGHT-1m 有什么区别?
A: X-MBX-USED-WEIGHT-1m 是官方推荐,标明是 1 分钟窗口;X-MBX-USED-WEIGHT 是历史遗留字段,值相同。以 -1m 后缀的为准。
Q3: 我被 418 封了要等多久?
A: 首次违规 2 分钟,升级会到 5/15/60 分钟,极端情况 3 天。Retry-After 头给出精确时间。恢复后立即优化代码,避免短期内重复违规导致累积升级。
Q4: 多线程并发调用如何统一计算权重?
A: 用进程级全局令牌桶(见本文第五节),或用 Redis 分布式令牌桶(多机部署时必需)。单机内的 threading.Lock 足够。
Q5: 测试网 testnet.binance.vision 的权重规则一样吗?
A: 测试网权重更宽松(通常是主网的 10 倍),但规则结构一致。不要基于测试网的权重消耗评估主网表现,上线前务必在主网小流量验证。
看完限频策略,回到 分类导航 选择「API接入」分类查看 WebSocket 与签名实战。