You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python开发Discord机器人出现WebSocket连接关闭异常如何稳定运行

问题根因
  1. 你手动实现的Discord网关WebSocket连接没有异常处理逻辑,Discord网关会不定期主动断开连接做负载均衡,遇到断连时ws.recv()直接抛出WebSocketConnectionClosedException,没有被捕获就会导致程序崩溃
  2. 心跳逻辑存在错误:心跳包的d字段传了字符串'null',按照Discord网关规范应该传空值(Python的None),不符合规范的心跳会被服务器主动断开连接
  3. 同时使用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实现,添加重连逻辑的方法
  1. 封装WebSocket初始化函数,异常时自动重试,加退避延迟避免被限流
  2. 捕获WebSocketConnectionClosedException异常,断开时自动重建连接、重新发送鉴权 payload、重启心跳线程
  3. 修复心跳包的错误字段
    关键修改代码如下:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 18:45:00