Study/AI 비서

AI 비서 만들기 프로젝트 ③|수영장 빈자리 알림 봇 직접 만들어보기

by somida 2026. 8. 10.
반응형

지난 글에서는 텔레그램 봇을 만들고 Python 개발 환경을 준비하면서,

나만의 AI 비서를 만들기 위한 기본적인 준비 과정을 정리했다.

 

이번에는 조금 더 현실적인 기능을 하나 붙여보기로 했다.

 

바로 구립 수영장 빈자리 알림 봇이다.

 

사실 시작한 계기는 굉장히 단순했다.

수영장 자리가 너무 안 나온다.

 

새벽반이든 저녁반이든 원하는 강좌를 신청하려고 예약 페이지에 들어가 보면 이미 만석인 경우가 많았다.

그렇다고 자리가 생길 때까지 계속 예약 페이지를 새로고침하고 있을 수도 없는 노릇.

 

그러다 문득 이런 생각이 들었다.

"이걸 내가 계속 확인할 필요가 있나? 봇한테 시키면 되잖아."

 

그렇게 오늘 저녁, ai-secretary라는 이름으로 프로젝트를 하나 만들었다.

 

목표도 간단했다.

 

수영장 예약 페이지를 주기적으로 확인하고, 빈자리가 생기면 텔레그램으로 알려주는 것.

 

그리고 실제로 첫 커밋부터 리팩토링까지 걸린 시간은 약 36분이었다.

 


 

1. 일단 작동하는 것부터 만들어보기

 

처음부터 멋진 아키텍처를 설계할 생각은 없었다.

 

이번에는 철저하게 MVP(Minimum Viable Product) 방식으로 접근했다.

일단 수영장 예약 페이지에서 내가 원하는 강좌의 잔여 좌석을 가져오고,

자리가 생기면 텔레그램으로 알려주는 것.

 

이 두 가지가 되면 일단 성공이라고 생각했다.

 

가장 먼저 해야 했던 일은 예약 페이지의 HTML 구조를 확인하는 것이었다.

 

실제 웹페이지에서 어떤 태그에 강좌 정보가 들어 있고, 잔여 좌석은 어떤 형태로 표시되는지 확인했다.

 

그런데 개발할 때마다 실제 사이트에 계속 요청을 보내는 방식은 조금 부담스러웠다.

짧은 시간 동안 너무 많은 요청을 보내면 IP가 차단되거나 사이트에서 요청을 제한할 가능성도 있기 때문이다.

 

그래서 처음에는 예약 페이지를 HTML 파일로 저장해서 로컬에서 테스트하는 방식을 선택했다.

 

실제 사이트에 계속 접속하지 않고 저장해둔 페이지를 가지고 파싱 로직을 먼저 검증하는 방법이다.

개발하면서 이런 식으로 테스트하니 훨씬 마음 편하게 구조를 수정할 수 있었다.

 

# services/swim_monitor.py (MVP 버전)

import re

import requests
from bs4 import BeautifulSoup


class SwimMonitor:

    URL = "https://xxx"

    TARGET = "[2026추첨]저녁수영20B(여성)(평영 이상 등록가능)"

    def __init__(self):

        self.session = requests.Session()

        self.session.headers.update({
            "User-Agent": "Mozilla/5.0",
            "Cache-Control": "no-cache"
        })

    def check(self):

        response = self.session.get(
            self.URL,
            timeout=10
        )

        response.raise_for_status()

        soup = BeautifulSoup(
            response.text,
            "html.parser"
        )

        rows = soup.select("table.tbl_scm1 tbody tr")

        for row in rows:

            text = row.get_text(" ", strip=True)

            if self.TARGET not in text:
                continue

            match = re.search(r"(\d+)\s*/\s*(\d+)", text)

            if not match:
                raise Exception("좌석 정보를 찾을 수 없습니다.")

            remain = int(match.group(1))
            total = int(match.group(2))

            return remain, total

        raise Exception("강좌를 찾을 수 없습니다.")

 

 


 

2. 수영장 빈자리 확인 로직 만들기

 

수영장 페이지에서 특정 강좌를 찾고 잔여 좌석을 확인하는 역할은 SwimMonitor라는 클래스로 분리했다.

핵심적으로 하는 일은 다음과 같다.

  1. 수영장 예약 페이지에 요청을 보낸다.
  2. HTML을 가져온다.
  3. BeautifulSoup으로 페이지를 파싱한다.
  4. 내가 원하는 강좌를 찾는다.
  5. 현재 좌석 / 전체 정원 형태의 정보를 추출한다.
  6. 잔여 좌석 수를 반환한다.

예를 들어 내가 찾고 있던 강좌는 다음과 같은 형태였다.

 

[2026추첨]저녁수영20B(여성)(평영 이상 등록가능)

 

해당 강좌가 있는 행을 찾은 다음 정규식을 이용해 좌석 정보를 가져왔다.

 

결과적으로

현재 좌석 2 / 전체 정원 20

같은 형태로 데이터를 얻을 수 있도록 만들었다.

 

