본문 바로가기
잡지식 저장고/크롤링

[KAIST한정]전문연구요원 출퇴근 관리 텔레그램 봇을 만들어보자(2)

by Slate_Knowledge 2024. 1. 16.
728x90

2024.01.17 - [잡지식 저장고/크롤링] - [KAIST한정]전문연구요원 출퇴근 관리 텔레그램 봇을 만들어보자(1)

이전 게시글에서 카이스트 인증 통과 및 전문연구요원 근태관리 사이트에 접속하여 정보를 읽어오는 과정까지를 다뤄보았다. 이번 게시글에서는, 이렇게 얻은 정보를 추가적인 인증 없이 나에게 일정시간마다 텔레그램을 통해 보내는 기능을 설명한다.

1. 오라클 무료 클라우드 인스턴스 할당받기(개인 서버가 있다면 스킵)

항상 동작하면서 일정시간마다 나에게 메시지를 보내는 서비스를 제대로 구동시키려면, 가장 먼저 언제나 켜져있는 서버가 필요하게 된다. 연구실에서 내가 오롯이 쓸 수 있는 서버장비가 구비되어있거나 한다면 편하겠지만 그렇지 않은 경우도 많을테고, 실물 서버는 언제든 전원이 꺼진다거나 인터넷이 끊긴다거나 하는 물리적인 요소들도 고려해주어야 한다. 이때 유용하게 사용할 수 있는것이 클라우드 가상 인스턴스이다.

일반적인 클라우드 서비스 제공업체들은 한시적으로 무료 인스턴스를 제공하거나 아예 그런 항목이 없는데에 반해, 오라클은 회원가입만 하면 작지만 영구적으로 사용할 수 있는 무료 인스턴스를 제공해준다! 본 포스팅에서 다루고 있는 어플리케이션이 그렇게 시스템 자원을 많이 잡아먹는게 아니므로, 오라클 클라우드는 아주아주 적절한 선택지라고 할 수 있다.

1-1. 오라클 클라우드 회원가입하기

먼저 free tier 회원가입을 위해서 아래의 링크로 접속해주자

https://signup.cloud.oracle.com/?sourceType=_ref_coc-asset-opcSignIn&language=ko_KR

 

Oracle Cloud Free Tier Signup

 

signup.cloud.oracle.com

오라클 클라우드 프리티어 가입화면
가입사항 입력

위의 그림과 같이 기본적인 인적사항 및 메일을 기입하고, 메일 확인 링크를 클릭한다. 메일 인증이 끝나면 비밀번호 설정과 맨 아래의 홈 영역을 설정할 수 있다. 필자는 South Korea North(Chuncheon) 으로 홈 영역을 설정해주었다.

이후에 정보를 계속 입력하고, 보증용 카드정보까지 입력하면 아래와 같이 가입이 완료된다.

무료티어 시작

필자는 이미 계정이 있는 상태에서 또 만들려고 해서 에러가 났지만 카드에 문제가 없는 한, 정상적으로 계정이 생성될 것이다.

1-2. 오라클 클라우드 무료 인스턴스 생성하기

이제 계정도 생성하고, 카드도 연동했으니 로그인 페이지로 이동해서 로그인을 해보면 아래와 같은 시작 화면을 볼 수 있다.

오라클 클라우드 시작화면

자 이제 여기서, 메뉴(우상단 가로줄 3개) --> 컴퓨트 --> 인스턴스 의 순서로 클릭해서 아래와 같은 인스턴스 관리 페이지로 넘어간다.

그 다음 인스턴스 생성을 눌러주고

아래로 스크롤하다보면 나오는 "이미지 및 구성" 항목의 "편집" 버튼을 눌러서, 인스턴스 운영체제를 우분투 미니멀로 바꿔준다. 18.04나 20.04 중 아무거나 선택하면 된다.

그 다음 ssh를 통한 접속을 허용하기 위해서 본인이 접속하고자 하는 컴퓨터 콘솔에서

ssh-keygen

명령어와 엔터 연타를 통해 public_key를 생성하고 "Your public key has been saved in <위치>" 라고 안내된 저 파일을 직접 업로드 하거나 public key 입력 옵션을 통해서 입력해준다.

생성된 ssh-key. 전부 긁어서 넣어주면 된다.

그 다음, 인스턴스에 ssh로 접속하면 아래와 같이 성공적으로 접속되는것을 확인할 수 있다.(인스턴스 공개 IP는 대시보드에서 확인)

접속화면

 

2. 텔레그램 봇 만들기

상당히 간단하다. 먼저 telegram PC 버전을 설치한 다음, 핸드폰에도 앱을 설치해주고 사용자 검색에서 "BotFather" 를 검색한다.

