본문 바로가기
프로젝트/GCP+Python | 성결대학교 공지사항 알리미 앱

[성결대학교 공지사항 알리미 디스코드 채널 만들기] #4. 디스코드 봇 클라우드 서버 사용해서 24시간 돌리기

by 카랑현석 2024. 3. 30.

이전 문제 상황

이전 글에서 디스코드를 통해 성결대학교 홈페이지에 새롭게 공지사항 글이 올라오게 되면 그 것을 디스코드로 알려주는 것까지 구현을 했다.

 

이제 컴퓨터를 24시간 키고 실행할 수는 없으므로 이 것을 대신해줄 가상의 컴퓨터가 필요했다.

클라우드 컴퓨팅 서비스 선택

대표적인 클라우드 컴퓨팅 서비스는 AWS, GCP, Azure 가 있다.

하지만 AWS, GCP, Azure은 모두 유료이고 지갑 사정이 넉넉하지 않은 나로서는 부담이 갔다.

 

무료 클라우드 컴퓨팅 서비스를 찾던 중 Oracle Cloud을 알게 되어 사용하기로 결정했었다.

 

하지만, Oracle Cloud 가입을 하는 과정에서 결제 카드를 등록하고 나면 '트랜잭션을 처리하는 중 오류' 가 발생하였다.

Live Chat을 통해 문의도 넣었지만 불행하게도 저희가 도와줄 수 있는 것은 없다.는 대답만 들었다.

이미 많은 사람들이 겪고 있는 문제인 것 같았다.

 

AWS, Azure은 가입을 한 적이 없어 가입 크레딧을 아끼기 위해 기존에 어짜피 사라질 학교 계정으로 가입했던 GCP을 사용하기로 결정했다.

 

1. VM 인스턴스 생성

1. 구글에 'Google Cloud Platform' 검색해서 최상단 페이지 클릭

2. '콘솔로 이동' 클릭

3. 'Compute Engine' 클릭

4. '사용' 클릭

5. 좌측 상단 햄버거 바에서 'Compute Engine' > 'VM 인스턴스' 클릭

 

6. '인스턴스 만들기' 클릭

 

7. 인스턴스 설정

리전 : 'us-west1 (오리건)' 또는 아이오와, 사우스캐롤라이나 중 하나로 지정

머신 구성 : E2

머신 유형 : e2-micro

부팅 디스크 : 운영체제 - Ubuntu , 버전 - Ubuntu 20.04 LTS

8. ID 및 API 액세스 설정

액세스 범위 : 모든 Cloud API에 대한 전체 액세스 허용

방화벽 : HTTP 트래픽 허용 / HTTPS 트래픽 허용 / 부하 분산기 상태 점검 허용

 

9. '만들기' 버튼 클릭

10. VM 인스턴스 페이지에서 'SSH' 클릭

2. SSH 설정 및 라이브러리 설치

파이썬 프로그램을 돌리기 위해 내 코드에서 썼던 모듈들을 다운로드 해주어야 한다.

beautifulsoup4,pyperclip,requests,selenium,chromedriver-autoinstaller,undetected-chromedriver,pyautogui,discord.py,asyncio 라이브러리를 설치한다.

 

1. Ubuntu에서는 pip가 설치되어 있지 않아 pip을 설치해주어야 한다.

 

     - 1) pip 설치 명령어

wget https://bootstrap.pypa.io/get-pip.py

 

     - 2) 최고 권한으로 실행

sudo python3 get-pip.py

 

     - 3) Ubuntu을 업그레이드 해준다.

sudo apt-get update

 

     - 4) python 설치 확인

python3 --version

 

2-A. beautifulsoup4,pyperclip,requests,selenium,chromedriver-autoinstaller,undetected-chromedriver,pyautogui,discord.py,asyncio 라이브러리를 설치

pip3 install beautifulsoup4 pyperclip requests selenium chromedriver-autoinstaller undetected-chromedriver pyautogui discord.py asyncio

만약 설치 시 중간에 아래 사진와 같은 경고가 발생한다면?

-> chromedriver-path 스크립트가 설치된 경로가 현재의 실행 PATH에 포함되어 있지 않아 생기는 경고이므로 이 문제를 해결 하기 위해서 다음과 같은 절차를 실행한다.

  WARNING: The script chromedriver-path is installed in '/home/hhs0991/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
더보기

방법1) PATH에 경로 추가하기(1회성)

1) 경고가 언급한 경로(/home/hhs0991/.local/bin)를 현재 사용자의 PATH에 추가합니다. 이렇게 하면 시스템에서 이 경로의 스크립트를 찾을 수 있게 됩니다. 이를 위해 다음과 같은 명령을 사용할 수 있습니다.

