에브리타임 시간표를 원타임으로 가져온 방법 🚚💨
🎬 서론
안녕하세요. OneTime 백엔드 개발자 한상호입니다 🧑🏻💻
얼마 전, 고대하던 원타임의 신기능이 배포되었습니다!
바로 에브리타임 시간표 URL을 등록하기만 하면, 사용자의 고정 일정으로 자동 등록해주는 기능인데요.
이번 글에서는 해당 기능을 만들게 된 배경과 구현 과정을 적어보려고 합니다.
🧐 '왜' 만들었을까?
원타임에는 내 스케줄 이라는 기능이 존재합니다. (* 로그인 유저 전용)
왼쪽과 같이 본인의 고정적인 스케줄, 즉 안 되는 시간을 미리 등록해두면, 오른쪽처럼 해당 시간을 제외하고 모두 되는 시간으로 불러와 지는 기능이에요.
로그인 한 유저에게 더욱 편리함을 제공하기 위해서 위 기능을 만들었지만...정작 사용자는 우리 팀 밖에 없다는 슬픈 사실을 알게 되었어요 😢
그러던 어느 날 회의 중, 에브리타임 시간표 를 추출해서 가져와보자는 의견이 나오게 되었습니다.
아무래도 원타임의 주요 타겟이 대학생이기도 하고, 당시에는 1월이었기에 3월 개강 시즌에 맞춰 오픈하면 좋은 홍보 수단이 될 것 같다는 생각이 들어 저 또한 좋다고 말을 했어요.
제가 대부분의 로직 구현을 맡아야 했는데, 당시에는 시간이 꽤나 많이 남아있었기에 막연하게 되지 않을까? 싶었던 것 같아요. (이 생각은 구현을 하며 처참히 부서졌지만요 🫠)
또한 어려워보이기는 했지만 개인적으로 도전해서 성공해 보고 싶은 마음이 들었는데요, 현재 발생한 문제(내 스케줄 기능 접근성 부족)를 기술을 활용해서 해결하고, 이로 인해 비즈니스 가치를 더욱 늘릴 수 있는 좋은 기회였기 때문입니다.
이는 내가 평소에 제가 중요하게 여기는 가치와 일맥상통했기에, 꼭 구현해내겠다는 마음가짐으로 개발에 돌입했어요.
🕵🏻♂️ 구현 방식에 대한 고민
OCR에 도전해보다
초기 구현 목표는, 시간표 이미지를 입력 받은 후 시간대를 추출하여 스케줄로 등록하는 것이었어요.
때문에 OCR 기능의 도입이 필요했고 여러 방식들을 알아 보았는데요.
Python 라이브러리
처음에는 파이썬의 Tesseract OCR 라이브러리를 활용하여 구현에 도전하였고, 어찌저찌 한글로 변환에 성공하고 추출이 되기는 하였으나...이미지에 있는 글자가 너무 작아서인지 정확히 추출이 되지 않았어요.
또한 사람들마다 똑같은 형태의 이미지를 업로드한다는 보장이 없었기 때문에, 어디부터 어디까지를 몇 시로 정해야하는지도 결정하기가 어려웠구요.
며칠을 붙잡으며 계속해서 정확도를 올리려고 노력해보았지만.. 결국 어렵겠다라는 것을 인정하고 OCR 라이브러리를 이용한 직접 구현은 포기하였습니다. 그 대신 조금 비용을 내더라도 상용 서비스를 활용해볼까 하여 네이버 클로바 OCR로 눈을 돌리게 되었어요.
네이버 클로바 OCR
그렇게 대기업의 힘을 빌려 해결해보고자 했지만!
이 또한 이미지의 크기와 형태가 제각각이라는 점 때문에 포기하게 되었는데요, 네이버 OCR은 틀을 미리 정해두고 이미지에서 정보를 뽑아내는 것에 특화되어 있기 때문이었습니다.
결론적으로 아래의 문제들로 인해서 OCR로 구현은 포기하게 되었어요.
1. 사용자마다 이미지가 너무 제각각이고, 정확히 어떤 이미지를 업로드할 지 모름
시간표 이미지는 사용자별로 구성이 다르고 일정한 패턴이 존재하지 않음.
예를 들어, 사용자가 캡처한 이미지마다 해상도, 회전 여부, 폰트 스타일 등이 달라서 일반적인 OCR 모델로는 인식이 어려움.
2. OCR의 성능이 좋지 않음
라이브러리를 쓰자니 인식 성공률이 너무 낮음
NCP를 쓰자니 비용 문제 + 이미지가 고정이 아니라 지정 자체가 불가함
이 때는 정말 포기해야하나.. 싶었어요. 왜냐하면 평소에 시간이 많지 않아 남는 시간에 개발을 해야하기 때문에, 온전히 시간을 쓸 시간이 부족했기 때문이었어요.
그렇게 좌절하던 찰나... 주변에서 누군가 해주었던 말이 떠올랐어요.
링크를 넣어서 자동으로 추출까지 해주면 편하겠다.
그렇게 이끌리듯이 에브리타임 시간표 링크로 접속해보았고..해결방법을 찾을 수 있었어요!
웹 크롤링 방식
에브리타임에는 시간표 이미지 저장 외에도, 위와 같은 시간표 URL 공유 기능이 존재합니다.
공유 버튼을 누른 후 복사된 시간표 URL로 접속하게 되면, 위와 같은 웹 화면이 나오게 되구요.
이를 보자마자 웹 크롤링을 이용해야겠다는 생각이 들었는데요, 이유는 아래와 같습니다.
1. 웹이기 때문에 HTML 구조를 파악해서 웹 크롤링을 실행할 수 있다.
2. 시간대가 기본적으로 오전 9시 ~ 오후 11시로 고정적으로 구성되어 있다.
3. 요일과 시간대의 픽셀 크기가 동일하다. (요일은 변동이 있습니다. 이는 아래에서 추가 설명이 이어져요.)
🧱 설계
HTML 분석 및 공식 도출
지금부터는 HTML을 분석하며 공식을 도출해낸 설계 과정을 설명해보고자 해요.
노션에 정리한 내용을 기반으로 작성하기에 두서가 조금 없을 수 있습니다 😅
가. 시간표 세로 px 고정

