본문 바로가기
카이스트 정글

정글 나인 헬퍼 제작 후기

by hjy00n 2024. 9. 6.

 

"카이스트 9기 정글러들을 위한 문지캠퍼스 식단 봇"

매일 3회, 특정 시간에 슬랙 채널에 아침, 점심, 저녁 식단을 자동으로 전송한다.

 

정신없는 red-black tree 주차에 어떻게든 시간을 내서, 간단한 슬랙 봇을 만들어 보았다.

여기서 "간단한"의 의미는 손쉽게 만들었다는 의미가 아니다.

흔히 오픈소스를 둘러보면 발견할 수 있는 Simple... 로 시작하는 작은 프로젝트, 여기서 쓰인 Simple 을 의미한다.

정말 최소한의 필요한 기능, 최초에 필요로 했던 기능만을 남기고 불필요한 것들을 모두 제거한 미니멀한 봇이다.

 

그럼에도 불구하고, 제작 및 배포 까지 총 5시간은 걸린 것 같다.

 

기존에 비슷한 봇을 만들었던 경험이 있었기 때문에, 대략 아이디어 및 웹 스크래핑 방법을 구상하고 서버에서 어떻게 자동화할 지에 대한 계획을 모두 세웠다. 기존에 이미 만들어져 있는 정글 봇을 구글링을 통해 몇몇 발견했지만, 내가 돌릴 리눅스 서버 환경에 맞지 않다거나, 라이브러리 버전이 낮다거나, 필요하지 않은 기능들이 포함되어 있었기에 처음부터 다시 만들기로 결정했다.

 

처음에는, 로직 자체는 매우 단순했기 때문에 이것저것 설정하는 시간을 포함해서 1시간이면 충분할 것이라고 생각했다.

 

우선, 예전 경험에 대한 기억을 되짚어보았다. 

목표는 슬랙 봇을 만드는 것이므로, 슬랙 채널에 메시지를 전송할 수 있어야한다. 그러기 위해선 슬랙에서 제공하는 기능을 사용해야 하는데, 예전에 사용했던 것은 Incoming WebHooks 라는 슬랙 앱이었다. 이것을 정글 워크스페이스에 추가하고 특정 채널에 대한 웹훅 API을 받아서 이를 활용하는 방식이었다. 이 API를 통해 원하는 메시지를 특정 채널에 전송할 수 있었다.

 

그렇게 떠올린 기억을 기반으로 작업을 하려는데 이게 웬걸, 다음과 같은 문구를 발견했다.

Please note, this is a legacy custom integration - an outdated way for teams to integrate with Slack. These integrations lack newer features and they will be deprecated and possibly removed in the future. We do not recommend their use. Instead, we suggest that you check out their replacement: Slack apps.

 

곧 해당 기능은 deprecated 될 예정이니, 슬랙 앱을 직접 만들어서 사용하라는 것이었다.

 

다행히 새로운 방법은 매우 익숙했다. 흔히 sns 로그인에서 사용되는 OAuth2 방식이었기 때문이다. 심지어 내가 필요로 하는 웹훅 기능은 App 인증 과정도 필요치 않았다.

 

결과적으로, 간단히 웹훅 URL을 생성하고, 임의의 application/json POST 요청을 통해 테스트용 채널에 메시지가 성공적으로 전송되는 것을 확인했다.

 

전송이 정상적으로 작동하는 것을 확인했으므로, 이제 데이터를 전송해야 한다. 그러나 아직 전송할 식단 데이터를 가지고 있지 않다.

 

해당 데이터는 아래와 같다.

여기서 조식, 중식, 석식에 대한 식단만 가져와야 한다.

 

예를 들어, 조식의 경우 필요한 데이터는 다음과 같다.

흑미밥, 된장찌개, 멸치볶음, 옛날소세지전/케찹, 도시락김, 포기김치

 

웹 브라우저에서 사이트 내 해당 데이터의 elements 위치를 알아내는 것은 매우 간단하다. 브라우저의 inspect 기능을 이용하면 된다. 이 기능으로 현재 커서가 위치한 곳의 elements 를 단번에 알아낼 수 있다. 즉, CSS Selector 를 복사할 수 있다.

 

방법은 다음과 같다.

1. requests 라이브러리를 사용하여 html 본문을 가져온 뒤,

2. BeautifulSoup 라이브러리의 html parser를 이용하여,

3. CSS Selector 로 특정 값만 추출한다.

 

그러나 그 당시에, 이러한 방식으로는 값을 어떠한 알 수 없는 이유로 제대로 파싱하지 못했다. 그래서 사이트에서 javascript를 사용하여 동적으로 값을 서버로부터 가져오는 것 때문인가 생각했다.

 

