프로젝트를 시작한 이유

본 프로젝트는 사범대의 졸업 요구 조건인 '인적성 검사', '응급처치 교육'을 신청하기 위해 3년 동안 공지사항 페이지를 반복적으로 들락거리며 '공지사항' 페이지는 꼴도 보기 싫은 한 사범대생에 의해 시작되었다..

또한 이 글은 한 프로젝트를 1차적으로 마무리하며 배웠던 점을 돌아보기 위함과 동시에, 혹시 나와 같은 고충과 불만을 겪고 있는 사람이 해결책을 찾는 데 조금이나마 도움이 되었으면 하는 바람으로 작성하게 되었다.
전술했듯, 필자는 사범대학에 소속되어 있다. 사범대생은 교원자격증을 취득하기 위해, 졸업 전 2회의 응급처치 교육과 2회의 인적성 검사를 수행해야 하는데, 이를 신청하는 과정은 보통 불편한 일이 아니다. 1년 약 8회의 교육이 있고, 교육 전 교직팀 웹사이트 '공지사항'에 수시로 들어가 확인해야만 교육 신청 기간을 알 수 있다. 이 말은 즉, 교육일정을 확인하기 위해 수시로 공지사항을 확인해야 한다는 뜻이며, 4년 동안 이러한 작업을 반복해야 한다는 뜻이다...

필자는 이런 반복적인 작업을 더 이상 하고 싶지 않았다. 신청한다고 해도 우선순위에 밀려 선발되지 않을 수 있는 교육을 신청하기 위해 마음 한 켠에서 계속 신경 쓰고 있어야 한다는 자체가 싫었다.
언젠간 내 손으로 이 문제를 해결해보리라 마음 먹었지만 바쁘다는 핑계로 미루고 있던 때, 마침 한 대외활동의 코딩 테스트 준비를 위해 파이썬을 학습할 기회가 생겼고, '나도 코딩'님의 파이썬 기초 강좌를 통해 파이썬을 공부하던 중, 파이썬 웹 스크래핑 강의를 접하게 됐다. 강의를 보자마자, '이거 내가 찾던 해결책 아니야??' 라는 생각이 머리를 스침과 동시에 내 손으로 웹페이지의 정보를 긁어올 수 있다는 것이 너무나도 재밌어 보여 빠르게 강의를 수강한 뒤, 나의 프로젝트를 시작했다.
프로젝트 소개
필자가 구현하고자 한 기능은, 교직팀 웹사이트에 공지사항이 등록됐을 때 그 정보를 카톡으로 보내주는 알림봇이었고, 이를 위해 우선 웹 스크래핑으로 원하는 정보를 가져와야 했다. 다행히 나도 코딩님의 강의를 들으며 웹 스크래핑을 익혔고, 아래와 같이 크롬 개발자 도구에서 내가 찾는 정보가 어떤 위치, 태그에 속해있으며, 클래스는 무엇인지 탐색이 가능했다.

