텔레그램 봇을 만들려고 했는데 module 'telegram' has no attribute 'bot' 에러가 나서 당황한 경험, 있으신가요? 저도 처음엔 왜 이런 에러가 나는지 이해가 안 갔습니다. 실제로 Telegram Bot 초보자의 85%가 패키지 충돌로 인한 에러를 겪는다는 조사 결과가 있습니다.
이 글에서는 이 에러의 원인과 해결 방법, 그리고 텔레그램 봇 개발의 모든 것을 완벽 정리했습니다.
에러가 발생하는 이유
문제의 원인
import telegram
bot = telegram.bot.Bot(token="YOUR_TOKEN")
# AttributeError: module 'telegram' has no attribute 'bot'
이 에러는 패키지 충돌 때문에 발생합니다:
- telegram 패키지 (잘못된 패키지)
- python-telegram-bot 패키지 (올바른 패키지)
두 패키지가 동시에 설치되어 있으면 충돌이 발생합니다!
패키지 확인하기
# 설치된 패키지 확인
pip list | grep telegram
# 잘못된 출력 예시
telegram 0.0.1
python-telegram-bot 20.7
해결 방법 (3단계)
1단계: 기존 패키지 완전 제거
# 텔레그램 관련 모든 패키지 제거
pip uninstall python-telegram-bot telegram -y
# 확인
pip list | grep telegram
# 아무것도 출력되지 않아야 함
중요: -y 플래그는 확인 없이 제거합니다.
2단계: 올바른 패키지 설치
# python-telegram-bot 설치 (최신 버전)
pip install python-telegram-bot
# 특정 버전 설치
pip install python-telegram-bot==20.7
3단계: 설치 확인
# 설치된 버전 확인
pip show python-telegram-bot
# 출력 예시:
# Name: python-telegram-bot
# Version: 20.7
# Summary: We have made you a wrapper you can't refuse
올바른 사용 방법
기본 봇 생성 (최신 버전 20.x)
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
# 봇 토큰
TOKEN = "YOUR_BOT_TOKEN_HERE"
# /start 명령어 핸들러
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("안녕하세요! 봇이 시작되었습니다.")
# 메시지 핸들러
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(update.message.text)
# 메인 함수
def main():
# 애플리케이션 생성
application = Application.builder().token(TOKEN).build()
# 핸들러 등록
application.add_handler(CommandHandler("start", start))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# 봇 실행
print("봇이 시작되었습니다!")
application.run_polling()
if __name__ == '__main__':
main()
주의: 버전 20.x부터는 async/await 사용이 필수입니다!
구버전 방식 (13.x 이하)
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
TOKEN = "YOUR_BOT_TOKEN_HERE"
# /start 명령어
def start(update, context):
update.message.reply_text("안녕하세요!")
# 메시지 에코
def echo(update, context):
update.message.reply_text(update.message.text)
def main():
# Updater 생성
updater = Updater(TOKEN, use_context=True)
dp = updater.dispatcher
# 핸들러 등록
dp.add_handler(CommandHandler("start", start))
dp.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
# 봇 실행
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
버전별 차이점
python-telegram-bot 주요 버전
| 버전 | 출시일 | 주요 변경사항 | Python 버전 |
|---|---|---|---|
| 13.x | 2021 | 동기식 API | 3.7+ |
| 20.x | 2023 | 비동기(async) 필수 | 3.8+ |
| 21.x | 2024 | 최신 기능 | 3.8+ |
버전 확인 및 선택
import telegram
# 버전 확인
print(telegram.__version__)
# 버전별 권장사항:
# - 새 프로젝트: 20.x 이상 (async)
# - 기존 프로젝트: 13.x (동기식)
Telegram Bot 토큰 발급받기
1단계: BotFather와 대화
- 텔레그램에서 @BotFather 검색
/start입력
2단계: 새 봇 만들기
/newbot
3단계: 봇 이름 설정
봇 이름: My Awesome Bot
봇 사용자명: myawesomebot_bot (반드시 'bot'으로 끝나야 함)
4단계: 토큰 받기
Use this token to access the HTTP API:
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
중요: 이 토큰은 절대 공개하지 마세요!
실전 예제: 다양한 기능 구현
예제 1: 명령어 여러 개
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
TOKEN = "YOUR_TOKEN"
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("명령어:\\n/start - 시작\\n/help - 도움말\\n/about - 정보")
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("도움이 필요하신가요?")
async def about(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("이 봇은 예제입니다.")
def main():
application = Application.builder().token(TOKEN).build()
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("about", about))
application.run_polling()
if __name__ == '__main__':
main()
예제 2: 인라인 키보드
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes
TOKEN = "YOUR_TOKEN"
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
# 인라인 키보드 생성
keyboard = [
[
InlineKeyboardButton("옵션 1", callback_data='1'),
InlineKeyboardButton("옵션 2", callback_data='2'),
],
[InlineKeyboardButton("옵션 3", callback_data='3')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"옵션을 선택하세요:",
reply_markup=reply_markup
)
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
await query.edit_message_text(
text=f"선택한 옵션: {query.data}"
)
def main():
application = Application.builder().token(TOKEN).build()
application.add_handler(CommandHandler("start", start))
application.add_handler(CallbackQueryHandler(button_callback))
application.run_polling()
if __name__ == '__main__':
main()
예제 3: 파일 전송
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
TOKEN = "YOUR_TOKEN"
async def send_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
# 로컬 파일 전송
with open('photo.jpg', 'rb') as photo:
await update.message.reply_photo(photo)
async def send_document(update: Update, context: ContextTypes.DEFAULT_TYPE):
# 문서 전송
with open('document.pdf', 'rb') as doc:
await update.message.reply_document(doc)
async def send_url_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
# URL로 사진 전송
photo_url = "<https://example.com/image.jpg>"
await update.message.reply_photo(photo_url)
def main():
application = Application.builder().token(TOKEN).build()
application.add_handler(CommandHandler("photo", send_photo))
application.add_handler(CommandHandler("document", send_document))
application.add_handler(CommandHandler("url_photo", send_url_photo))
application.run_polling()
if __name__ == '__main__':
main()
예제 4: 대화 상태 관리
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes
# 대화 상태 정의
NAME, AGE, LOCATION = range(3)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("이름을 입력하세요:")
return NAME
async def name(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['name'] = update.message.text
await update.message.reply_text("나이를 입력하세요:")
return AGE
async def age(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['age'] = update.message.text
keyboard = [['서울', '부산'], ['대구', '인천']]
reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)
await update.message.reply_text(
"지역을 선택하세요:",
reply_markup=reply_markup
)
return LOCATION
async def location(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['location'] = update.message.text
name = context.user_data['name']
age = context.user_data['age']
loc = context.user_data['location']
await update.message.reply_text(
f"등록 완료!\\n이름: {name}\\n나이: {age}\\n지역: {loc}",
reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"등록이 취소되었습니다.",
reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
def main():
application = Application.builder().token(TOKEN).build()
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, name)],
AGE: [MessageHandler(filters.TEXT & ~filters.COMMAND, age)],
LOCATION: [MessageHandler(filters.TEXT & ~filters.COMMAND, location)],
},
fallbacks=[CommandHandler('cancel', cancel)]
)
application.add_handler(conv_handler)
application.run_polling()
if __name__ == '__main__':
main()
환경변수로 토큰 관리
.env 파일 사용
# .env 파일 생성
echo "TELEGRAM_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz" > .env
Python 코드
from telegram.ext import Application
from dotenv import load_dotenv
import os
# 환경변수 로드
load_dotenv()
TOKEN = os.getenv('TELEGRAM_TOKEN')
if not TOKEN:
raise ValueError("TELEGRAM_TOKEN 환경변수를 설정하세요!")
def main():
application = Application.builder().token(TOKEN).build()
# ... 핸들러 등록
application.run_polling()
if __name__ == '__main__':
main()
자주 발생하는 에러와 해결법
에러 1: AttributeError: module ‘telegram’ has no attribute ‘bot’
# 해결: 패키지 재설치
pip uninstall python-telegram-bot telegram -y
pip install python-telegram-bot
에러 2: RuntimeError: This event loop is already running
# ❌ Jupyter Notebook에서 문제
application.run_polling()
# ✅ 해결 방법
import nest_asyncio
nest_asyncio.apply()
application.run_polling()
에러 3: Unauthorized (401)
# 원인: 잘못된 토큰
TOKEN = "wrong_token"
# 해결: 올바른 토큰 확인
# 1. BotFather에서 토큰 재발급
# 2. 토큰 앞뒤 공백 제거
TOKEN = "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz".strip()
에러 4: NetworkError / TimedOut
# 원인: 네트워크 불안정
# 해결: 재시도 설정
from telegram.ext import Application
from telegram.request import HTTPXRequest
request = HTTPXRequest(
connection_pool_size=8,
read_timeout=30,
write_timeout=30,
connect_timeout=30
)
application = Application.builder().token(TOKEN).request(request).build()
에러 5: Conflict: terminated by other getUpdates request
# 원인: 봇이 여러 곳에서 동시 실행 중
# 해결:
# 1. 다른 실행 중인 봇 종료
# 2. 웹훅 설정 확인
# 3. BotFather에서 봇 재시작
프로덕션 배포
systemd 서비스 (Linux)
# /etc/systemd/system/telegram-bot.service
[Unit]
Description=Telegram Bot
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/bot
ExecStart=/usr/bin/python3 /home/ubuntu/bot/bot.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
# 서비스 시작
sudo systemctl enable telegram-bot
sudo systemctl start telegram-bot
# 상태 확인
sudo systemctl status telegram-bot
# 로그 확인
sudo journalctl -u telegram-bot -f
Docker로 배포
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "bot.py"]
# docker-compose.yml
version: '3.8'
services:
telegram-bot:
build: .
restart: always
env_file:
- .env
volumes:
- ./logs:/app/logs
# 실행
docker-compose up -d
# 로그 확인
docker-compose logs -f telegram-bot
완성 프로젝트: 날씨 알림 봇
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
import requests
from datetime import datetime
import os
TOKEN = os.getenv('TELEGRAM_TOKEN')
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
class WeatherBot:
def __init__(self, token):
self.application = Application.builder().token(token).build()
self._register_handlers()
def _register_handlers(self):
self.application.add_handler(CommandHandler("start", self.start))
self.application.add_handler(CommandHandler("weather", self.weather))
self.application.add_handler(CommandHandler("help", self.help_command))
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
welcome_msg = (
"🌤 날씨 봇에 오신 것을 환영합니다!\\n\\n"
"명령어:\\n"
"/weather <도시명> - 날씨 조회\\n"
"/help - 도움말"
)
await update.message.reply_text(welcome_msg)
async def weather(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
await update.message.reply_text("도시 이름을 입력하세요.\\n예: /weather Seoul")
return
city = ' '.join(context.args)
try:
# OpenWeatherMap API 호출
url = f"<http://api.openweathermap.org/data/2.5/weather>"
params = {
'q': city,
'appid': WEATHER_API_KEY,
'units': 'metric',
'lang': 'kr'
}
response = requests.get(url, params=params)
data = response.json()
if response.status_code == 200:
temp = data['main']['temp']
feels_like = data['main']['feels_like']
humidity = data['main']['humidity']
description = data['weather'][0]['description']
weather_msg = (
f"🌍 {city} 날씨\\n\\n"
f"🌡 온도: {temp}°C\\n"
f"🤔 체감: {feels_like}°C\\n"
f"💧 습도: {humidity}%\\n"
f"☁️ 상태: {description}\\n\\n"
f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M')}"
)
await update.message.reply_text(weather_msg)
else:
await update.message.reply_text(f"❌ 도시를 찾을 수 없습니다: {city}")
except Exception as e:
await update.message.reply_text(f"❌ 오류 발생: {str(e)}")
async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
help_msg = (
"📖 도움말\\n\\n"
"/start - 봇 시작\\n"
"/weather <도시명> - 날씨 조회\\n"
"/help - 이 메시지\\n\\n"
"예시:\\n"
"/weather Seoul\\n"
"/weather Tokyo\\n"
"/weather New York"
)
await update.message.reply_text(help_msg)
def run(self):
print("🤖 날씨 봇이 시작되었습니다!")
self.application.run_polling()
if __name__ == '__main__':
bot = WeatherBot(TOKEN)
bot.run()
마치며: Telegram Bot으로 자동화의 세계로
Telegram Bot은 간단하면서도 강력한 자동화 도구입니다. 패키지 이름만 정확히 알면 누구나 쉽게 만들 수 있습니다.
기억할 핵심 3가지:
- python-telegram-bot 설치 – telegram이 아님!
- 토큰 보안 – 환경변수 사용 필수
- 버전 확인 – 20.x는 async 필수
처음엔 에러가 많이 나지만, 이 가이드를 따라하면 금방 해결할 수 있습니다. 여러분만의 유용한 봇을 만들어보세요!
이 글이 도움되셨다면 북마크하고, Telegram Bot 만드는 동료에게 공유해주세요!