그 다음 봇파더에게 /newbot 명령어를 채팅으로 전달하면 아래와 같이 봇 이름 및 username을 정하게 되고 그러면 봇파더가 api 토큰을 생성해준다.

파이썬에서 봇을 통해 채팅을 보내기 위해서는 채팅방의 id가 필요한데 저 첫번째 "t.me/봇이름" 으로 되어있는 채팅방 링크를 클릭해서 봇 방에 들어간 다음 "Start" 버튼을 눌러준다. 그 다음, 사용자 검색에서 "GetMyIDBot" 을 검색 및 추가해서 마찬가지로 "Start" 버튼을 눌러주면 나의 chat ID를 알려준다.

이제 파이썬 콘솔로 돌아가서 현재 환경에

python -m pip install python-telegram-bot==13.11

과 같이 파이썬 텔레그램 API를 설치해주고, 아래와 같이 간단하게 메시지를 보내는 코드를 통해서 메세지 자동 전송이 되는것을 확인할 수 있다.

import telegram
telegram_token = '토큰'
telegram_chat_id = '챗 아이디'
bot = telegram.Bot(token=telegram_token)

bot.sendMessage(chat_id=telegram_chat_id, text='Hello I am Your New Bot!")

3. 파이썬의 apschduler를 이용한 반복작업 스케줄러

기존의 귀찮은 인증 과정을 극복하고 일정 시간마다 정보를 텔레그램으로 보내기 위해서, 파이썬에서 제공하는 apscheduler 라이브러리를 사용할 수 있다. 

3-1. 1분마다 페이지 새로고침을 통해서 로그인 정보 유지하기

대개의 홈페이지들은 사용자 불편을 최소화하기 위해서 웹 상에서의 사용자 작업이 계속되는 경우 로그인 정보를 유지해주는데, 이걸 이용해서 인증 정보를 유지해보도록 하자. 아주 간단하게 1분마다 홈페이지를 새로고침해주면 이 목적을 달성해줄 수 있다.

from apscheduler.schedulers.blocking import BlockingScheduler
from selenium.webdriver.chrome.options import Options
from selenium import webdriver
options = Options()

options.add_argument('log-level=3')
options.add_argument(f"--window-size=1920,1080")
options.add_argument("--hide-scrollbars")
driver = webdriver.Chrome(options=options, executable_path='chromedriver.exe')
driver.get('https://krp.kaist.ac.kr') # 로그인은 이전 포스팅 참고

def refresh():
    driver.refresh()
    
if __name__ == '__main__':
    scheduler = BlockingScheduler()
    scheduler.add_job(refresh, 'cron', minute='1-29,31-59/1')
    scheduler.start()
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            driver.quit()
            scheduler.shutdown()
            break

위 코드와 같이 해주면, 30분과 0분을 제외한 나머지에서 1분마다 홈페이지를 새로고침 해주게 된다. 이때, 0분과 30분을 남겨둔 이유는, 이때 정보를 크롤링 해서 채팅을 전송할 예정이기 때문이다.

3-2. 정보 전송하는 코드를 main 함수로 엮기 

메세지를 전송하는 코드도 위의 Blocking Scheduler를 그대로 사용하면 되는데, 이전 포스팅의 코드를 main() 함수로 묶은 다음 아래와 같이 텔레그램 전송 코드랑 엮으면 아래와 같은 최종 코드가 완성된다. 

import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

from apscheduler.schedulers.blocking import BlockingScheduler

import telegram
telegram_token = '텔레그램토큰'
telegram_chat_id = '텔레그램 chat id'
bot = telegram.Bot(token=telegram_token)

options = Options()

# 브라우저 화면을 띄우지 않고 작업만 수행하도록 headless 옵션을 활성화한다.
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080")
options.add_argument('log-level=3')

c = DesiredCapabilities.CHROME
c['pageLoadStrategy'] = 'none'

driver = webdriver.Chrome(options=options, executable_path='크롬드라이버경로', desired_capabilities=c)
w  = WebDriverWait(driver, 6000)

actionChains = ActionChains(driver)

def refresh():
	# 로그인정보가 유지되도록 1분에 한번씩 브라우저를 새로고침한다
    driver.refresh()

def login():
    # 로그인 하던 부분은 처음에 한번만 실행되게 밖으로 빼기
    driver.get('https://krp.kaist.ac.kr')
    w.until(EC.presence_of_element_located((By.XPATH, '//*[@id="wrap_main"]/div[1]/div/a')))
    driver.find_element(By.XPATH, '//*[@id="wrap_main"]/div[1]/div/a').click()

    w.until(EC.presence_of_element_located((By.XPATH, '//*[@id="IdInput"]')))
    driver.find_elements_by_id('IdInput')[0].click()
    driver.find_elements_by_id('IdInput')[0].send_keys('아이디')
    driver.find_element(By.XPATH, '/html/body/div/div/div[2]/div/div/fieldset/ul/li[2]/input[1]').click()

def main(do_refresh=False):
    if do_refresh:
    	#맨 처음 main을 호출할때는 refresh를 하면 안되기 때문에 인자로 받는다.
        refresh()
    # 아래의 코드 구문을 통해서 우리가 원하는 정보가 담긴 box 가 로딩될때까지 대기한다.
    w.until(EC.presence_of_element_located((By.XPATH, '//*[@id="container"]/div/div[3]')))

    boxes = driver.find_element(By.XPATH, '//*[@id="container"]/div/div[3]').find_elements_by_class_name('box')
    time.sleep(1)
    information = {}
    for box in boxes:
        category = box.text.split('\n')[0].replace(' ', '')
        content = box.find_elements_by_class_name('txt_box')[0].text
        if content == '':
            try:
                content = ' '.join(box.text.split('\n')[1:])
            except:
                pass
        information[category] = content
        if len(box.find_elements_by_class_name('mini_txt_box')) > 0:
            mini_text = box.find_element_by_class_name('mini_txt_box').text
            category = mini_text.split(':')[0].replace(' ', '')
            content = list(map(int, mini_text.split(':')[1:]))
            information[category] = content
    print(information)

    elapse = information['오늘예상누적']
    mystate = [elapse[0], elapse[1]]
    information['오늘예상누적'] = f'{elapse[0]}H {elapse[1]}M'
    remain = information['금주잔여복무시간(목표:40H)'].split('H')
    if remain[1] == '':
        remain[1] = '00'
    # 금주의 복무시간 40시간 중 오늘 복무한걸 제외하면 얼마나 남았는지 계산한다.
    remtime = max(int(remain[0])*60 + int(remain[1].replace('M','').replace(' ','')) - mystate[0]*60 - mystate[1], 0)
    information['남은시간'] = f'{remtime//60}H {remtime%60}M'

    final_text = ''
    for key, info in information.items():
        final_text += f'{key} : {info}\n'

    from datetime import datetime
    now = datetime.now()
	
    if information['퇴근시간'] != '':
    	# 퇴근한 경우 telegram 메시지를 송신할 필요가 없으므로, 콘솔에만 출력한다
        print('퇴근하였습니다.')
    elif remtime != 0 and now.hour >= 10 and information['출근시간'] == '':
    	# 금주의 남은 복무시간이 0이 아니고, 현재 10시가 넘었는데 아직 출근을 하지 않았다면,
        # 출근체크를 독촉하는 문자가 날아간다.
        bot.sendMessage(chat_id=telegram_chat_id, text='오늘은 아직 출근하지 않았습니다!!!')    
    else:
        bot.sendMessage(chat_id=telegram_chat_id, text=final_text)

    return 0
    

    
if __name__ == '__main__':
    login()
    main()
    from functools import partial
    from apscheduler.schedulers.blocking import BlockingScheduler
    scheduler = BlockingScheduler()
    main_job = partial(main, True)
    # 매 시각 30분마다(0분, 30분) 메인 함수를 실행한다(refresh 포함)
    scheduler.add_job(main_job, 'cron', minute='*/30')
    # 나머지 1~29분, 31~59분 사이에는 1분마다 페이지를 새로고침하여 로그인 정보를 유지한다.
    scheduler.add_job(refresh, 'cron', minute='1-29,31-59/1')
    scheduler.start()
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            driver.quit()
            scheduler.shutdown()
            break

완성된 어플리케이션은 아래와 같이 동작하는걸 확인할 수 있다.(데모를 위해서 1분마다 실행되도록 바꿨다)

텔레그램 봇을 사용해서 완성된 주기적 모니터링 봇 실행영상

3-3. Screen 툴을 통해서 백그라운드 프로세스로 만들기

screen 말고도 백그라운드 프로세스를 만들 수 있는 방법은 많지만(nohup 등, 윈도우 제외), screen은 언제든 attach 할 수 있다는 점이 매우 매력적이기 때문에, 본 포스팅에서는 해당 어플리케이션을 사용하도록 한다.

sudo apt-get install screen

과 같이 간단하게 설치해준 다음,

screen -R krpbot

을 실행해주면 새로운 스크린이 생성된다. 거기서 우리가 작성한 bot.py를 실행해 준 다음에 Ctrl + a + d 를 누르면 프로세스가 실행된 채로 빠져나올 수 있다. 그렇게 되면 우리가 원하던 복무 모니터링 툴이 완성된다!

이 모든걸 담은 깃허브 링크는 아래와 같다

다들 행복한 군생활 되기를 바란다.

링크 : https://github.com/dlrudco/MyBlogCodes/blob/master/krp_bot/

 

728x90
반응형

댓글