export PATH=$PATH:/home/hhs0991/.local/bin

 

방법2[추천]) 영구적으로 PATH에 경로 추가하기 

1) .bashrc 또는 .profile 파일 열기

nano ~/.bashrc

 

2) PATH에 경로 추가하기

파일의 맨 끝에 아래 코드와 같은 문구를 추가합니다.

export PATH=$PATH:/home/hhs0991/.local/bin

 

3) Ctrl+숫자0 을 눌러 파일을 저장하고, Ctrl+X을 눌러 텍스트 편집기를 종료합니다.

 

4) 변경사항 적용하기

 

source ~/.bashrc

 

 

2-B. 기존 개발 환경 파일 (requirements.txt) 을 업로드하고 개발 환경을 서버에도 똑같이 적용하기 (2-A보다 추천하는 방법)

pip install -r requirements.txt

 

3. 파일 올리기

1. '파일 업로드' 를 통해 필요한 파일을 모두 올립니다.

2. ls 명령어를 통해 파일이 정상적으로 올라갔는지 확인합니다.

 

3. notice_bot 디렉토리를 하나 만들어서 그 디렉토리로 프로그램 파일을 옮깁니다.

notice_bot 디렉토리 생성

 

crawling+discord.py 파일과 useragents.txt 파일을 notice_bot 폴더로 이동시킨다.

 

4. 파이썬 프로그램 서버에서 실행

1. 프로그램 소스 코드가 있는 notice_bot 파일로 이동합니다.

2. 실시간 모니터링 프로그램의 경우 시간이 중요합니다. 우분투 서버 시간을 한국 시간으로 바꿉니다.

sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

 

3. SSH 창을 종료해도 프로그램이 계속해서 돌아가도록 하기 위해 nohup 명령어를 사용합니다.

=> nohup 명령어 : 프로세스를 실행한 터미널의 세션 연결이 끊기더라도 지금 실행시킨 프로그램을 종료하지 않도록 하는 명령어이다.

nohup python3 crawling+discord.py &

명령어를 치고 엔터를 한 번 더 칩니다.

 

정상적인 경우) 엔터해도 아무것도 뜨지 않는다.

 

ps 명령어를 통해 실행중인 프로세스를 조회해보면 python3가 실행중임을 볼 수 있다.

 

오류)

위와 같이 뜨면 crawling+discord.py 파일의 실행 중 문제가 있는 것이므로 nohup.out 의 로그를 살펴보아야 합니다.

 

** 실행 파일에 문제가 있는 경우 (자기 컴퓨터에서는 정상 작동했다가 서버에서 작동하지 않는 경우 원인 파악 방법)

더보기

A) nohup.out 을 열어봅니다.

명령어 : cat nohup.out

 

B) 문제의 원인을 파악하고 해결합니다.

 

[참고 명령어]

nohup.out 안의 내용 초기화

> nohup.out

 

nohup.out 내용 조회

cat nohup.out

 

5. 기타 오류 해결 과정

1) python3으로 파일 실행하기

python3 [파일이름].py

 

오류1) ValueError: No chrome executable found on PATH 

원인 : Chrome 브라우저의 실행 파일이 시스템의 PATH에 존재하지 않아서 발생하는 문제

해결 : Ubuntu(SSH)에서 아래 2개 명령어 입력

sudo apt update
sudo apt install -y chromium-browser

 

오류2) xdg-settings: not found 오류

 

원인 :  Linux 시스템에서 사용되는 명령어인 xdg-settings을 찾을 수 없을 때 발생

해결 : Ubuntu(SSH)에서 아래 STEP1~STEP2 명령어 실행

STEP 1) xdg-utils 설치
sudo apt-get install xdg-utils
STEP 2) 환경 변수 설정
export PATH="/usr/bin/xdg-settings:$PATH"

 

오류3) /usr/lib/python3/dist-packages/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (2.2.1) or chardet (3.0.4) doesn't match a supported version! 오류

 

원인 : requests 패키지의 버전이 urllib3 또는 chardet과 호환되지 않음

해결 : Ubuntu(SSH)에서 아래 STEP1~STEP2 명령어 실행

STEP 1) requests 패키지를 최신 버전으로 업데이트
pip install --upgrade requests
STEP 2) urllib3, chardet 패키지 최신 버전으로 업데이트
pip install --upgrade urllib3 chardet

 

오류4)  `cannot connect to chrome at 127.0.0.1:37541` when using undetected-chromedriver with Python 오류

원인 : selenium 사용 시 특정 브라우저와 드라이버가 호환이 되지 않아서 생기는 문제