처음에는 정말 단순한 코드였지만, 일단 원하는 데이터가 제대로 나오는 것을 확인하니 다음 단계로 넘어갈 수 있었다.

 

bot/sender.py.

import requests

from config import TELEGRAM_CHAT_ID, TELEGRAM_TOKEN


def send_message(message: str):

    url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"

    response = requests.post(
        url,
        json={
            "chat_id": TELEGRAM_CHAT_ID,
            "text": message
        },
        timeout=10
    )

    if not response.ok:
        print(response.text)

 

3. 자리가 생기면 텔레그램으로 알려주기

좌석 정보를 가져왔다면 이제 알림만 보내면 된다.

텔레그램 봇을 이미 만들어두었기 때문에 이번에는 Telegram Bot API를 이용해서 메시지를 보내는 기능을 붙였다.

 

별도의 복잡한 구조 없이 sender.py라는 파일을 만들어 메시지 전송 기능을 분리했다.

 

예를 들어 빈자리가 발생하면 다음과 같은 내용의 메시지를 보내도록 했다.

🏊 자리 발생!!

 

강좌명

현재 좌석

예약 페이지 링크

 

이렇게 만들어놓으니 드디어 처음 목표했던 기능이 완성됐다.

 

수영장 페이지를 계속 보고 있을 필요 없이,

자리가 생기면 텔레그램이 먼저 알려주는 구조가 만들어진 것이다.

 


 

4. app.py에서 주기적으로 확인하기

 

이제 만들어놓은 기능들을 실제로 계속 실행시킬 차례였다.

처음에는 app.py에서 아주 단순한 반복문으로 구현했다.

 

흐름은 다음과 같다.

수영장 좌석 확인 → 현재 좌석 기록 → 빈자리 발생 여부 확인 → 텔레그램 알림 → 잠시 대기 → 다시 확인

 

이 과정을 반복한다.

 

여기서 중요한 것은 단순히 잔여 좌석이 1개 이상이라고 매번 알림을 보내면 안 된다는 것이다.

예를 들어 현재 자리가 2개인데 10초마다 확인한다면 같은 알림이 계속 날아오게 된다.

 

그래서 이전 좌석 수와 현재 좌석 수를 비교하도록 했다.

처음에는 자리가 없다가,

0 → 1

 

처럼 좌석이 생겼을 때 알림을 보내는 방식이다.

 

이렇게 하니 불필요하게 같은 메시지를 반복해서 보내는 문제도 해결할 수 있었다.

 


 

5. 첫 번째 목표 달성

 

여기까지 만들고 나니 처음 생각했던 기능은 전부 동작했다.

수영장 예약 페이지를 확인하고,

원하는 강좌를 찾고,

잔여 좌석을 확인하고,

자리가 생기면 텔레그램으로 알려준다.

 

화려한 기능은 아니지만 내가 실제로 필요했던 문제를 해결했다는 점에서 꽤 만족스러웠다.

 

그런데 코드를 다시 살펴보니 바로 다른 문제가 보였다.

"이걸 수영장 하나만을 위한 프로그램으로 계속 가져가도 될까?"

 


 

6. MVP를 만들고 나니 구조가 보이기 시작했다

 

현재 구조는 수영장 빈자리를 확인하는 데는 문제가 없었다.

 

하지만 이 프로젝트의 이름은 ai-secretary.

 

즉, 앞으로 여러 가지 기능을 추가할 개인 비서 프로젝트였다.

나중에

  • 택배 도착 알림
  • 환율 알림
  • 주식 가격 알림
  • 날씨 알림
  • 기타 반복 작업

같은 기능을 추가한다고 생각해보자.

 

그때마다 모든 코드를 app.py에 계속 추가한다면 금방 관리하기 어려워질 것이다.

그래서 첫 번째 MVP가 제대로 동작하는 것을 확인한 뒤 구조를 다시 정리했다.


7. 8분 만에 프로젝트 구조 리팩토링

이번에는 각 기능의 역할을 분리했다.

core/base_service.py

각 알림 서비스가 공통적으로 사용할 인터페이스를 정의했다.

core/scheduler.py

여러 서비스를 등록하고 일정한 주기로 실행하는 역할을 담당한다.

models/course.py

수영 강습이라는 데이터를 명확하게 표현하기 위한 모델을 만들었다.

services/swim_service.py

기존에 하나의 파일에 몰려 있던 수영장 관련 로직을 서비스 구조에 맞게 다시 정리했다.

이렇게 분리하고 나니 프로젝트의 모습이 꽤 달라졌다.

처음에는 수영장 빈자리를 알려주는 스크립트였다면,

이제는 여러 가지 알림 서비스를 추가할 수 있는 구조가 됐다.

특히 새로운 기능을 추가할 때 기존 코드를 크게 건드리지 않아도 된다는 점이 마음에 들었다.

 

 

8. 단순 알림봇에서 진짜 '비서'로

여기서 한 단계 더 욕심을 냈다.

지금까지의 봇은 내가 아무것도 하지 않아도 자리가 생기면 먼저 메시지를 보내준다.

