최근에 지인에게서 연수를 자동으로 넘겨주는 닥터클릭이라는 프로그램에 대해 듣게 되었다.
일단 터미널이 열리는 것부터 뭔가 파이썬 냄새가 났다.
그래서 나도 한번 만들어보기로 했다.
이름은 OPEN AI의 이름을 딴 오픈 클릭으로 작명해 보았다.
나 또한 이 프로그램을 만드는 이유는 닥터클릭 제작자와 다르지 않다.
손목이 아파 마우스나 키보드를 사용하기 힘든 이들을 위해 제작한다.
연수를 자동으로 넘겨주는 동안, 사용자는 앞에서 자리를 지키며 시청만 하면 된다.
물론 사용은 자유지만, 사용에 대한 책임은 본인에게 있음을 인지하길 바란다.
파이썬 라이브러리 중 Selenium이라는 라이브러리를 이용할 것이다.
브라우저를 자동으로 조작할 수 있게 만들어주는 툴이다.
셀레니움으로 접속 -> 사용자 로그인 -> 연수 실행 -> 자동으로 넘겨주기 순으로 진행하면 된다.
xpath나 css 선택자로 요소들을 찾기만 하면 구현은 쉽다.
현재 버전은 1.0.0, 암호는 earthscience.kr 이다.
사용법은 무척 간단하다.
먼저 압축을 풀고 실행을 하면 검은 창이 뜬다.
이 창을 터미널이라고 하는데 해킹이 아니니까 너무 걱정 말자.
브라우저서 뜨는 팝업들은 모두 꺼준 뒤에 로그인을 한다.
로그인 한 뒤 검은 창에서 다시 엔터를 치거나 아무거나 입력하면 수강중인 연수가 나온다.
연수에 맞게 번호를 입력하고 엔터를 치면...
수강하는 과목에서 제일 마지막에 들은 수업으로 이동하고 영상이 계속 넘어가게 된다.
exe파일 실행이 두려운 분들은 파이썬에 아래의 코드를 복사, 붙여넣기 해서 이용해도 된다.
코드 전문을 대충 쓴 주석과 함께 올린다.
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import UnexpectedAlertPresentException, NoSuchWindowException, NoSuchElementException, ElementClickInterceptedException
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
import time
import traceback
from requests import post
import json
import os
# 셀레니움 driver 정의
try :
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
except :
driver = webdriver.Chrome()
# 요소 찾고 클릭하기. 루프를 돌며 요소가 클릭 가능할 때 까지 장시간 대기함.
# 웹페이지 로딩으로 인한 오류 방지를 위해 씀
def find_and_click(element, num=0):
for _ in range(10):
try:
if isinstance(element, str):
somethings = driver.find_elements(By.XPATH, element)
if somethings:
something = somethings[num]
something = WebDriverWait(driver, 10).until(EC.element_to_be_clickable(something))
something.click()
return True
else:
WebDriverWait(driver, 10).until(EC.element_to_be_clickable(element))
element.click()
return True
except:
time.sleep(0.5)
if isinstance(element, str):
print(f"클릭할 수 없음 : {element}")
else: print(element.get_attribute('innerText'))
return False
# 연수 사이트 접속하기
def getWebAndSelLec():
driver.get("https://www.neti.go.kr/")
driver.maximize_window()
windows = driver.window_handles
if len(windows) > 1:
driver.switch_to.window(windows[1])
find_and_click('//span[contains(text(),"5일")] / ancestor::button[1]')
driver.switch_to.window(windows[0])
input('로그인 후 아무키나 입력해주세요. 팝업창은 닫아주세요! : ')
userNmae = driver.find_element(By.XPATH, '//div[@class="my_profile"]/span/em').text
cardTops = driver.find_elements(By.XPATH, '//ol[@id="mainLoling_99"]/li')
print(f'{userNmae}님 반갑습니다.')
if len(cardTops) == 0:
print('현재 들을 수 없는 연수가 없습니다. 연수를 신청 후 .')
print(f'지금 듣고 계신 수업은 총 {len(cardTops)}개 있습니다.')
cardDct = {}
for i, cardTop in enumerate(cardTops):
title = cardTop.find_element(By.XPATH, './/a[@class="title"]').get_attribute('innerText')
cardDct[i] = title
print(i, ':' ,title)
while True:
try:
selCardTop = int(input('어떤 연수를 들으시겠어요? : '))
if 0 <= selCardTop < len(cardTops):
break
print('연수 번호가 올바르지 않습니다.')
except:
print('숫자를 입력해주세요.')
userNmae = driver.find_element(By.XPATH, '//div[@class="my_profile"]/span/em').text
cardTops = driver.find_elements(By.XPATH, '//ol[@id="mainLoling_99"]/li')
selCardHomeBtn = cardTops[selCardTop].find_element(By.XPATH, './/a[contains(text(), "강의실 홈")]')
print(selCardTop,"번 ",cardDct[selCardTop][0:27], "...", '을 수강합니다.')
find_and_click(selCardHomeBtn)
# 제일 마지막 수강 내역을 찾아 영상 재생하기
def getTopLec():
print('가장 마지막에 수강한 부분부터 진행합니다.')
while True:
time.sleep(1)
windows = driver.window_handles
if len(windows) > 1:
driver.switch_to.window(windows[1])
break
while True:
tabList = driver.find_elements(By.XPATH, '//ul[@role="tablist"]')
if tabList:
break
time.sleep(0.5)
tabList = driver.find_element(By.XPATH, '//ul[@role="tablist"]')
lecCategoryBtn = tabList.find_element(By.XPATH, './/a[contains(text(), "학습목록")]')
find_and_click(lecCategoryBtn)
learnList = driver.find_element(By.XPATH, '//ul[contains(@class,"content_list")]/li')
if not learnList.is_displayed():
driver.find_element(By.XPATH, '//li/a[@class="end list_title listTitle01"]').click()
learnBtns = driver.find_elements(By.XPATH, "//div[@class='learn_con']/div[text()='학습중' or text()='학습전' or text()='학습하기'] // following-sibling::a[@class='btn_learning_list']")
learnTitleBtns = driver.find_elements(By.XPATH, '//a[@class="cnts_title"][span and not(span[contains(., "학습완료")])]')
driver.execute_script("arguments[0].scrollIntoView(true);", learnTitleBtns[0])
time.sleep(0.5)
if not learnBtns[0].is_displayed():
find_and_click(learnTitleBtns[0])
driver.execute_script("arguments[0].scrollIntoView(true);", learnBtns[0])
time.sleep(0.5)
find_and_click(learnBtns[0])
time.sleep(1)
find_and_click('//button[@title="동영상 재생하기"]')
print('동영상 재생 버튼을 클릭합니다.')
# 연수가 종료될 때까지 루프를 돌며 영상재생하기
def lectureLoop():
while True:
preProhibitDiv = driver.execute_script("return document.getElementById('prohibitDiv').style.width")
stopCount = 0
isNextLearningClass = True
while True:
time.sleep(2)
playerBox = driver.find_element(By.XPATH, "//div[contains(@class,'player_box')]")
quizShowBtn = driver.find_elements(By.XPATH, '//p[contains(text(),"완료") and contains(text(),"퀴즈")] /ancestor::div[1]')
if len(quizShowBtn) > 0:
if "block" in quizShowBtn[0].get_attribute('style'):
find_and_click(quizShowBtn[0])
playState = driver.find_element(By.CSS_SELECTOR, '.class_list .class_list_box li.play').text.split('\n')[1]
nextProhibitDiv = driver.execute_script("return document.getElementById('prohibitDiv').style.width")
if nextProhibitDiv == preProhibitDiv and playState == "재생중":
stopCount += 1
if stopCount >5:
driver.find_elements(By.XPATH, "//li[@id='studyPageLi']//div[contains(@class,'fll flr') or contains(@class, 'fll pre')]//a")[0].click()
continue
if '완료' in playState or "flex" in playerBox.get_attribute("style"):
break
if '완료' in playState and isNextLearningClass:
nextLearningClass = driver.find_elements(By.XPATH, "//li[@id='studyPageLi']//div[contains(@class,'ing') or contains(@class,'pre')]/b/a")
if len(nextLearningClass) == 0:
try :
driver.find_element(By.XPATH, '//a[contains(text(), "다음 차시")]').click()
except :
isNextLearningClass = False
print('다음 차시가 없습니다.')
continue
driver.execute_script("arguments[0].scrollIntoView(true);", nextLearningClass[0])
nextLearningClass[0].click()
continue
preProhibitDiv = nextProhibitDiv
find_and_click('//div[@id="next-btn"]/button[@title="다음 페이지"]')
time.sleep(0.5)
popupText = driver.find_elements(By.XPATH, '//p[contains(text(), "마지막 목차")]')
if len(popupText) > 0:
print('마지막 목차입니다.')
break
print('모든 연수를 수강했습니다. openClick을 종료합니다.')
driver.quit()
if __name__ == '__main__':
getWebAndSelLec()
getTopLec()
lectureLoop()
예전에 인스타에서 좋아요를 눌러주는 코드를 짰을 때, 꽤 많은 사람들이 내 블로그를 방문해 주었다.
몇몇 개발자들이 이걸 돈받고 파는걸 보며 이게 그 정도인가 싶었는데, 유로로 잘 운영되고 있는걸 보니 그 정도가 맞았나보다.
앞으로도 계속 유지 보수를 할 계획이지만 자녀가 어려서 빈번하게는 어려울 수도 있다.
어쨌든 유용하게 쓰길 바란다.