カテゴリー: Discordボット開発
Discordボット開発#11FlaskでWebダッシュボードを作る! (Pythonサンプル付き)
この記事は韓国語の原文から翻訳したものです。
目次10項目
こんにちは!Mojangcamです 😎
前回の#10では、ボットをグローバルボットにする多言語(i18n)対応を扱いましたね。本来はクールダウン・権限管理を予告していましたが、その前に多くの方が気になっているテーマを先に持ってきました。そう、Webダッシュボード(Web Dashboard)作りです!(クールダウン編ももうすぐ公開するので、お楽しみに)
ボットをサーバーで動かしていると、こんなことが気になってきます。「今、ボットってちゃんと生きてるかな...?」毎回Discordを開いて確認する代わりに、ブラウザのアドレスバーにURLを入れるだけでボットの状態・サーバー数・Pingがひと目でわかるページがあったら、ずっと便利ですよね?今回はPythonのWebフレームワークFlaskで、そんなミニ管制塔を作っていきます。

🧠 なぜFlaskなの?
Flaskは、Pythonでいちばん有名なマイクロWebフレームワークです。「マイクロ」という名前のとおり、数行のコードでWebサーバーがさっと作れるので、ボットに添えるダッシュボード用にぴったりです。
- ボットが動いているかをブラウザ(スマホでも!)ですぐ確認
- サーバー数・ユーザー数・応答速度をリアルタイムで監視
- あとからコマンド統計、ログビューアーなどに無限に拡張できる
ひとつだけ知っておきたい概念があります。discord.pyは非同期(asyncio)で動くプログラムで、Flaskは同期方式のWebサーバーです。この2つを1つのファイルで一緒に動かすには、Flaskを別のスレッド(Thread)で実行するテクニックが必要です。今回はその方法までしっかり身につけましょう!
📌 準備するもの
- Python 3.9以上 (Flask 3.1は3.9+が必要)
- discord.py 2.x (この記事は2.7.1を基準にしています)
- Flask 3.1.x (この記事は3.1.3を基準にしています)
- ボットトークン + ボットを招待したテスト用サーバー (#1を参照)
pip install -U discord.py flask📌 ステップ1 - Flaskサーバーを単体で立ち上げてみる
ボットと組み合わせる前に、Flaskがどんな形をしているのか、5行の最小サンプルで感覚をつかんでみましょう。
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に変えてもOKです。port=5000: アクセスするアドレスはhttp://localhost:5000になります。
実行してブラウザでhttp://localhost:5000を開くと、メッセージが表示されます。確認できたらCtrl+Cで止めて、次のステップへ!
📌 ステップ2 - ボットとFlaskを1つのファイルで一緒に動かす
ここが今回のいちばん大事なところです。bot.run()もapp.run()も、それぞれプログラム全体をつかんで離さない(ブロッキング)関数なので、そのまま続けて書くとどちらか一方しか実行されません。解決策は、Flaskをデーモンスレッドに分けることです。
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("ここにボットトークンを入力") # ボットはメインスレッドひとつずつ見ていきましょう 💡
threading.Thread(...): Flaskサーバーをボットと同時に動かすための、別の処理の流れです。daemon=True: メインプログラム(ボット)が終了すると、Flaskも一緒に終了するようにします。これがないと、ボットを止めてもFlaskが残ってターミナルが閉じません。use_reloader=False: Flaskの自動リロード機能をオフにします。この機能はメインスレッドでしか動かないので、オンのままだとスレッド内でエラーになります。(よくあるエラーの1番を参照!)- 順番に注意 :
bot.run()はシグナル処理の関係で、必ずメインスレッドに置く必要があります。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: サーバーごとのメンバー数です。デフォルトのインテントでも読み取れます (membersインテントは別途不要)。bot.latency: Discordとの応答の遅延です。秒単位の小数なので、×1000でmsに変換します。ログイン前は値がない(nan)ので、is_ready()で一度ふるいにかけます。
ダッシュボードの画面(HTML)は全体のコードに入れてあります。fetch()で5秒ごとに/api/statsを呼び出して数字を更新する、シンプルな構造です。
📌 全体のコード (コピペOK)
# 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>Ping(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("ここにボットトークンを入力")📌 実行結果
ボットを起動すると、ターミナルにログインメッセージとダッシュボードのアドレスが表示されます。ブラウザでhttp://localhost:5000を開くと...
- ボットの名前と、緑色の「● オンライン」が表示されます
- サーバー数 / ユーザー数 / Ping(ms) がカードで表示され、5秒ごとに自動更新されます
- ボットを止めると(ダッシュボードだけ動いているとき)、赤い「● オフライン」に変わります
- 同じWi-Fiにつないだスマホから
http://내컴퓨터IP:5000(「내컴퓨터IP」の部分は自分のPCの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サーバーを1つのファイルで同時に実行し、ボットの状態・サーバー数・Pingを表示するリアルタイムWebダッシュボードを作ってみました。ここにコマンドの使用統計や最近のログを追加すれば、本格的な管理者ページに育てられますよ!
次回は、コマンドの連打を防ぐクールダウン(Cooldown)と権限管理を扱う予定です。お楽しみに 🙌
📚 Discordボット開発シリーズ
- Discordボット開発 - スラッシュコマンド(Slash Command)完全攻略!
- Discordボット開発 #2 - スラッシュコマンドにオプション(Arguments)を追加しよう!
- Discordボット開発 #3 - スラッシュコマンドにサブコマンド(Subcommand)を追加しよう!
- Discordボット開発 #4 - スラッシュコマンドで埋め込み(Embed)を送信しよう!
- 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でWebダッシュボードを作る ← 今読んでいる記事!
連載 · 全32回中11回目
Discordボット開発
- #1Discordボット開発 - スラッシュコマンド(Slash Command)完全攻略!
- #2スラッシュコマンドにオプション(Arguments)を追加しよう!
- #3スラッシュコマンドにサブコマンド(Subcommand)を追加しよう!
- #4スラッシュコマンドで埋め込み(Embed)を送信しよう!
- #5ボタン(Button)のインタラクションを実装しよう!
- #6ドロップダウン(Select Menu)メニューを実装しよう!
- #7モーダル(Modal)で入力を受け取る方法 (Pythonサンプル付き)
- #8埋め込み(Embed)を画像・フィールドで飾る (Pythonサンプル付き)
- #9AI(Claude・ChatGPT API)と連携する! (Pythonサンプル付き)
- #10多言語(i18n)に対応する! (Pythonサンプル付き)
- #11FlaskでWebダッシュボードを作る! (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임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (韓国語)
