식당 메뉴 알려주는 챗봇 만든 후기

lsy·2022년 10월 23일
0
post-custom-banner

후기

프로젝트 자체는 5월에 완성됐고 현재 유지보수중이다.

후기를 좀 더 일찍 작성해야 됐었는데 너무 늦게 작성했다... 지금이라도 이 프로젝트의 목적과 만드는 과정 도중의 의사결정 등을 조금 기록해두려고 한다.

메뉴

우리 학교의 교직원 식당과 학생 식당은 매주 식단표를 제공하는데, 그 식단표를 보는 방법이 상당히 끔찍하다.

학교 어플 -> 로그인 시간 대기... -> 교직원/학생 식당 식단표 버튼 클릭 -> 웹 브라우저로 리다이렉트 -> 식단표 확대

이 과정을 거쳐야 한다. 물론 식단사이트를 미리 웹에다가 올려놓고 매일 볼 수도 있으나.. 귀찮고 무엇보다 핸드폰 화면에 맞지 않는 사이즈라서 제대로 안보여서 계속 확대해야한다.

나는 학생 식당을 자주 이용했는데 이 과정이 너무 번거로웠다. 그래서 이 메뉴를 알려주는 챗봇을 만들기로 했다.

어떻게 만들까?

우선은 접근성이 좋은 카카오톡 챗봇을 사용하기로 했다. 매일 사용하는 메신저에서 컴퓨터든 스마트폰이든 식당 메뉴를 확인할 수 있게 하고 싶었다.

그래서 카카오톡 챗봇의 문서를 확인하였다. 챗봇은 입력한 말에 따라 어떤 결과를 리턴하는 형태로 i builder 홈페이지에서 해당 내용을 수정할 수 있었다.

스킬이라는 항목이 있었는데, 그 항목이 내가 원하던 것이었다. 내가 챗봇에게 "학생식당 메뉴"라고 입력하면 봇은 내가 만든 API에게 요청하고, 내가 만든 API는 카카오 챗봇이 알아들을 수 있는 json 형태에 맞춰서 응답한다. 그리고 그 응답을 카카오 챗봇이 받아 메시지를 보내는 것이다.

그래서 나는 해당 봇 세팅을 마친 뒤, API 서버를 만들기로 했다.

전체적인 구조는 다음과 같다. API 서버는 매주 월요일에 메뉴를 파싱하여 DB에 저장하고, 이것을 날짜에 따라 가져와 응답하기로 하였다.

API 서버

API 서버를 만들 때 고민이 있었다. 프레임워크를 우선 골라야 했는데 Golang의 Gin을 사용할 것인가 Java의 Spring boot를 사용할 것인가.. 두 선택지에서 고민하게 되었다. Go는 서브 언어로 학습중이어서 사용해보고 싶은 마음이 컸다.

하지만 처음 해보는 프로젝트이기도 하고, 내가 구현하다가 필요한 내용들을 쉽게 찾으려면 인터넷에 레퍼런스가 많이 퍼져있는 Spring이 나을 것 같아서 Spring boot로 진행하기로 결정했다.

메뉴 파싱하기

그 다음 고민으로는 메뉴를 가져오는 방법이다.

해당 메뉴는 웹 사이트에서 이미지 형태로 제공되었다.

그래서 맨 처음에 생각한 방법은 파이썬의 OCR을 사용하는 방법이었다. 우선 테스트로 해당 라이브러리를 사용해봤는데 한글 인식률이 심각했다... 알아보니 전처리 과정이 중요하다고는 하는데, 나는 저 메뉴판을 도저히 전처리할 엄두가 나지 않았고 전처리를 한다 해도 정확성을 보장할 것 같지 않았다.

따라서 다른 방법을 찾아보기 시작했다. 해당 사이트를 뒤적뒤적한 결과 해당 사이트가 파일 다운로드를 제공한다는 사실을 알게 되었다. 그리고 그 파일은 엑셀 파일이었다.

여기서 영감을 얻었다. 그렇다면 파이썬을 통해서 해당 엑셀 파일을 파싱하면 되지 않을까?? 머리 속에 전구가 반짝한 기분이었다.

그래서 패킷 분석을 통해 해당 파일을 다운로드하는 주소를 알아내었고, 파이썬으로 해당 파일을 다운로드 하는 코드를 짜서 엑셀 파일을 다운로드 하는 것에 성공했다.