오전 9시부터 시작하는 항목의 top이
450px이다. => 즉, 시간표는450px부터 시작한다.또한, 각 칸 (=1시간)은
50px를 차지한다.=> 즉, 450 + 50 * 15 (=09 ~ 24시까지)
=>
1200px가 시간표의 끝이 된다.
나. 시간표 가로 px 특정 가능
각 요일의 폭이 어느정도 일정하다.
월 ~ 금일 때는94 px이다.
월 ~ 토일 때는79 px이다.
월 ~ 일일 때는67 px이다.
이 외의 경우는 없기 때문에, 3가지로 나누어 요일을 특정할 수 있었습니다!
다. 시간표 각 항목 세로 px 고정
1시간 15분은
64px이다.
1시간 25분은
72px이다.
1시간 30분은
76px이다.
에브리타임은 5분 단위로 시간이 설정 가능하고, 30분에 26px, 1시간에 51px, 1시간 30분에 76px 를 가지는 것으로 파악되었어요. 하지만 5분 단위로 세세하게 나눌 수록 조금의 차이가 있었습니다.
하지만, 원타임에서는 30분 단위로 타임블럭이 존재하기 때문에, 자잘한 픽셀은 버리고 계산이 가능했습니다!
30분에 26px, 1시간에 51px, 1시간 30분에 76px 이 고정이라는 점을 활용해서 아래의 공식을 도출했어요.
Math.ceil((height - 1) / 25)
위 공식에 1시간 15분인 65을 대입 → 64 / 25 = 2.xx → 올림하면 3 → 3은 1시간 반을 의미한다.
올림한 수를 2로 나눈 후, 나머지가 있다면 30분을 추가한다.
위 테스트2 항목을 기준으로 본다면 top이
600px이므로, 시작 시간이12:00인 것을 알 수 있다.여기서 위 계산을 통해 1시간 반이 나왔으므로, 종료 시간은
13:30으로 계산 가능하다!
🚫 하지만 만약에 시작 시간이 xx:00으로 고정이 아니라면?
유저가 시작 시작을
13:15,16:05와 같이 설정할 가능성이 존재한다.

시작 시간이 13:15인 경우,
612px