하지만 반대로 생각하면 내가 봇에게 말을 걸 수는 없었다.

 

즉,

봇 → 나

라는 일방적인 구조였다.

 

그래서 이번에는 반대로

나 → 봇

방향의 기능도 추가했다.

 


 

9. Telegram 메시지를 직접 받도록 만들기

 

새롭게 receiver.py와 handlers.py를 추가했다.

 

이제 텔레그램에서 내가 보내는 메시지를 Python 프로그램이 받아서 처리할 수 있게 됐다.

예를 들어 /status 같은 명령어를 보내면 현재 수영장 상태를 바로 확인해서 답변하도록 만들었다.

이 기능이 추가되면서 느낌이 조금 달라졌다.

 

이전에는 그냥 "자리가 생기면 알려주는 알림봇"이었다.

이제는 내가 필요할 때 직접 물어보고 답변을 받을 수 있게 됐다.

 

아주 작은 차이지만 개인적으로는 이 부분이 이번 프로젝트에서 가장 큰 변화였다.

일방적으로 알림을 보내는 프로그램에서, 사용자의 명령을 이해하고 응답하는 프로그램으로 한 단계 발전한 것이다.

 


 

10. 36분 동안 만든 결과

 

처음 커밋부터 구조를 다시 정리하고 양방향 메시지까지 구현하는 데 걸린 시간은 총 36분 정도였다.

 

짧은 시간 안에 만들다 보니 당연히 아직 부족한 부분도 많다.

하지만 이번 프로젝트에서 중요한 건 완벽한 코드를 만드는 것이 아니었다.

 

실제로 필요한 문제를 하나 정하고,

작동하는 코드 → 문제 발견 → 리팩토링 → 기능 확장

 

순서로 발전시켜보는 것이었다.

 


 

💡 이번 개발에서 가장 만족스러웠던 것

 

1. 먼저 동작하게 만들었다

 

처음부터 완벽한 아키텍처를 설계하지 않았다.

HTML 구조를 확인하고 실제로 좌석을 가져오는 것부터 시작했다.

특히 실제 사이트를 계속 호출하지 않고 HTML 스냅샷을 이용해 파싱 로직을 먼저 테스트한 것은 좋은 선택이었다.

개발 단계에서 불필요한 요청을 줄이면서 로직 자체에 집중할 수 있었기 때문이다.

 

2. 동작하는 코드를 보고 구조를 결정했다

 

처음부터 BaseService니 Course니 하는 구조를 고민했다면 오히려 시작하기 어려웠을 것 같다.

일단 만들어보니 어떤 부분을 분리해야 할지 자연스럽게 보였다.

그래서 그다음에 리팩토링했다.

완벽하게 설계하고 시작하는 것보다 일단 작게 만들어보는 것의 장점을 직접 경험한 셈이다.

 

3. 일방향 알림에서 양방향으로 확장했다

 

이번 프로젝트에서 가장 큰 전환점이었다.

빈자리가 생기면 알려주는 것에서 끝나는 것이 아니라,

내가 /status 같은 명령을 보내면 현재 상태를 확인할 수 있도록 만들었다.

이제 조금씩 '비서'라는 이름에 가까워지고 있는 것 같다.

 


 

마치며|왓슨쭈니어의 첫 번째 업무 완료

이렇게 해서 나만의 AI 비서 '왓슨쭈니어'의 첫 번째 업무가 완성됐다.

 

첫 번째 업무는 거창하지 않았다.

구립 수영장 빈자리 알림.

 

하지만 개인적으로는 이런 작은 기능부터 시작하는 게 오히려 좋다고 생각한다.

내가 실제로 불편했던 일을 하나 해결했고,

그 과정에서 Telegram Bot과 Python을 연결하는 방법도 익혔다.

 

무엇보다 이제부터 새로운 기능을 추가할 수 있는 기본적인 구조도 만들어졌다.

 

물론 아직 갈 길은 멀다.

택배 알림도 만들 수 있고,

환율을 알려주게 할 수도 있고,

반복적인 업무를 자동화할 수도 있다.

 

그리고 무엇보다 아직 중요한 게 하나 빠져 있다.

 

AI가 없다.

 

현재 왓슨쭈니어는 내가 시킨 명령을 처리하고 정해진 조건에 따라 알림을 보내는 봇에 가깝다.

 

아직 내 말을 자연스럽게 이해하고 상황에 맞는 행동을 판단하는 진짜 AI 비서라고 부르기에는 부족하다.

 

 

그래서 다음 단계부터가 본격적인 시작이다.

 

이제 Telegram Bot을 어떻게 사용하는지는 어느 정도 익숙해졌으니,

다음 글에서는 AI를 연결해서 왓슨쭈니어가 단순한 알림봇을 넘어 실제로 나와 대화하고 일을 처리할 수 있도록 만드는 방법을 하나씩 추가해볼 예정이다.

 

일단 오늘은 여기까지.

 

왓슨쭈니어, 첫 번째 업무 완료. 🏊

반응형

댓글