Python开发Discord机器人出现WebSocket连接关闭异常如何稳定运行
问题根因
- 你手动实现的Discord网关WebSocket连接没有异常处理逻辑,Discord网关会不定期主动断开连接做负载均衡,遇到断连时
ws.recv()直接抛出WebSocketConnectionClosedException,没有被捕获就会导致程序崩溃 - 心跳逻辑存在错误:心跳包的
d字段传了字符串'null',按照Discord网关规范应该传空值(Python的None),不符合规范的心跳会被服务器主动断开连接 - 同时使用discord.py客户端和手动维护的WebSocket连接属于重复实现,discord.py本身已经封装了完整的网关连接、心跳、自动重连逻辑,稳定性远高于手动实现的代码
推荐解决方案(最优):直接使用discord.py原生功能
完全删除你自己写的WebSocket相关代码,用discord.py内置的on_message事件监听消息,自带7*24小时自动重连,稳定性有保障,代码简化后如下:
import discord from collections import Counter from discord.ext import commands # 填写你自己的配置 LISTEN_CHANNEL_ID = 123456789 # 要监听的频道ID,填整数不要填字符串 SEND_CHANNEL_ID = 987654321 # 要推送的频道ID,填整数 BOT_TOKEN = "你的机器人私钥" intents = discord.Intents.default() intents.message_content = True # 要在Discord开发者后台开启消息内容权限 client = discord.Client(intents=intents) count_apparition = [] @client.event async def on_ready(): print("Bot ready") @client.event async def on_message(message): # 只处理监听频道的消息 if message.channel.id != LISTEN_CHANNEL_ID: return # 处理消息里的embed if message.embeds: for embed in message.embeds: if not embed.description: continue print(embed.description) try: crypto = embed.description.split()[embed.description.split().index('to')-2].split("(")[0] except (ValueError, IndexError): continue print(crypto) count_apparition.append(crypto) t = Counter(count_apparition) data = t.most_common() s = ['Tickers Apparitions'] for item in data: s.append(' '.join([str(i).center(10, ' ') for i in item])) d = '```' + '\n'.join(s) + '```' send_embed = discord.Embed(title = 'MY TITLE', description = d) send_channel = client.get_channel(SEND_CHANNEL_ID) await send_channel.send(embed=send_embed) client.run(BOT_TOKEN)
如果你坚持要保留手动WebSocket实现,添加重连逻辑的方法
- 封装WebSocket初始化函数,异常时自动重试,加退避延迟避免被限流
- 捕获
WebSocketConnectionClosedException异常,断开时自动重建连接、重新发送鉴权 payload、重启心跳线程 - 修复心跳包的错误字段
关键修改代码如下:
import websocket, json, threading, time from websocket._exceptions import WebSocketConnectionClosedException TOKEN = "你的TOKEN" GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json" # 用最新的v10网关 ws = None heartbeat_thread_running = False def send_json_request(ws, request): try: ws.send(json.dumps(request)) except: pass def receive_json_response(ws): try: response = ws.recv() if response: return json.loads(response) except WebSocketConnectionClosedException: return None def heartbeat(interval, ws): print('Heartbeat begin') while heartbeat_thread_running: time.sleep(interval) if not ws.connected: break heartbeatJSON = { 'op': 1, 'd': None # 修复这里的null值 } send_json_request(ws, heartbeatJSON) print("Heartbeat sent") def init_ws(): global ws, heartbeat_thread_running # 停止旧的心跳线程 heartbeat_thread_running = False time.sleep(1) # 重连,加退避延迟 retry_delay = 1 while True: try: ws = websocket.WebSocket() ws.connect(GATEWAY_URL) event = receive_json_response(ws) if not event: raise Exception("Invalid gateway response") # 启动心跳 heartbeat_interval = event["d"]["heartbeat_interval"] / 1000 heartbeat_thread_running = True threading._start_new_thread(heartbeat, (heartbeat_interval, ws)) # 发送鉴权 payload = { "op": 2, "d": { "token": TOKEN, "properties": { "$os": 'windows', "$browser": 'brave', "$device": "pc" }, "intents": 1 << 15 # 开启消息内容意图 } } send_json_request(ws, payload) print("WebSocket reconnected successfully") return except Exception as e: print(f"WebSocket connect failed, retry in {retry_delay}s: {e}") time.sleep(retry_delay) retry_delay = min(retry_delay * 2, 30) # 最大重试延迟30秒
然后在你的test循环里判断event为None时调用init_ws()重连即可:
@tasks.loop(seconds = 1) async def test(): global ws channel = client.get_channel(SEND_CHANNEL_ID) while True: event = receive_json_response(ws) if event is None: # 连接断开,重连 init_ws() continue # 后面是你原来的业务逻辑 try: # ... 你的原有代码 ... except Exception as e: print(f"Process event error: {e}")
注意事项
- 频道ID需要传整数类型,不要用字符串,否则
get_channel会返回None - 旧的
v6网关版本已经被Discord废弃,建议升级到v10版本 - 不要用空的
except语句,尽量捕获指定异常,避免隐藏代码里的其他错误
内容的提问来源于stack exchange,提问作者Kben59
相关产品推荐
相关产品推荐