시작 시간이 13:30인 경우,
625px
💡 이런 경우에는 top 픽셀을 25단위로 구분한다!
1. 우선
450px(09:00)를 기준으로 해서 그 차이를 50px로 나눈다.2.
612px(13:15)라면 (612-450) % 50 = 12가 나오게 된다.3. 여기서 만약 25보다 그 값이 작다면, 내림(버림)을 해서
600px(13:00)로 맞춰준다.1) 원타임 타임블럭은 30분 단위이기에 이를 맞출 수가 없다.
2)
안 되는 시간을 위한 기능이기에, 오히려 앞 뒤를 넉넉하게 잡는 편이 낫다.4. 하지만 만약
625px(13:30)라면, 25px 이상이 남기 때문에 13:30으로 설정한다.
🔀 전체적인 동작 및 크롤링 순서
위와 같이 구조를 파악하고 공식을 수립한 후에, 유저가 기능을 사용한다고 가정하고 설계를 진행했어요.
이는 설계 단계에서 예측하며 작성한 것이기에, 실제 코드의 로직 및 자료구조와 다를 수 있습니다!
가. 유저 에브리타임 URL 링크 입력
링크는 https://everytime.kr/@de9YHaTAnl47JtxH0muz 와 같은 형태이다.
everytime.kr/@로 시작하는 것이 고정이기에, 그렇지 않은 링크에 대해서는 예외 처리를 하며 최대한 오류를 줄인다.
나. 링크에 접속하여 파싱 진행
1) 요일 정보 구하기
월 ~ 금일 때는94 px월 ~ 토일 때는79 px월 ~ 일일 때는67 px
=> 해당 정보를 통해서 요일이 어떻게 구성되어 있는지를 우선 파악한다.
width를 통해서 파악이 가능하다.
2) 요일 고정 후, 각 항목 시간대 파악
이제
월 화와 같은 요일을 고정시킨 후에, 아래로 탐색을 시작한다.

