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

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

by Slate_Knowledge 2024. 1. 16.
728x90

 

Introduction (왜 만들었는지 설명하는 잡설, 스킵해도 됨)

필자는 카이스트에서 전문연구요원으로 2021년도에 편입하여 다가오는 2024년도 2월말에 소집해제가 되는 말년 이등병 소총수이다.

전문연구요원 굳건이. - 출처 : 나무위키

전문연구요원으로서 복무하는동안 출퇴근은 아주 중요한 관리 안건이었다. 2022년 전까지는 10AM 까지 출근을 깜빡하고 찍지 않으면 지각 페널티를 물었고, 이외에는 하루 8시간을 잘 채우면 끝나는 것이라서 어떻게 보면 관리 난이도 자체는 낮았다고 할 수 있겠으나, 2022년도 자율출퇴근 이후에는 주 40시간 중에 내가 오늘 얼마나 했고, 앞으로 얼마나 남았는지를 분 단위로 계산하는게 상당히 고역이었다.

이미지출처 : http://www.catholicnews.co.kr/news/articleView.html?idxno=23028

더욱이, 자율출퇴근 초창기에는 시스템 상에서 지원해주는 기능이 몇 없었기 때문에 필자는 이를 자동으로 해줄 수 있는 봇의 필요성을 느끼고 간단히 개발에 착수하였다.

누군가 왜 굳이 텔레그램 봇이냐고 묻는다면 : 1. 카카오톡 챗봇은 토큰이 소멸시효가 있어서(심지어 꽤 짧다) 이걸 정기적으로 갈아끼워줘야하는데 이것도 역시 자동화까지 성공은 했으나 굉장히 복잡하고 알 수 없는 에러가 많이 난다. 2. gmail API를 이용하여 메일로 보내는것도 가능하지만 일반적으로 하루에 수십통씩 메일이 오는 필자 입장에서는 여기에 30분 주기로 정크 메일을 추가 생성하기 싫었다. 3. 텔레그램 봇은 아주 만들고 유지하기 쉽고 카톡 자리도 차지하지 않으며 메일 칸도 소비하지 않는다 = 텔레그램으로 결정!

텔레그램의 유일한 단점은 최근 여러 이슈로 인해서 세간의 인식이 매우, 매우매우 안 좋다는 것인데 거리낄 것 없으면 걍 봇만 덩그러니 있는 톡방을 보여주자....(신규 가입 알림들과 함께)


Methodology

본 포스팅을 통해서 공개하는 봇은 크게 두 파트로 이뤄졌다고 보면 되는데, 1) 크롤링을 통해 학교 복무관리 홈페이지(krp.kaist.ac.kr)에서 지속적으로 현재 내 상태(출퇴근 상황 및 금주의 복무 잔여시간)를 파싱하는 parser와 2) 파싱된 정보를 텔레그램을 통해서 나에게 알려주는 notification 모듈 로 나눌 수 있겠다.

본 포스팅에서는 이 중 parser 모듈을 우선적으로 다루고, 이어지는 포스팅에서 텔레그램 봇 만드는법부터 파이썬으로 메세지 보내기 까지를 다룬다.

** 중요 : 만약 오라클 무료 클라우드를 사용해서 개인 서버나 컴퓨터 없이도 진행하고 싶은 독자들은 아래 다음 포스팅의 오라클 클라우드 인스턴스 생성 파트를 먼저 수행하고 돌아오기 바란다.

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

 

0. 설치 

먼저, 크롤링을 수행하기 위해서 크롬드라이버와 셀레니움을 설치해준다. 

제일 먼저 크롬드라이버인데, 윈도우의 경우에는 크롬이 대부분 깔려있으므로 아래 링크에서 다운로드 받을 수 있는 exe 파일을 원하는 곳에 받아준다. 해당 링크에서는 chrome 과 chromedriver 모두를 제공하고 있기 때문에, chromedriver인지, window 인지를 꼭 확인하자. 다른 설치 안내 포스트들을 보면 환경변수 세팅도 하고 그러는데 크롬은 버전이 빠르게 바뀌는데다가 코드 분석을 하다보면 알겠지만 필요 없으므로 하지말자. 그냥 내 컴퓨터에 설치된 크롬과 크롬 드라이버의 버전이 같은지만 확인.

링크 : https://googlechromelabs.github.io/chrome-for-testing/

 

Chrome for Testing availability

chrome-headless-shellmac-arm64https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/mac-arm64/chrome-headless-shell-mac-arm64.zip200

googlechromelabs.github.io

chrome driver 다운로드 링크들. 링크를 복사해서 주소창에 적거나 콘솔에서 wget으로 받을 수 있다.
동일한 링크에서 chrome 자체의 설치파일도 제공한다.

우분투(필자는 레드햇이나 기타 리눅스는 모릅니다.)의 경우에는 대개 firefox만 깔려있고 크롬이 설치 안되어있는 경우가 많으니, 아래의 명령어를 터미널에서 차례로 입력하여 설치를 수행해준다.

wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb

그런 다음, 위의 링크에서 Stable 밑의 linux64에 해당하는 다운로드 링크를 사용해서 크롬 드라이버를 받아준다.

크롬드라이버까지 다 설치를 마쳤다면, 그 다음은 python에서 selenium 을 설치해주자. pip로 설치해도 되고, 아나콘다가 설치되어 있다면 conda를 사용해도 된다(권장).

pip install selenium

or

conda create -n krpbot python=3.9
conda activate krpbot
conda install selenium

셀레니움을 설치하고 나면 아래와 같이 리눅스 및 윈도우 상의 파이썬 콘솔에서 제어가능한 크롬 브라우저를 실행시킬 수 있다.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