해결 : 아래 A)~E) 의 모든 명령어를 순서대로 실행 후 파이썬 코드 변경

# A) undetected-chromedriver 3.0.6 버전으로 재설치
python3.8 -m pip uninstall undetected-chromedriver # undetected-chromedriver를 제거
python3.8 -m pip install undetected-chromedriver==3.0.6 #undetected-chromedriver 라이브러리 3.0.6 버전으로 설치
sudo ln -s /usr/bin/google-chrome-stable /bin/chrome # 심볼릭 링크 설정

# B) 우분투의 패키지들 업데이트 (저장소 목록을 새로 가져오기)
sudo apt-get update

# C) 크롬 설치
sudo apt install wget –y #wget 설치
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb #Chrome 패키지 다운로드
sudo dpkg –i ./google-chrome-stable_current_amd64.deb #Chrome 패키지 설치

# D) 크롬 버전 확인
google-chrome –version

# E) 버전에 맞는 크롬드라이버 설치
다운로드 사이트 : https://chromedriver.chromium.org/downloads
다운로드 사이트를 들어가서 자기 환경에 맞는 파일을 우클릭해서 링크 복사를 진행한다.
wget [복사한 링크명]
wget https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.86/linux64/chromedriver-linux64.zip # 해당 파일 다운로드
sudo apt install unzip # unzip 프로그램 설치
unzip chromedriver-linux64.zip # 다운받은 파일 압축해제

 

파이썬 코드에서 uc.Chrome 의 인자값으로 excutable_path = 'chromedriver_path' 추가합니다.
# excutable_path는 chromdriver가 위치한 경로를 적어주면 된다. code와 동일한 경로일 경우 아래처럼 'chromedriver' 만 적어주거나 아예 excutable_path 자체가 없어도 된다.
driver = uc.Chrome(executable_path='chromedriver')

 

오류5) __init__() got an unexpected keyword argument 'version_main' 오류

 

해결 : selenium 버전 다운그레이드

A) 파이썬 코드 수정
driver = uc.Chrome(options=options, version_main=123) # 이전 코드
driver = uc.Chrome(options=options) # 변경 코드

B) 클라우드 서버 수정
클라우드 서버의 selenium을 4.9로 다운그레이드 합니다.

 

오류6) Message: unknown error: Chrome failed to start: exited abnormally.

(unknown error: DevToolsActivePort file doesn't exist)

(The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.) 오류

 

python 코드에서 아래 3개 옵션을 추가하고 적용
	options.add_argument("--headless")
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
	uc.Chrome(options=options)

 

오류7) [WARNING ] discord.gateway: Shard ID None heartbeat blocked for more than 40 seconds. 오류

 

python 코드에서 Heartbeat 간격을 조정합니다.
bot = commands.Bot(command_prefix='!', intents=intents, heartbeat_timeout=60)  # Heartbeat 간격을 60초로 설정

 

6. SSH을 끈 이후로는..

- 작동중이던 프로그램 강제 종료 시키기 (kill -9 명령어)

kill -9 [pid]
kill -9 2630

 

- SSH을 껐다가 킨 경우 다른 세션으로 접속을하여 프로세스가 보이지 않는다. 이런 경우 ps -A 명령어를 사용하여 모든 프로세스 목록 출력으로 python3을 찾을 수 있다.

 

결과물

이제 클라우드 서버를 돌리면서 24시간 공지사항 알리미 봇 개발을 완료하였다!


----------------------------------------------------------------------------------------------------------------------------------------------------------------

봇이 꺼지지 않아 정상작동 되는줄 알았는데 학교 공지사항이 올라와도 봇이 메시지를 보내지 않아 cat nohup.out 명령어를 통해 오류 메시지를 확인했다.

핵심적인 오류 내용은 discord.errors.ConnectionClosed: Shard ID None WebSocket closed with 1000 인 것 같았다.

 

필자의 경우 while True 구문을 사용해서 계속 호출했는데 asyncio.sleep 시간을 30초로 너무 짧게 지정한 것이 문제가 아닐까? 라는 생각이 들었다. 찾아보니 240초 이상으로 지정하는 것이 좋다고 한다. 아래 좋은 자료가 있어 첨부한다.

https://github.com/Rapptz/discord.py/issues/229

 

Bot websocket closes with code 1000 when under http load · Issue #229 · Rapptz/discord.py

My bot has been dying with websocket code 1000 (normal close). Previously the bot would run forever, but after interfacing with another API and doing aiohttp get requests on the same event loop (10...

github.com

 

또 모든 함수들을 비동기 처리로 바꾸었다. (async)