파이썬 Telegram Bot 에러 완벽 해결 가이드

텔레그램 봇을 만들려고 했는데 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'

이 에러는 패키지 충돌 때문에 발생합니다:

  1. telegram 패키지 (잘못된 패키지)
  2. 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.x2021동기식 API3.7+
20.x2023비동기(async) 필수3.8+
21.x2024최신 기능3.8+

버전 확인 및 선택

import telegram

# 버전 확인
print(telegram.__version__)

# 버전별 권장사항:
# - 새 프로젝트: 20.x 이상 (async)
# - 기존 프로젝트: 13.x (동기식)

Telegram Bot 토큰 발급받기

1단계: BotFather와 대화

  1. 텔레그램에서 @BotFather 검색
  2. /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가지:

  1. python-telegram-bot 설치 – telegram이 아님!
  2. 토큰 보안 – 환경변수 사용 필수
  3. 버전 확인 – 20.x는 async 필수

처음엔 에러가 많이 나지만, 이 가이드를 따라하면 금방 해결할 수 있습니다. 여러분만의 유용한 봇을 만들어보세요!

이 글이 도움되셨다면 북마크하고, Telegram Bot 만드는 동료에게 공유해주세요!


참고 자료

댓글 남기기