하지만 강의에서의 실습과 달랐던 점(강의에서는 주로 네이버, 구글 등 규모가 큰 웹페이지를 대상으로 실습) 은 학교 웹사이트는 개발 당시 각 태그별로 클래스를 많이 활용하지 않아서, 내가 원하는 특정 태그에 접근하려면 클래스 이름으로 접근하는 것이 아니라 Xpath 혹은 특정 태그의 [i] 번째 자식, 부모에 접근해야 했다.
버튼 하나하나에도 클래스와 아이디가 있는 대형 웹사이트의 코드와 비교했을 때 매우 단조로운 코드를 보며 '학교 공지사항과 같은 홈페이지는 한 번 개발하면 유지보수 할 인력이 많이 없기도 하고, 외주를 맡기기 때문에 이렇게 단조로운 구조를 갖고 있나? 혹은 불필요한 기능은 다 덜어내고 웹 페이지의 본 기능에 충실하는 것인가?'라는 의문이 들기도 했다. 그렇게 html태그를 직접 들여다보며, 다른 개발자가 설계한 html 구조에 대해 '왜 이렇게 했을까?'라는 고민해볼 수 있었다.
웹 스크래핑을 위한 html 구조 파악이 어느정도 된 후에는 곧바로 Python을 활용한 알림봇 프로그램을 작성했다. Python의 requests라는 http 통신 관련 모듈을 활용해 공지사항 페이지의 html 문서를 get, 이를 Beautifulsoup으로 parsing하여 내 입맛에 맞게 원하는 정보를 추출, 가공했다. 그리고 텔레그램 api를 활용하여, 가공한 정보를 나와 봇과의 채팅방에 전송하는 기능까지 구현했다.
from bs4 import BeautifulSoup
from datetime import date
import requests
import time
import telegram
# Fixed values
TOKEN = '텔레그램에서 발급받은 토큰'
CHAT_ID = '메세지를 전송할 채팅방 ID'
url = "https://teaching.korea.ac.kr/teaching/community/notice1.do"
# Telegram bot
bot = telegram.Bot(token=TOKEN)
# requests, bs4
res = requests.get(url)
res.raise_for_status()
soup = BeautifulSoup(res.text, "html.parser")
# 클래스 구분이 명확하지 않은 페이지에서 공지사항 리스트(ul) 추출(사이트 구조 변경 시 수정 필요)
to_find_notice = soup.findAll("ul", attrs={"class": "m"})
grabed_notice = to_find_notice[1]
# 각 공지사항의 element에 접근하기 위해 공지사항 li elements를 리스트 자료구조로 변환
notice_list = grabed_notice.findAll('li')
#오늘 날짜를 문자열로
today = date.today().isoformat().replace('-', '.')
# 공지사항 전송
for notice in notice_list:
post_date = notice.span.get_text().replace('교직팀', '')
if(today == post_date): #오늘 게시글이 있으면
title = notice.a.get_text()
link = 'https://teaching.korea.ac.kr/teaching/community/notice1.do' + \
notice.a['href']
feed = '{0}\n\n게시일: {1}\n\n링크: {2}'.format(title, post_date, link)
bot.send_message(text=feed, chat_id=CHAT_ID)
time.sleep(2)
else:
continue


최종적으로는 텔레그램을 선택했지만, 처음에는 카카오 메시지 api를 활용해 구현했다. 이 과정에서 json으로 데이터를 주고받는 경험을 해봤고, api는 '키보드'라는 니코쌤의 비유가 더 이해가 잘 되었다. 마치 제품 설명서와 같이 작성된 카카오의 api 문서를 읽으며 "당신이 이 기능을 구현하고 싶으면 이 버튼을 누르세요"가 적절한 비유라며 공감했다.
다만, 카카오의 경우 액세스 토큰의 유효기간이 짧아 지속적으로 갱신해줘야 하는 수고스러움이 있었고, 공지사항 페이지도 자주 들어가기 싫어서 하는 프로젝트인데 최대한 수고를 줄이자는 생각에, 토큰 발급 과정이 간소한 텔레그램을 활용했다.
https://python-telegram-bot.org/ 에서 제작한 Python module을 설치함으로써 Python에서도 텔레그램 봇을 손쉽게 활용할 수 있으며, 카카오 api 적용을 경험해서인지, 그 과정이 더욱 수월했다.
함수를 정의하고, 사용자의 채팅 메시지를 계속해서 업데이트하는 방식으로 실제 봇과 대화하는 것처럼 구현할 수도 있지만, 필자는 일방적인 알림 전송만 필요했기에 기본 메시지 전송 기능만을 활용하여 간편하게 알림봇 기능을 구현했다.
터미널에서 코드를 실행해보니 매우 잘 동작했는데... 곰곰히 생각해보니, 공지사항을 보기 위해 매번 파이썬 코드를 실행할 수 없지 않은가? 나의 애플리케이션이 스스로 동작하게 하려면 어떻게 해야 하나?라는 고민은 자연스럽게 서버, 데이터베이스 관련 학습으로 이어졌다.
실행과 동시에 웹페이지의 데이터를 가져오며, 스크랩 대상 웹페이지에 당일 등록 게시물이 있는지만 확인하면 되기에, 데이터베이스에 데이터를 저장할 필요도 없었다. 단지 누군가 정해진 시간에 필자가 작성한 파이썬 코드의 실행 버튼만 눌러주면 되는 것이다. 따라서 '파이썬 자동 코드 실행', '예약 실행'과 같은 맥락으로 검색했고,
1. 하나의 응용프로그램(.exe)을 만들어 로컬에서 예약 실행하는 법 (cronjobs 등)
2. 정해진 Trigger에 따라 함수를 실행해주는 Serverless architecture(AWS Lambda, Google Cloud Function 등)
3. Firebase와 같은 BaaS
등의 결과를 얻었다.
결론적으로, 예약시간에 컴퓨터가 켜져 있어야 하는 로컬과, 백엔드 구축에 가까운 Firebase는 적절하지 않다고 판단했고, 정기적으로 정해진 시간에 함수를 호출해주는 서버리스 아키텍처인 'AWS Lambda'를 선정했다.