그러면 이 다음은 이제 엑셀 파일을 파싱하는 것인데, xml 파싱을 지원하는 pandas를 사용하기로 했다. 그리고 엑셀 파일의 공백란 등을 분석해서 파싱하는 코드를 짜서 드디어 원하는 메뉴를 파싱하는 데에 성공했다.

크롤러

파일을 다운로드 하는 데에는 몇 가지 필요한 정보들이 존재했다. bookcode라는 해당 사이트가 활용하는 파일의 구분자와 파일의 이름이었다. 이것들은 전부 해당 웹사이트의 html파일에 존재했다.

따라서 나는 파이썬의 requests 모듈을 이용해 해당 웹사이트의 html body를 파싱하기로 했다. 근데 문제가 생겼다. 파일의 이름을 파싱할 수 없던 것이다. 해당 div 자체가 존재하지 않았다.

알아본 결과 해당 페이지는 동적 페이지이기 때문에 파일의 이름은 나중에 자바스크립트에 의해 생성 되는데 requests 모듈은 그냥 바로 html 파일만을 가져오기 때문에 일어난 문제였다.

그렇다면 requests 모듈을 사용하지 못한다. 그래서 실제 브라우저를 동작시켜서 크롤링을 진행하는 방식인 selenium을 사용하여 크롤링을 하기로 했다. 물론 아주 잘 파싱할 수 있었다.

aws lambda

위에서 언급했듯 내가 생각한 구조는, api 서버가 매주 월요일 메뉴를 파싱하는 것이었다. Spring boot에서는 파이썬을 실행할 방법이 없다. 그렇다면 파이썬을 위한 별도의 서버를 만들어야 했다.

그런데 일주일에 한 번 일어나는 파싱을 가지고 서버를 띄우는 건 너무 자원 낭비 같았다. 실제로 AWS 프리티어는 하나의 ec2 밖에 못 띄운다.(무료로)

그래서 aws lambda를 이용하기로 했다. 파싱 하는 동작은 어떤 상태를 유지해야할 필요도 없고, 그냥 요청이 들어오면 해당 내용을 실행하면 되기 때문이다.

aws lambda에서 selenium을 동작하는 방법을 찾고.. 파일의 저장 경로를 찾는 등 시행착오를 거쳐서 마침내 코드를 완성했다.

코드는 request의 파라미터에 따라 selenium을 이용해서 학생 식당이나 교직원 식당의 bookcode와 파일 이름을 찾아오는 코드 하나, 해당 내용들을 사용해 파일을 저장후 파싱하는 코드 두 개(학생 식당, 교직원 식당 따로)를 만들었다.

이것들을 전부 aws lambda에 업로드 후에 api gateway를 이용해 각 함수에 주소를 달아줬다.

Spring Scheduler

api 서버는 매주 월요일에 파싱하는 동작을 실행해야 한다. 이 동작을 하게 하는 방법을 찾아본 결과 Spring Scheduler를 사용하면 된다고 해서 해당 어노테이션과 cron을 사용해 매주 월요일 오전 7시에 파싱을 시작하도록 했다.

파싱을 시작할 때는 boolean으로 만든 isParingNow라는 변수의 flag를 true로 만들고 파싱을 시작한다. 컨트롤러는 이 flag에 따라 챗봇에게 "파싱중입니다"라는 메시지를 전달하여 데이터베이스에 데이터가 존재하지 않아서 이상한 데이터가 전달되는 것을 방지했다.

이후 해당 메서드에서는 현재 DB에 저장되어 있는 파일 이름과 위에서 aws lambda로 만든 api로부터 파일 이름을 응답 받아 서로 비교한다. 만약 두 파일 이름이 같지 않다면 새 메뉴가 업로드 된 것이니 파일 파싱하는 api에 메뉴 파싱을 요청하게 된다.

간략한 내용은 이렇다. 만약 더 자세한 설명이나 코드를 보고 싶다면 레포지토리를 참조바랍니다!

마무리

해당 API 서버는 현재 AWS ec2에 올라가 있고, DB는 AWS의 RDB를 이용하고 있다. 그리고 현재도 챗봇은 동작 중이다.

친구가 267명인데 비록 많은 숫자는 아니지만 사용해주는 사람이 있어서 뿌듯하다. 물론 나도 맨날 쓰고 있다..

https://github.com/sunaookamishiroko/menuplanner-chatbot
더 자세한 설명과 코드는 위 레포지토리에서 확인할 수 있다.

profile
server를 공부하고 있습니다.
post-custom-banner

0개의 댓글