options.add_argument('log-level=3')

driver = webdriver.Chrome(options=options, executable_path='크롬드라이버 exe 경로')

크롬드라이버와 파이썬 셀레니움을 통해서 제어가능한 크롬드라이버 열기

이제 그 다음 과정들을 실행하기 위해서 import 해야하는 모듈들을 포함해서 아래와 같이 준비하면 된다.

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

options = Options()

# 처음 테스팅할때는 아래 주석이 있는 상태로 진행하고, 이후에는 주석을 푼다.
# 주석을 풀면 화면이 표시되지 않는다.
# 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)

1. 인증절차

최근 들어서(이것도 몇년 됐지만),  카이스트 내부 시스템에 접속하려면 무조건적으로 포탈 로그인을 해야한다. 단순히 아이디와 비밀번호만 있으면 됐던 시절과는 다르게 이제는 별도의 인증 시스템을 통과해야하는데, 여기에는 간편인증과 비밀번호 인증 후 2중 인증 방식이 있다.

아이디를 넣고, 간편인증이나 비밀번호 인증을 선택할 수 있다. 비밀번호 인증을 선택하면 비밀번호 입력칸이 생성된다.
비밀번호 인증을 선택하고 비밀번호를 맞게 입력하면 나오는 2중 인증 창

본 포스팅에서는 심신의 안정을 위해서 그냥 간편 인증(휴대폰 Kaist Auth 어플로 인증하기)을 사용할 것이지만, 필자는 이전에 외부 메일을 통해서도 구축한 적이 있다. 간단한 방식은 아래와 같다.

  1. 외부 메일로 인증번호를 보내도록 한다.
  2. 외부 메일 계정에 outlook으로 메일 전달을 시켜놓는다.
  3. windows outlook api에서 메일함 업데이트 및 신규 메일을 쿼리하고, 여기서 인증번호를 알아낸다
  4. 인증번호를 입력한다.

위와 같이 하면 인증 과정을 전부 자동화 가능한데, 윈도우에서만 가능한데다가 아웃룩이 상당히 느리고 무거우므로 어지간하면 간편인증으로 하자. 

아래서부터는 간편인증을 포스팅의 독자가 할 수 있다고 가정하고 진행하겠다.

간편인증을 위해서는, 위의 화면에서 두 가지 작업을 수행해야 함을 알 수 있다. 

  1. 아이디 입력창에 나의 아이디를 기입하기
  2. 아이디가 기입되었으면, 간편인증 버튼을 누르기

이 두가지는 크로미움에서 아주아주 간단하게 수행할 수 있는 과정이다. 

수문장 페이지

페이지 접근과 그 이후 나오는 수문장 페이지는 아래와 같이 넘어가주고,

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()
# 아래의 코드 구문을 통해서 우리가 원하는 정보가 담긴 box 가 로딩될때까지 대기한다.
w.until(EC.presence_of_element_located((By.XPATH, '//*[@id="container"]/div/div[3]'))

이러면 본인의 핸드폰에서 인증요청이 뜰거고, 인증을 완료하면 다음 화면으로 넘어간다.

여기까지의 데모를 아래에서 확인하자.

인증은 휴대폰으로 진행하였다.

2. 가져올 수 있는 정보 확인하기

우리가 원하는 정보가 담긴 krp.kaist.ac.kr 상의 근태현황 칸

위의 그림과 같이, 우리는 특정한 정보만 쿼리해서 가져올 수 있으면 만사형통이다. 따라서, 아래의 코드와 같이 근태현황을 담은 박스만 잘 떼어다가 안에 있는 텍스트를 해석하기만 하면 되겠다.

먼저 근태현황을 담은 전체 박스는 다음과 같이 불러오고

boxes = driver.find_element(By.XPATH, '//*[@id="container"]/div/div[3]').find_elements_by_class_name('box')

그러면 아래와 같이 box들의 리스트를 얻을 수 있다.

이 각각의 박스들은 순서대로 '잔여휴가일수' 부터 '퇴근시간' 까지의 정보를 담고 있다.

이제 이 정보들을 잘 파싱해서 정리하고 금주 잔여 복무시간에서 오늘 예상 누적을 빼 지금 퇴근하면 금주에 남는 복무시간을 추가로 표시하도록 해보자.

boxes = driver.find_element(By.XPATH, '//*[@id="container"]/div/div[3]').find_elements_by_class_name('box')
driver.implicitly_wait(1)
information = {}
for box in boxes:
    category = box.text.split('\n')[0].replace(' ', '')
    content = box.find_elements_by_class_name('txt_box')[0].text
    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]
        content = list(map(int, mini_text.split(':')[1:]))
        information[category] = content

elapse = information['오늘예상누적']
mystate = [elapse[0], elapse[1]]
remain = information['금주잔여복무시간(목표:40H)'].split('H')
if remain[1] == '':
    remain[1] = '00'
remtime = max(int(remain[0])*60 + int(remain[1].replace('M','').replace(' ','')) - mystate[0]*60 - mystate[1], 0)
information['지금 퇴근하면 남은시간'] = [remtime//60, remtime%60]

print(information)

이러면 이제 코드 실행 한번에 원하는 정보를 프린트 할 수 있다.

그런데 문제가 있다, 기껏 인증까지 해야하는데 위 코드는 한번 실행하면 정보를 툭 뱉어버리고. 이 문제를 해결하기 위해서, 다음 포스팅에서는 위에서 언급한 텔레그램 봇과 오라클 클라우드 무료 인스턴스, 그리고 background 데몬 프로세스를 이용해 한번 켜두면 지속적으로 근무시간 모니터링을 수행해주는 형상을 완료하는 내용을 다루도록 하겠다

728x90
반응형

댓글