Discord.py 中正确实现 Bot 状态轮换以避免速率限制的完整指南

本文详解如何在 discord.py 中安全地轮换 bot 的在线状态(如活动状态),避免触发 websocket 速率限制警告,重点修正 `asyncio.sleep` 未 await 导致的高频请求问题,并提供错误重试机制与最佳实践。

在 Discord.py 中,频繁调用 bot.change_presence()(例如在无限循环中)极易触发网关速率限制(Gateway Rate Limit),表现为控制台持续输出类似 WARNING discord.gateway WebSocket

in shard ID None is ratelimited, waiting X seconds 的警告——即使你设置了 5 分钟(300 秒)间隔,问题仍存在,根本原因在于:asyncio.sleep(300) 本身是协程对象,若未用 await 调用,它不会真正暂停执行,而是立即返回并进入下一轮循环,导致 change_presence 实际以毫秒级频率被反复调用,远超 Discord 的允许阈值(通常为每 15 秒最多 1 次)

以下是修复后的推荐实现,兼顾稳定性、可维护性与健壮性:

import asyncio
import random
import discord
from discord.ext import commands

actaray = [
    "PcktWtchr's Videos",
    "Cams",
    "and Listening Always",
    "or Listening or Both"
]

@bot.event
async def on_ready():
    print(f'Logged in as {bot.user}')
    # 启动后台任务(推荐方式,避免阻塞事件循环)
    bot.loop.create_task(presence_rotation())

async def presence_rotation():
    while True:
        try:
            activity = discord.Activity(
                type=discord.ActivityType.watching,
                name=random.choice(actaray)
            )
            await bot.change_presence(activity=activity)
            # ✅ 正确使用 await:真正暂停协程 300 秒
            await asyncio.sleep(300)
        except discord.HTTPException as e:
            if e.status == 429:  # 429 Too Many Requests
                retry_after = int(e.response.headers.get("Retry-After", "5"))
                print(f"Rate limited! Retrying after {retry_after} seconds...")
                await asyncio.sleep(retry_after + 1)  # 加 1 秒缓冲,避免边界重试失败
            else:
                print(f"HTTP error during presence update: {e}")
                await asyncio.sleep(60)  # 其他 HTTP 错误降频重试
        except Exception as e:
            print(f"Unexpected error in presence rotation: {e}")
            await asyncio.sleep(60)

# 可选:全局错误处理器(补充兜底)
@bot.event
async def on_error(event, *args, **kwargs):
    if event == "on_ready" and args and isinstance(args[0], discord.HTTPException):
        if args[0].status == 429:
            retry_after = int(args[0].response.headers.get("Retry-After", "5"))
            print(f"on_ready rate limit hit. Waiting {retry_after}s before retry...")
            await asyncio.sleep(retry_after + 1)

关键改进说明:

  • await asyncio.sleep(300) 替代 asyncio.sleep(300):这是最核心的修复。协程必须 await 才会挂起当前任务,否则循环体瞬间执行完毕,造成“伪延迟”。
  • 封装为独立异步任务:使用 bot.loop.create_task() 启动 presence_rotation(),比直接在 on_ready 中写 while True 更清晰,也便于后续扩展(如动态启停)。
  • 精细化异常捕获:单独处理 discord.HTTPException,尤其识别 429 状态码,并读取响应头中的 Retry-After 字段进行精准退避,而非盲目等待固定时间。
  • 防御性编程:对非 429 错误(如网络中断、认证失效)设置合理 fallback 延迟(如 60 秒),防止异常导致高频重试。

注意事项:

  • Discord 官方未公开精确的 change_presence 速率限制规则,但实测建议 最低间隔 ≥ 15 秒,5 分钟(300 秒)完全安全——前提是真正生效。
  • 避免在 on_ready 内直接写阻塞式 while True 循环(尤其未加 await),这会干扰事件循环调度,甚至影响其他事件响应。
  • 若 Bot 运行于多分片(shard)环境,请确保状态更新逻辑对所有分片一致,或使用 bot.wait_until_ready() 做前置校验。

通过以上改造,你的 Bot 将稳定、合规地轮换状态,彻底告别烦人的速率限制警告。