分类: Discord 机器人开发
Discord 机器人开发#11用 Flask 制作网页仪表盘!(Python 示例)
本文译自韩语原文。
目录10 项
大家好!我是 Mojangcam 😎
上一篇 #10 我们讲了让机器人变成全球化机器人的多语言(i18n)支持。原本预告的是冷却时间和权限管理,不过在那之前,先带来很多人好奇的主题。那就是制作网页仪表盘(Web Dashboard)!(冷却时间篇也很快就会发布,敬请期待)
把机器人部署到服务器上之后,总会冒出这样的念头:"机器人现在还活着吗……?"与其每次都打开 Discord 去确认,如果有一个页面,只要在浏览器地址栏输入网址,机器人状态、服务器数量、延迟(Ping)就能一目了然,是不是方便多了?今天我们就用 Python Web 框架 Flask 来做一个这样的迷你监控台。

🧠 为什么选 Flask?
Flask 是 Python 里最有名的微型 Web 框架。正如"微型"这个名字,只要几行代码就能搭出一个 Web 服务器,拿来给机器人配一个仪表盘再合适不过。
- 用浏览器(手机也行!)直接查看机器人是否在线
- 实时监控服务器数量、用户数量和响应速度
- 以后还能扩展成命令统计、日志查看器等,扩展空间无限
只有一个概念需要先了解。discord.py 是以异步(asyncio)方式运行的程序,而 Flask 是同步方式的 Web 服务器。想让两者在同一个文件里一起运行,就需要一个小技巧:把 Flask 放到单独的线程(Thread)里运行。今天就把这个方法彻底学会!
📌 准备工作
- Python 3.9 及以上(Flask 3.1 需要 3.9+)
- discord.py 2.x(本文以 2.7.1 为准)
- Flask 3.1.x(本文以 3.1.3 为准)
- 机器人 Token + 已邀请机器人的测试服务器(参考 #1)
pip install -U discord.py flask📌 第 1 步 - 先单独启动 Flask 服务器
在和机器人合并之前,先用一个 5 行的最小示例感受一下 Flask 长什么样。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "机器人仪表盘准备中!"
app.run(host="0.0.0.0", port=5000)我们逐个拆开来看 💡
Flask(__name__):创建 Web 应用对象。__name__的意思是"当前这个文件就是应用的基准点"。@app.route("/"):表示浏览器访问/地址(首页)时,执行下面的函数。和斜杠命令的装饰器是同样的原理吧?host="0.0.0.0":不只允许本机,也允许同一网络里的其他设备(手机等)访问。如果只有自己看,改成127.0.0.1也可以。port=5000:访问地址就是http://localhost:5000。
运行后在浏览器打开 http://localhost:5000,就能看到那句文字。确认之后按 Ctrl+C 关掉,进入下一步!
📌 第 2 步 - 让机器人和 Flask 在同一个文件里一起运行
这里是今天的核心。bot.run() 和 app.run() 都是会把整个程序牢牢占住(阻塞)的函数,直接接着写的话只有其中一个会运行。解决办法就是把 Flask 分离到守护线程(daemon thread)里。
import threading
def run_flask():
app.run(host="0.0.0.0", port=5000, use_reloader=False)
threading.Thread(target=run_flask, daemon=True).start() # Flask 在单独的线程中运行
bot.run("在这里输入机器人 Token") # 机器人在主线程中运行我们逐个拆开来看 💡
threading.Thread(...):让 Flask 服务器和机器人同时运行的另一条执行路线。daemon=True:主程序(机器人)关闭时,Flask 也一起关闭。没有这个的话,即使关掉机器人,Flask 也会留着,终端就关不掉。use_reloader=False:关闭 Flask 的自动重启功能。这个功能只能在主线程里工作,开着的话在线程里运行会报错。(参考常见报错第 1 条!)- 注意顺序:
bot.run()因为要处理信号(signal),必须放在主线程里。这就是要把 Flask 放到线程里的原因。
📌 第 3 步 - 制作机器人状态 API + 仪表盘页面
现在来做一个以 JSON 返回机器人信息的 API,以及一个每 5 秒获取一次数据并显示出来的 HTML 页面。
from flask import jsonify
@app.route("/api/stats")
def stats():
return jsonify({
"online": bot.is_ready(),
"name": str(bot.user) if bot.user else "连接中...",
"servers": len(bot.guilds),
"members": sum(g.member_count or 0 for g in bot.guilds),
"ping_ms": round(bot.latency * 1000) if bot.is_ready() else None,
})我们逐个拆开来看 💡
jsonify(...):把 Python 字典转换成浏览器能读取的 JSON 响应。bot.is_ready():机器人是否已经完全登录到 Discord。用来显示仪表盘上的在线/离线状态。bot.guilds:机器人所在的服务器列表。用len()统计服务器数量。g.member_count:每个服务器的成员数。用默认 Intent 也能读取(不需要单独开启 members Intent)。bot.latency:和 Discord 之间的响应延迟。单位是秒的浮点数,所以要 ×1000 换算成 ms。登录前没有值(nan),所以先用is_ready()过滤一下。
仪表盘页面(HTML)已经放在完整代码里了。结构很简单:用 fetch() 每 5 秒请求一次 /api/stats,然后更新数字。
📌 完整代码(可直接复制)
# discord.py 2.7.x / Flask 3.1.x / Python 3.9+
import threading
import discord
from discord.ext import commands
from flask import Flask, jsonify, render_template_string
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
app = Flask(__name__)
PAGE = """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>机器人仪表盘</title>
<style>
body { font-family: sans-serif; background: #313338; color: #fff;
display: flex; flex-direction: column; align-items: center; }
h1 { margin-top: 40px; }
.cards { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; }
.card { background: #2b2d31; border-radius: 12px; padding: 24px 32px;
text-align: center; min-width: 140px; }
.card .num { font-size: 32px; font-weight: bold; color: #5865f2; }
.on { color: #23a55a; }
.off { color: #f23f43; }
</style>
</head>
<body>
<h1>🤖 <span id="name">加载中...</span></h1>
<p id="status" class="off">● 检查中</p>
<div class="cards">
<div class="card"><div class="num" id="servers">-</div>服务器</div>
<div class="card"><div class="num" id="members">-</div>用户</div>
<div class="card"><div class="num" id="ping">-</div>延迟(ms)</div>
</div>
<script>
async function refresh() {
const res = await fetch("/api/stats");
const d = await res.json();
document.getElementById("name").textContent = d.name;
const s = document.getElementById("status");
s.textContent = d.online ? "● 在线" : "● 离线";
s.className = d.online ? "on" : "off";
document.getElementById("servers").textContent = d.servers;
document.getElementById("members").textContent = d.members;
document.getElementById("ping").textContent = d.ping_ms ?? "-";
}
refresh();
setInterval(refresh, 5000); // 每 5 秒自动刷新
</script>
</body>
</html>
"""
@app.route("/")
def dashboard():
return render_template_string(PAGE)
@app.route("/api/stats")
def stats():
return jsonify({
"online": bot.is_ready(),
"name": str(bot.user) if bot.user else "连接中...",
"servers": len(bot.guilds),
"members": sum(g.member_count or 0 for g in bot.guilds),
"ping_ms": round(bot.latency * 1000) if bot.is_ready() else None,
})
def run_flask():
app.run(host="0.0.0.0", port=5000, use_reloader=False)
@bot.event
async def on_ready():
print(f"{bot.user} 登录成功!")
print("仪表盘:http://localhost:5000")
threading.Thread(target=run_flask, daemon=True).start()
bot.run("在这里输入机器人 Token")📌 运行结果
启动机器人后,终端里会打印出登录消息和仪表盘地址。在浏览器里打开 http://localhost:5000……
- 会显示机器人名称和绿色的 ● 在线 标志
- 服务器数量 / 用户数量 / 延迟(ms)以卡片形式显示,每 5 秒自动刷新
- 关掉机器人(只有仪表盘还开着时),就会变成红色的 ● 离线
- 用连着同一个 Wi-Fi 的手机访问
http://내컴퓨터IP:5000("내컴퓨터IP"部分换成你电脑的 IP 地址),手机上也能查看!


📌 常见报错
- ValueError: signal only works in main thread
原因:在开启 Flask 调试模式(自动重启)的状态下放到线程里运行,或者反过来把
bot.run()放进了线程。两者只要不在主线程,就会在信号处理时崩溃。 解决:一定要加上app.run(..., use_reloader=False),并保持机器人在主线程 / Flask 在线程的结构。 - OSError: [Errno 98] Address already in use(Windows:WinError 10048)
原因:5000 端口已经被其他程序(或者没关掉的上一次运行)占用了。
解决:改用
port=5001这样的其他端口,或者把终端完全关掉后重新运行。 - 仪表盘上显示 0 个服务器、0 个用户 原因:机器人还在登录中,或者机器人没有被邀请到任何服务器。 解决:稍等片刻再刷新看看(每 5 秒会自动刷新)。如果一直是 0,请确认是否已经用机器人邀请链接把它邀请到测试服务器。
📌 结语
今天我们把 Flask 放到单独的线程里,让机器人和 Web 服务器在同一个文件里同时运行,并做出了一个显示机器人状态、服务器数量和延迟的实时网页仪表盘。在这个基础上再加上命令使用统计或最近日志,就能把它扩展成真正的管理后台!
下一篇准备讲防止刷命令的冷却时间(Cooldown)与权限管理。敬请期待 🙌
📚 Discord 机器人开发系列
- Discord 机器人开发 - 彻底掌握 Slash Command!
- Discord 机器人开发 #2 - 给 Slash Command 添加选项(Arguments)!
- Discord 机器人开发 #3 - 给 Slash Command 加入子命令(Subcommand)!
- Discord 机器人开发 #4 - 用 Slash Command 发送嵌入消息!
- Discord 机器人开发 #5 - 实现按钮(Button)交互
- Discord 机器人开发 #6 - 实现下拉菜单(Select Menu)
- Discord 机器人开发 #7 - 用模态框(Modal)接收输入
- Discord 机器人开发 #8 - 用嵌入消息(Embed)的图片和字段美化消息
- Discord 机器人开发 #9 - 接入 AI(Claude·ChatGPT API)
- Discord 机器人开发 #10 - 支持多语言(i18n)
- Discord 机器人开发 #11 - 用 Flask 制作网页仪表盘 ← 就是这一篇!
连载 · 共 32 篇中的第 11 篇
Discord 机器人开发
- #1Discord 机器人开发 - Slash Command 完全攻略!
- #2为 Slash Command 添加选项(Arguments)!
- #3在 Slash Command 中添加子命令(Subcommand)!
- #4用 Slash Command 发送 Embed 嵌入消息!
- #5实现按钮(Button)交互!
- #6实现下拉菜单(Select Menu)!
- #7用模态框(Modal)接收用户输入(Python 示例)
- #8用嵌入消息(Embed)的图片和字段美化消息(Python 示例)
- #9接入 AI(Claude·ChatGPT API)!(Python 示例)
- #10支持多语言(i18n)!(Python 示例)
- #11用 Flask 制作网页仪表盘!(Python 示例)当前文章
- #12管理命令冷却时间(Cooldown)与权限(Permission)!(Python 示例)
- #13用身份组·频道自动化管理服务器!(Python 示例)
- #14保存数据!JSON vs SQLite(Python 示例)
- #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제) (韩语)
- #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제) (韩语)
- #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제) (韩语)
- #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제) (韩语)
- #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제) (韩语)
- #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제) (韩语)
- #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제) (韩语)
- #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제) (韩语)
- #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제) (韩语)
- #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제) (韩语)
- #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제) (韩语)
- #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제) (韩语)
- #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제) (韩语)
- #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제) (韩语)
- #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제) (韩语)
- #30打造投票·问卷(Poll)机器人!原生投票 + 按钮实时统计(Python 示例)🗳️
- #32打造服务器日志·审核日志(Audit Log)频道!自动记录消息删除、编辑、成员进出和踢出(Python 示例)🕵️
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (韩语)