Lambda는 내가 작성한 코드를 함수에 담아, 사전에 설정한 Trigger가 발생하면 함수를 대신 호출해주는 서비스이다. 필자처럼 마이크로 한 프로젝트에도 적용할 수 있고, 특정 조건에서만 실행할 수 있다는 이점 때문에 리소스 절약이 필요한 큰 프로젝트의 일부분에 적용되는 경우도 있다고 한다. 다만 Python 표준 모듈 외에는 사용자가 사용하고자 하는 모듈을 직접 패키징 해서 등록해야 해서, 필자도 모듈 import 하는 과정에서 한참을 헤매다 구글링을 통해 겨우 해결했다.. 모듈 설치와 전반적인 Lambda 활용 강의는 동빈나 님의 'AWS Lambda로 크롤링 봇 만들기'강좌를 추천한다.

시간 예약은 Lambda -> Functions -> Add Trigger -> EventBridge (CloudWatch Events)를 통해 설정할 수 있으며, 특정 표현식을 활용한다.

갖가지 문제에 봉착했지만, 구글링을 통해 하나하나 한 결과 내가 필요해서 만든 나를 위한 프로그램이 드디어 탄생했다! 매일 저녁 8시에 공지사항을 확인하고, 새로운 공지사항이 있으면 텔레그램으로 알림을 보내주는, 50줄도 안 되는 코드를 작성하기 위해 몇시간을 붙잡고 있었지만, 하나하나 해결해가는 과정이 정말 재미있었다.
이번 프로젝트를 통해 Python이라는 언어와 아주 조금은 친해졌고, 처음으로 내가 작성한 프로그램을 내 컴퓨터가 아닌 다른 컴퓨터에서 실행하는 진귀한 경험을 했다. 그리고 웹페이지에 표시된 정보를 스크래핑을 통해 내 입맛에 맞게 가공하는 경험, api 활용 경험, html구조 분석 경험은 필자에게 피와 살이 되는 경험일 것이라 믿어 의심치 않는다.
소스코드 깃허브
https://github.com/omins/notiScrapper
GitHub - omins/notiScrapper: 학과 공지사항 알림봇
학과 공지사항 알림봇. Contribute to omins/notiScrapper development by creating an account on GitHub.
github.com
수정사항
(2022.07.11) 공지사항 리스트 정렬 기준 변경에 따른 반복문 수정 break -> continue.
(2022.07.11) 교직원 퇴근 시간이 지연된 케이스를 커버하기 위해 eventTrigger 실행 시간을 오후 8시에서 오후 11시 59분으로 수정
댓글