EDIT:

지금 다시 확인해보니 사이트는 SSR 방식으로 동작하는 것으로 보인다. requests 로부터 가져온 html 원문에 어떤 특수 문자가 포함되어 있어서 값을 제대로 파싱하지 못한 것이다. 만약 이 문제가 해결된다면, 아래에서 후술할 selenium 및 firefox 사용은 필요하지 않으므로, 코드가 훨씬 짧아지고, 성능은 훨씬 올라갈 것이다.

현재 수정 PR 작성 중.

 

그래서 다음 방법으로 selenium을 사용했다. 이것은 실제 브라우저의 동작을 시뮬레이션 할 수 있다.

이후 동작은 동일하다. 해당 URL 접속을 시뮬레이션 후, CSS Selector로 값을 추출한다. 그리고 웹훅을 사용하여 슬랙 채널에 전송한다.

 

로컬에서의 selenium 테스트를 성공한 후, 우분투 서버에서도 마찬가지로 테스트를 했지만 실패했다.

 

예전의 기억을 되짚어보았다. geckodriver 직접 다운받은 뒤, selenium에서 옵션으로 load 해서 사용했던 기억이 난다. 그러나 그 방법도 마찬가지로 작동하지 않았다. selenium 버전이 예전에 사용했던 버전과 달라진 것이다. 공식문서를 참조하니 방법이 약간 달라진 것 같다.

 

우여곡절 끝에 우분투의 패키지 매니저를 통해 firefox를 설치했지만, 또 무엇인가 문제가 생긴다.

 

설명하면 길어지므로 아래의 링크로 해결방법을 대체하겠다.

https://stackoverflow.com/questions/72374955/failed-to-read-marionette-port-when-running-selenium-geckodriver-firefox-a

 

"Failed to read marionette port" when Running Selenium + geckodriver + firefox as a non-root user in a Docker container

I'm running selenium tests inside a docker container, with Firefox and Geckodriver. When running that container as root, everything works fine. When running the container as non-root user (USER 100...

stackoverflow.com

요약하자면, 우분투의 snap 버전이 아닌 deb 패키지로 firefox를 설치하는 것이다.

 

여기까지 진행한 결과, 서버에서도 성공적으로 식단 데이터를 슬랙 채널에 전송하는데 성공했다.

 

이제 서버에서 주기적으로 봇이 동작하도록 자동화를 할 차례이다.

 

여러가지 방법이 있겠지만, 나는 crontab을 사용하기로 했다. 물론, python 데몬을 띄워놓고 매 시간을 체크해서 전송하는 방법을 사용할 수도 있지만, 데몬 내에서 라이브러리의 memory leak 이슈로 인해 오랜 시간이 지나면 서버가 죽는 경우가 많았다. 그래서 작업을 시간대 별 프로세스 실행으로 리눅스에 위임하는 방식을 사용했다.

 

내가 작성한 python 코드는 실행 시, 전달한 인자에 따라서 아침, 점심, 저녁 식단 중 하나를 선택하여 메시지를 전송하도록 구현되어 있다. 그러므로 crontab 에서 정해진 시간에 맞게 인자를 전달하는 명령어를 실행하면 될 것이다.

 

예를 들어, 아래와 같다.

0 07 * * * .../run.sh breakfast
0 11 * * * .../run.sh lunch
0 17 * * * .../run.sh dinner

 

각각 오전 7시, 오전 11시, 오후 5시에 아침, 점심, 저녁 식단을 전송한다.

 

여기까지 작업하고 배포했다.

 

그러나 실제로는, 오후 4시에 아침 식단이 전송되었다.

 

생각해보니, 서버는 시간이 디폴트값인 UTC 이다. 즉 KST(+9) 기준으로 바꿔줘야 한다.

서버의 디폴트값을 KST로 변경하거나, UTC를 KST(+9) 에 맞게 직접 변경하는 방법이 있다.

 

나는 서버의 시간은 UTC를 유지하고 싶었으므로 후자를 택했다.

 

아래와 같다.

0 22 * * * .../run.sh breakfast
0 02 * * * .../run.sh lunch
0 08 * * * .../run.sh dinner

 

이제는 모두 정상적으로 동작한다.

 

소스코드

https://github.com/hjyoon/jungle9_menu_helper

'카이스트 정글' 카테고리의 다른 글

Malloc Lab 그리고 CS:APP  (0) 2024.09.13
레드-블랙 트리 그리고 C언어  (0) 2024.09.12
컴퓨팅 사고로의 전환  (0) 2024.08.31
리눅스 왜 써요?  (0) 2024.08.27
지식을 소화한다는 것  (0) 2024.08.23