버스하냥 시간표 변경 알리미 만들기
Last updated: Apr 18, 2023
버스하냥 팀원들이 더 편하게 시간표가 바뀌는 것을 알 수 있도록, 시간표가 변경되면 알려주는 시스템을 구축해보았습니다.
도입 배경
버스하냥 서비스는 한양대학교 ERICA 셔틀버스 시간표를 제공하는 서비스입니다. 하지만 저희 서비스는 시간표를 수동으로 입력한 값을 제공하고 있습니다. API에서 JSON값으로 제공을 하여 서비스에 적용을 하고 있지만, 시간표가 변경되면 변경된 시간표를 보고 사람이 수동으로 API에 들어가는 JSON 값을 수정해 줍니다. 이는 학교측에서 제공하는 시간표의 형식 때문에 자동화가 어려운 부분이 있어서 그러한데, 이 부분은 다음 챕터에서 서술할 예정입니다. 학교는 또한 셔틀버스 시간표가 변경되는것 또한 별다른 공지를 해주지 않는데, 이는 잘못하면 셔틀버스 시간이 변경되었음에도 서비스에서는 바뀌지 않아 학생들에게 착오를 줄 수 있는 부분이기도 합니다. 이러한 문제를 해결하기 위해, 시간표가 변경되면 알려주는 시스템을 구축하기로 하였습니다.
셔틀 시간표 공지 방식

저희 학교는 셔틀버스 시간표를 다음과 같이 홈페이지에서 PDF 파일을 제공합니다. PDF 파일을 다운로드 받으면 다음과 같습니다.

우선 PDF 가독성이 매우 떨어집니다… 이미지에는 나오지 않으나 전체 PDF를 확인해보면 마지막으로 PDF가 변경된 날짜가 우측 상단에 있어 이를 통해 시간표가 업데이트 되었는지를 확인할 수 있습니다. 하지만 이는 학생들이 직접 확인해야 하는 부분이기 때문에, 이를 자동화할 수 있는 방법을 찾아야한다고 생각했습니다.
알림 자동화 구축
저는 우선 해시를 이용하여 시간표 변경 유무를 검증하고자 하였습니다. PDF 파일의 해시를 저장해두고, 주기적으로 사이트의 PDF 해시를 체크하고, 변경된 부분이 있으면 해시 또한 변경되니 해시가 바뀌었으면 웹훅을 통해 버스하냥 팀원들에게 이메일을 보내게끔 하였습니다. 이를 위해 다음과 같은 과정을 거쳤습니다.
Cron 작업용 스크립트
일정 주기로 운영되는 작업이라 Cron 작업으로 구축하였습니다. PDF의 해시를 체크하고, 변경된 부분이 있으면 알려주는 스크립트를 주기적으로 실행하도록 하였습니다.
def main():
BASE_URL = "https://www.hanyang.ac.kr"
btn = find_download_button(f"{BASE_URL}/web/www/shuttle_bus_timetable")
if not btn:
# No button found
print(f"Button not found error! - {get_current_time()}")
sys.exit(1)
pdf_path = parse_pdf_path(btn)
if pdf_path == '':
# Invalid url
print(f"Invalid URL in button - {get_current_time()}")
sys.exit(1)
pdf_url = f"{BASE_URL}{pdf_path}"
pdf_hash = get_pdf_hash(pdf_url)
# Check previous hash
API_URL = "<SECRET_API_URL>"
response = requests.get(API_URL)
prev_hash = response.text
if pdf_hash == prev_hash:
print(f"Same hash value. - {get_current_time()}")
return
# Hash different, update to new hash
password = os.environ.get('API_PW')
param = {
"pw": password,
"hash": pdf_hash
}
# Update Hash
response = requests.post(API_URL, json=param)
if response.status_code == 200:
print(f"Hash updated. - {get_current_time()}")
email_param = {
"pw": password,
"previous_hash": prev_hash,
"new_hash": pdf_hash
}
# Send email
email_response = requests.post("<SECRET_EMAIL_API_URL>", json=email_param)
print(email_response.text)
else:
print(f"Hash update failed. Status: {response.status_code}")
sys.exit(1)
위 공지 방식 이미지에서 제공한 것 처럼, PDF 파일 다운로드 버튼이 있는데, 이를 Python bs4 라이브러리를 이용하여 찾은 후, PDF 파일의 URL을 통해 PDF를 다운받아 PDF의 해시를 구합니다. 이후, 이전에 저장해둔 해시와 비교하여 다르면 해시를 업데이트하고, 이를 API를 통해 저장합니다. 이후, 이전 해시와 다를 시 새로운 해시를 이메일로 보내주는 API를 호출합니다.
저희 버스하냥 팀은 따로 서버를 운영하고 있지 않습니다. 따라서 Cron 작업이 항상 돌아가는 서버가 필요했는데, 저희는 GitHub Actions를 이용하여 이를 구현하였습니다.
name:
Cron Job - Timetable
on:
schedule:
# Schedule to every hour, 12:00AM - 10:00AM which is equivalent to 09:00AM - 07:00PM in KST.
- cron: "0 0-10 * * *"
jobs:
cron:
runs-on: ubuntu-latest
steps:
- name: 🍽️ Checkout
uses: actions/[email protected]
- name: 🐍 Set Python environment
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip' # Cache pip packages in GitHub Actions to speed up the process
cache-dependency-path: '**/requirements.txt'
- name: 🛠️ Install Requirements
run: pip install -r requirements.txt
- name: 🏃♂️ Run Python Script
run: python parse.py
env:
API_TOKEN: ${{ secrets.API_PW }}
Cron 작업은 한국 시간 기준 오전 9시부터 오후 7시까지 한시간에 한번 매일 구동하게끔 설정하였습니다. 이는 학교 직원분들이 평균적으로 근무하는 시간대 외에는 시간표가 업데이트 될 확률이 거의 없기 때문에 불필요한 API 호출을 줄이기 위함입니다.
또한 GitHub Actions에서 지원하는 cache
옵션을 추가하여 매 시간 Cron 작업용 인스턴스가 생성된 후, 캐싱해둔 pip
패키지들을 추가하여 pip install
이 실행되는 시간을 대폭 줄였습니다.
API 서버 구축
위에 스크립트를 보면, API를 두개 사용하였습니다. 하나는 해시를 확인한 후, 해시를 업데이트 하는 API이고, 다른 하나는 이메일을 보내주는 API입니다. 해당 API는 저희 버스하냥 API와 동일하게 Cloudflare Workers를 이용하여 구축하였습니다. Cloudflare Workers에서는 MailChannels를 이용하여 별도의 설정 없이 무료로 이메일을 보낼 수 있기 때문에, 간단하게 구축할 수 있었습니다. (링크)
결과

위 이미지를 보면 Cron 작업이 정상적으로 실행됨을 확인 할 수 있습니다.

또한 Cron 작업이 실행된 로그를 보면 새로 생긴 인스턴스임에도 불구하고 캐싱된 pip 패키지들로 인하여 실행시간이 매우 짧음을 확인할 수 있습니다. 변경되지 않은 PDF 값을 확인했을 시에는 위와 같이 표기됩니다.

이메일이 잘 오는지 확인도 해보았습니다. API 서버에 저장된 해시값을 임의로 변경하고 Cron 작업이 돌아가게끔 둔 결과, 이메일이 정상적으로 발송되는 것을 확인할 수 있었습니다.