td 태그하나하나가 각 시간표의 항목이다.이 안에 있는
height와top을 통해서시작 시간및종료 시간을 계산할 수 있다.
3) 요일 별 계산 후 저장
월요일에 대해서 파악이 끝났다면, Map 자료구조에 저장한다. (Python 사용하므로
Dictionary)키는 요일 (월, 수..), 값은 시작 시간과 종료 시간이다.
{“월”, {[10:00, 11:30], …}}과 같은 구조로 예상된다.
4) 데이터 정제
{
"code": "200",
"message": "유저 고정 스케줄 조회에 성공했습니다.",
"payload": {
"schedules": [
{
"time_point": "월",
"times": [
"08:00",
"08:30",
"09:00"
]
},
{
"time_point": "화",
"times": [
"10:00",
"10:30",
"11:00"
]
},
...
]
},
"is_success": true
}
위와 같은 구조로 데이터를 보내주어야 한다. (기존 내 스케줄 등록 요청 데이터 형태)
종료 시간이 없고, 30분을 기준으로 나누어 시작 시간만 보내는 구조이다.
하지만 현재는 시작 시간 & 종료 시간 모두 있는 상태
그러므로 만약
[10:00, 11:30], [14:00, 15:00]가 저장되어 있는 상태라면, 아래와 같이 변환해서 보내주어야 한다.
{
"time_point": "월",
"times": [
"10:00",
"10:30",
"11:00",
"14:00",
"14:30"
]
}
5) 프론트엔드에서 처리
- 기존 내 스케줄 등록과 동일한 구조로 보내주기 때문에, 이후로는 프론트엔드 단에서 처리 가능하다!
🚀 구현
구현은 방식 고민 & 설계에 비해서는 그리 오래걸리지 않았어요.
다행히 설계가 잘 되어서, GPT와 함께 여러 번 코드를 짜고 테스트를 해보니 결과가 잘 나왔기 때문입니다.
결과물은 아래에서 공개하고, 여기서는 사용한 기술 스택과 아키텍처에 대해 이야기해보려 해요.
사용한 기술 스택
✅ Python
크롤링 로직을 쉽게 구현할 수 있으며, 다양한 웹 크롤링 라이브러리들을 지원하기에 선택하였다.
✅ Selenium
브라우저 렌더링이 필요한 페이지에서 동적으로 데이터를 가져올 수 있기에 선택하였다.
✅ Flask
가볍고 빠르게 REST API 서버를 구성할 수 있고, 사용해 본 경험이 있기에 선택하였다.
✅ AWS EC2
기존에도 AWS 플랫폼을 사용중이기에, Flask API 배포를 위해 사용하였다.
✅ Docker
환경 의존성을 줄이고, 배포 과정을 단순화하기 위해 컨테이너화하였다.
✅ Caddy
HTTPS 자동 적용 및 Let’s Encrypt SSL 인증서를 손쉽게 관리하기 위해 사용하였다. (도메인 구매 없이 HTTPS 적용 가능)
위와 같은 기술 스택들을 사용하여 구현하였는데요.
특히 Caddy는 이번에 처음 사용해 보았는데, 도메인을 구입하지 않고 HTTPS를 붙일 수 있어서 편리했습니다!
(참고한 블로그 글 : https://velog.io/@miro7923/AWS-EC2-서버-도메인-없이-https-붙이기)
시스템 아키텍처
웹 크롤링 서버까지 구축한 후의 최종적인 OneTime 시스템 아키텍처는 아래와 같아요.

전반적인 아키텍처 설명은 추후 작성하는 것으로 하고..
크롤링 서버만 보자면 EC2 내부에 플라스크 웹 애플리케이션 서버를 띄워서 클라이언트와 직접 API 통신할 수 있도록 구현하였어요.
또한 환경을 동일하게 유지하고 편리하게 배포하며, 경량화를 위해서 도커를 활용하였습니다.
코드에 대한 변경이 잦지 않을 것 같아서 아직 GitHub Actions 스크립트까지 작성하지는 않았는데, 생각보다 잦아서 조만간 해야할 것 같다는 생각이 드네요..!
🌈 성과
결과물

좌측은 에브리타임 시간표, 우측은 URL 입력 후 자동으로 추출된 내 스케줄이에요.
동일하게 추출되어 들어간 모습을 볼 수 있습니다!
아래의 영상을 보면 기능의 동작 과정에 대해 더 잘 이해할 수 있을 것 같아요.

거의 일주일 이상을 붙잡았던 기능 구현이었는데, 결과가 잘 나와서 매우 만족스러운 과정이었어요!
테스트를 해보았을 때는 추출에 오차가 없이 설계한 대로 잘 동작했구요 ✌🏻
해당 기능을 사용해서 몇 명의 유저라도 편리함을 느끼기를 바라며 배포를 진행했습니다.
배포 이후는?
누적 사용자 7,000+명
25년 4월 기준으로 누적 사용자 7,000+명을 보유한 서비스가 되었어요.
마케터 영입으로 인한 홍보 진행 + 신기능 업데이트 + 에브리타임 연동 기능 이벤트 + 개강 시즌 등.. 여러 요인이 있었다고 생각하는데요. 하지만 이 또한 서비스가 쓸만했기에 가능한 결과였다고 생각합니다.
지난 24년 8월부터 모든 팀원들이 함께 기획부터 참여하고 사용성에 대해 끊임없이 고민한 결과가 이렇게 긍정적으로 나오는 것 같아 뿌듯한 마음이 들어요.
백엔드 파트로서 앞으로도 계속해서 안정적인 서비스를 제공하고 고객의 불편함을 기술로 개선하기 위해 최선을 다하겠습니다 🧑🏻💻🔥
내 스케줄 사용자 80명 이상 증가
또한 에브리타임 시간표 추출 기능을 활용하여 내 스케줄 을 등록한 유저가 80명 이상 늘어났어요.
기존에는 거의 5명에만 그치던 것에 비하면 큰 수확이라고 볼 수 있습니다.
하지만 아직 더욱 많은 사람들이 사용해주었으면 하는 마음이 큰데요..!
1초만에 등록하고 나면, 이후로 스케줄을 등록할 때마다 최소 몇 십 초씩 절약이 가능하기 때문에 절대 손해가 아니라고 생각해요 😄 (효율에 미친 제가 보장합니다.)
🏁 마무리
이번 기능을 구현하며 오랜만에 새로운 것에 도전하는 즐거움을 느낀 것 같아요.
때로는 고통스러운 시간이었지만..! 결과적으로 서비스가 성장하는 데 도움을 주었고, 한 명의 유저라도 OneTime을 더욱 편리하게 사용하는 데 이바지했다는 것에 힘든 것은 다 잊을 수 있었습니다.
모두들 OneTime 많이 사용해주세요! 👋🏻




댓글
로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다.