디스코드 봇 만들기#11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
목차10개 항목
안녕하세요! Mojangcam입니다 😎
지난 #10편에서는 봇을 글로벌 봇으로 만드는 다국어(i18n) 지원을 다뤘죠? 원래 쿨다운·권한 관리를 예고했는데, 그 전에 많은 분들이 궁금해하시는 주제를 먼저 가져왔어요. 바로 웹 대시보드(Web Dashboard) 만들기입니다! (쿨다운 편도 곧 올라오니 기대해주세요)
봇을 서버에 올려두면 이런 생각이 들어요. "지금 봇이 살아있나...?" 매번 디스코드를 열어서 확인하는 대신, 브라우저 주소창에 주소만 치면 봇 상태·서버 수·핑(Ping)이 한눈에 보이는 페이지가 있다면 훨씬 편하겠죠? 오늘은 파이썬 웹 프레임워크 플라스크(Flask) 로 그런 미니 관제탑을 만들어볼 거예요.

🧠 왜 Flask예요?
플라스크(Flask)는 파이썬에서 가장 유명한 마이크로 웹 프레임워크예요. "마이크로"라는 이름답게 코드 몇 줄이면 웹 서버가 뚝딱 만들어져서, 봇에 곁들이는 대시보드용으로 딱이에요.
- 봇이 켜져 있는지 브라우저(폰에서도!)로 바로 확인
- 서버 수·유저 수·응답 속도를 실시간 모니터링
- 나중에 명령어 통계, 로그 뷰어 등으로 무한 확장 가능
딱 하나 알아둘 개념이 있어요. discord.py는 비동기(asyncio) 로 도는 프로그램이고, Flask는 동기 방식 웹 서버예요. 둘을 한 파일에서 같이 돌리려면 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__): 웹 앱 객체를 만들어요.__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를 데몬 스레드로 분리하는 거예요.
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(...): 파이썬 딕셔너리를 브라우저가 읽는 JSON 응답으로 바꿔줘요.bot.is_ready(): 봇이 디스코드에 완전히 로그인했는지 여부예요. 대시보드의 온라인/오프라인 표시에 씁니다.bot.guilds: 봇이 들어가 있는 서버 목록이에요.len()으로 서버 수를 세요.g.member_count: 서버별 멤버 수예요. 기본 인텐트로도 읽을 수 있어요 (별도 members 인텐트 불필요).bot.latency: 디스코드와의 응답 지연이에요. 초 단위 실수라 ×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>핑(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을 열면...
- 봇 이름과 ● 온라인 초록불이 표시돼요
- 서버 수 / 유저 수 / 핑(ms) 이 카드로 보이고, 5초마다 자동 갱신돼요
- 봇을 끄면(대시보드만 켜져 있을 때) 빨간 ● 오프라인으로 바뀝니다
- 같은 와이파이의 폰에서
http://내컴퓨터IP:5000으로 접속하면 폰에서도 확인 가능!


📌 자주 나는 에러
- ValueError: signal only works in main thread
원인: Flask 디버그 모드(자동 재시작)를 켠 채 스레드에서 실행했거나, 반대로
bot.run()을 스레드로 보낸 경우예요. 둘 다 메인 스레드가 아니면 시그널 처리에서 터져요. 해결:app.run(..., use_reloader=False)를 꼭 넣고, 봇은 메인 스레드 / Flask는 스레드 구조를 유지하세요. - OSError: [Errno 98] Address already in use (윈도우: WinError 10048)
원인: 5000번 포트를 이미 다른 프로그램(또는 안 죽은 이전 실행)이 쓰고 있어요.
해결:
port=5001처럼 다른 포트로 바꾸거나, 터미널을 완전히 껐다 다시 실행하세요. - 대시보드에 서버 0개·유저 0명으로 나와요 원인: 봇이 아직 로그인 중이거나, 봇이 초대된 서버가 없는 경우예요. 해결: 잠시 뒤 새로고침해보세요(5초마다 자동 갱신). 계속 0이면 봇 초대 링크로 테스트 서버에 초대됐는지 확인하세요.
📌 마무리
오늘은 Flask를 별도 스레드로 돌려서 봇과 웹 서버를 한 파일에서 동시에 실행하고, 봇 상태·서버 수·핑을 보여주는 실시간 웹 대시보드를 만들어봤어요. 여기에 명령어 사용 통계나 최근 로그를 추가하면 진짜 관리자 페이지로 키울 수 있답니다!
다음 편에서는 명령어 도배를 막는 쿨다운(Cooldown)과 권한 관리를 다뤄볼 예정이에요. 기대해주세요 🙌
📚 디스코드 봇 만들기 시리즈
- 디스코드 봇 만들기 - Slash Command 완전 정복!
- 디스코드 봇 만들기 #2 - Slash Command에 옵션(Arguments) 추가하기!
- 디스코드 봇 만들기 #3 - Slash Command에 서브 커맨드(Subcommand) 넣기!
- 디스코드 봇 만들기 #4 - Slash Command로 임베드 전송하기!
- 디스코드 봇 만들기 #5 - 버튼(Button) 상호작용 구현하기
- 디스코드 봇 만들기 #6 - 드롭다운(Select Menu) 메뉴 구현하기
- 디스코드 봇 만들기 #7 - 모달(Modal)로 입력 받기
- 디스코드 봇 만들기 #8 - 임베드(Embed) 이미지·필드로 꾸미기
- 디스코드 봇 만들기 #9 - AI(Claude·ChatGPT API) 연동하기
- 디스코드 봇 만들기 #10 - 다국어(i18n) 지원하기
- 디스코드 봇 만들기 #11 - 플라스크(Flask)로 웹 대시보드 만들기 ← 지금 이 글!
연재 · 32편 중 11번째
디스코드 봇 만들기
- #1디스코드 봇 만들기 - Slash Command 완전 정복!
- #2Slash Command에 옵션(Arguments) 추가하기!
- #3Slash Command에 서브 커맨드(Subcommand) 넣기!
- #4Slash Command로 임베드 전송하기!
- #5버튼(Button) 상호작용 구현하기!
- #6드롭다운(Select Menu) 메뉴 구현하기!
- #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)
- #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
- #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
- #10다국어(i18n) 지원하기! (파이썬 예제)
- #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)지금 읽는 글
- #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
- #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
- #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
- #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) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
- #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
