너무 조잡한 코드라 비공개할까 한다..ㅋㅋㅋ

지금은 셀레니움과 requests를 적절히 사용해 속도를 대폭 높였고

정리가 좀 됐기 때문에..

 

몇달전 EBS 온라인클래스로 수업을 진행했을 당시엔

아침에 일어나 출석체크 하는것이 너무나도 귀찮았었다.

(지금도 고3을 제외한 나머지중 아직도 온라인 수업을 하는 학년이 있을것이다)

 

출석체크는 매일 올라오는 출결 게시글에 댓글을 달아야 했다.

수업은 점심때쯤부터 듣더라도 출석체크때문에 8시에 일어나 출석체크를 해야만 하는것

 

그래서 크롬 기반 자동 출석 프로그램을 파이썬으로 만들었다.

코드는 상당히 조잡하며 정리가 되어있지 않아 보기 힘들지만 내가 잊어버릴까봐 올린다.

메모장이라는 말

 

영상도 올리겠다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#-*- coding: utf-8 -*-
import os, sys, time, datetime, schedule, pyperclip, threading
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
 
 
# pyinstaller 컴파일 예제: pyinstaller --clean --icon=ebs.ico --onefile --add-binary "chromedriver.exe";"." EAC_v2.py
############################################## .ui 파일을 pyinstaller에 포함하기 위함
def resource_path(relative_path):   
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
 
############################################## 스레드
class ThreadClass_Main(QtCore.QThread): 
    def __init__(self, parent = None): 
        super(ThreadClass_Main,self).__init__(parent)
    def run(self):
        mainProgress()
 
threadclass_Main = ThreadClass_Main() # 스레드 전용 .start 쓰기위해
 
class ThreadClass_Scheduler(QtCore.QThread): 
    def __init__(self, parent = None): 
        super(ThreadClass_Scheduler,self).__init__(parent)
    def run(self):
        schedule.every().day.at(timeEdit).do(threadclass_Main.start)   # 스케줄러
        while True:
            schedule.run_pending()
            time.sleep(1)
 
threadclass_schedule = ThreadClass_Scheduler()
 
############################################## 메인
def mainProgress():
    loginURL="https://hoc5.ebssw.kr/sso/loginView.do?loginType=onlineClass&hmpgId=soraehigh302"
    if getattr(sys, 'frozen'False):  
        chromedriver_path = os.path.join(sys._MEIPASS, "chromedriver.exe")
        driver = webdriver.Chrome(chromedriver_path)
    else:
        driver = webdriver.Chrome()
 
    def copy_input(xpath, input):   # 클립보드.복사 + 액션체인.붙여넣기 설정
        pyperclip.copy(input)
        driver.find_element_by_xpath(xpath).click()
        ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
        time.sleep(1)
    global usrID, usrPW
    if not(usrID and usrPW):
        usrID = "아무것도 입력되지 않았을 시"
        usrPW = "이곳에 입력된 계정정보로 로그인"    # 프로그램 사용자가 1명뿐일때 매번 아이디와 비밀번호를 입력하기 귀찮을것
        
    driver.get(loginURL)
    copy_input('//*[@id="j_username"]', usrID)
    copy_input('//*[@id="j_password"]', usrPW)
    
    driver.find_element_by_xpath('//*[@class="img_type"]').click()  # 로그인버튼 클릭
 
    print()
    time.sleep(1)
    try:
        popup1 = driver.switch_to.alert  # 팝업창 예외처리
        if("새롭게 로그인 하시겠습니까?" in popup1.text):
            popup1.accept()
        elif("아이디를 입력해주세요" in popup1.text):
            print("아이디 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        elif("비밀번호를 입력해주세요" in popup1.text):
            print("비밀번호 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        elif("잘못된" in popup1.text):
            print("아이디 또는 비밀번호 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        else:
            print("알려지지 않은 오류: {}\n#### 출석 실패 ####".format(popup1.text))
    except:
        pass
    
    time.sleep(1)
    
    ################################ 출결 '게시판' 색출
    menuList_txt = []
    menuList_href = []
    menuList_temp = driver.find_elements_by_xpath('/html/body/div[2]/div[2]/div[2]/nav/ul/li[3]/div/a'# 해당 full xpath 하위경로들 리스트화
 
    for i in range(len(menuList_temp)):
        menuList_txt.append(menuList_temp[i].get_attribute('text'))     # text 메뉴 이름 리스트화
        menuList_href.append(menuList_temp[i].get_attribute('href'))    # href 메뉴 주소 리스트화
        
    for lst in range(0,len(menuList_txt),3):
        try:
            print("{:<10}{:<10}{}".format(menuList_txt[lst], menuList_txt[lst+1], menuList_txt[lst+2]))
        except IndexError:
            try:
                print("{:<10}{}".format(menuList_txt[lst], menuList_txt[lst+1]))
            except IndexError:
                print(menuList_txt[lst])
        
    print("\n{:<13}{:>2}개 ]".format("[ 출결방 메뉴 개수: "len(menuList_txt)))
 
    try:
        menu_for_srch = dateEdit
        menuNum = menuList_txt.index(menu_for_srch)
    except:
        splited = menu_for_srch.split()    #공백 없애기
        fixed = "".join(splited)
        menuNum = menuList_txt.index(fixed)
    driver.get(menuList_href[menuNum])
    time.sleep(1)
    
    ################################ 출결 '게시글' 색출
    pageLen = driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[2]/div[1]/a')
    print("{:<14}{:>2}개 ]".format("[ 총 페이지 개수: "len(pageLen)))
    pageLen_for_Range = len(pageLen) + 1
 
    postList_txt = []
    post_for_srch = "조례"
    for m in range(1, pageLen_for_Range):
        driver.find_element_by_xpath('//*[@id="swedu_listL"]/div[2]/div[1]/a[{}]'.format(m)).click()
        time.sleep(2)
        print("\n\n==== 페이지 {} 진입 ====".format(m))
 
        postList_txt.clear()
        postList_temp = driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr/td[1]/a/span')
        for j in range(len(postList_temp)):
            postList_txt.append(driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr/td[1]/a/span')[j].text)
 
        for postlst in range(0,len(postList_txt),3):
            try:
                print("{:<17}{:<17}{}".format(postList_txt[postlst], postList_txt[postlst+1], postList_txt[postlst+2]))
            except IndexError:
                try:
                    print("{:<17}{}".format(postList_txt[postlst], postList_txt[postlst+1]))
                except IndexError:
                    print(postList_txt[postlst])
 
        print("\n[ 확인 단계 ]")
        for k in range(0len(postList_txt)):
            tempvalue = postList_txt[k].count(post_for_srch)
            print("{} --- 확인중".format(postList_txt[k]))
            if tempvalue == 1:
                print("\n[ 해당 게시글이 출석 게시글로 확인됨 ]")
                postNum = k+1
                break
    print("[ 색출된 게시글 인덱스: {} ]".format(postNum))
 
    driver.find_element_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr[{}]/td[1]/a/span'.format(postNum)).click()
    time.sleep(2)
 
    print("[ 댓글 입력 중 ]")
    temp = driver.find_element_by_xpath('//*[@name="cmmntsCn"]')    # 댓글창 클릭
    temp.click()
    time.sleep(2)
    temp.send_keys(toWrite)
 
    driver.find_element_by_xpath('//*[@class="submit"]').click()    # 댓글등록버튼 클릭
    try:
        popup2 = driver.switch_to.alert  # 팝업창 확인
        popup2.accept()
    except:
        pass
 
    print ("\n#### 출석 완료 ####\n완료시각: [{}]".format(datetime.datetime.now()))
 
############################################## GUI 생성
form = resource_path('UI_v3.ui')
try:
    form_class = uic.loadUiType("UI_v3.ui")[0]
except:
    form_class = uic.loadUiType(form)[0]
 
class MyWindow(QDialog, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setFixedSize(342,236# 화면크기조정 제한
 
        self.pushButton_timeset.clicked.connect(self.btnEvent_timeset)   # 버튼 이벤트
        self.pushButton_now.clicked.connect(self.btnEvent_now)   # 버튼 이벤트
    def btnEvent_timeset(self):
        global usrID, usrPW, dateEdit, timeEdit, toWrite  # 계정정보와 댓글내용과 주소 저장
        usrID = self.lineEdit_id.text()
        usrPW = self.lineEdit_pw.text()
        toWrite = self.textEdit_comment.toPlainText()
        tempdateEdit = self.dateEdit.date() # dateEdit 에서 QDate형태 객체 반환
        dateEdit = tempdateEdit.toString("M월 d일"# QDate형태 객체 문자열로 출력
        temptimeEdit = self.timeEdit_set.time() # timeEdit 에서 QTime형태 객체 반환
        timeEdit = temptimeEdit.toString("hh:mm:ss")    # QTime형태 객체 문자열로 출력
        print("ID: {}\nPW: {}\nComment: {}\nDate to attend: {}\n\n#### Run at: {} ####".format(usrID, usrPW, toWrite, dateEdit, timeEdit))
        threadclass_schedule.start()    # 스레드 시작
        
    def btnEvent_now(self):
        global usrID, usrPW, dateEdit, toWrite  # 계정정보와 댓글내용과 주소 저장
        usrID = self.lineEdit_id.text()
        usrPW = self.lineEdit_pw.text()
        toWrite = self.textEdit_comment.toPlainText()
        tempdateEdit = self.dateEdit.date() # dateEdit 에서 QDate형태 객체 반환
        dateEdit = tempdateEdit.toString("M월 d일"# QDate형태 객체 문자열로 출력
        print("ID: {}\nPW: {}\nComment: {}\nDate to attend: {}\n\n#### Run Immediately ####".format(usrID, usrPW, toWrite, dateEdit))
        threadclass_Main.start()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()
 
cs

 

 

 

.py > .exe 컴파일 & .ui 파일 포함시키는 방법

명령프롬프트(cmd)에서 cd 명령어를 통해 해당 .py파일이 있는 위치로 이동한 후

코드 상단 예제와 같이 pyinstaller를 통해 컴파일을 진행한다.

 

pyinstaller --clean --icon=ebs.ico --onefile --add-binary "chromedriver.exe";"." EAC_v2.py

 

컴파일이 완료되면 .py파일이 있는 디렉토리에 dist 폴더가 생기고 그 안에 .exe파일이 생긴다. 아직 건들지 말자.

이때 중요한점은 .py파일과 같은 이름의 .spec 파일이 생성되는데, 메모장으로 편집을 해 주어야 한다.

datas=[('UI_v3.ui', '.')] 로 바꿔준다. 위 코드에서 불러오는 UI파일 이름이 UI_v3.ui 이다.

마지막으로 cmd에 방금 편집한 .spec 파일도 컴파일해준다.

 

pyinstaller EAC_v2.spec

 

그럼 ui파일까지 포함된것이다. dist폴더의 실행파일은 이제 다른 윈도우 운영체제 컴퓨터에서도 작동한다.

 

참고한 사이트 목록

기본적인 기능 키워드: 셀레니움, 셀레니움 alert, 스케줄러, paperclip, 셀레니움 로그인 등등

 

threading 라이브러리를 통한 스레드 구현 (가장 유용했던 사이트는 못찾았다)

>https://niceman.tistory.com/138

 

pyqt를 이용한 UI

>http://pythonstudy.xyz/python/article/108-PyQt-QtDesigner

>https://nanite.tistory.com/53

>https://wikidocs.net/35478

 

pyinstaller를 이용한 컴파일과 ui 포함방법

>https://wikidocs.net/21952

>https://m.blog.naver.com/PostView.nhn?blogId=yhs11145&logNo=221450157087&proxyReferer=https:%2F%2Fwww.google.com%2F

>https://pagedown.n-e.kr/11

>https://stackoverflow.com/questions/59719083/pyinstaller-ui-files?noredirect=1&lq=1

'hobby > 코딩' 카테고리의 다른 글

비주얼 스튜디오 2017 프로페셔널 웹인스톨러  (0) 2020.05.11
아두이노 다크 테마 적용법  (0) 2019.12.31

OpenCV 강의에서 작성일 기준 최신버전인 vs2019 대신 vs2017 바탕으로 설명되어있어서

혹시모를 호환성 문제를 방지하고자 버전을 맞춰주는게 맞다고 판단해 설치파일을 찾아 공유한다.

 

아래는 vs2017 웹 인스톨러 파일이다.

vs2017_Community.exe
1.20MB
vs2017_Professional.exe
1.20MB

설치 후 로그인을 통해 제품인증을 하여 사용하는것을 적극 권장한다.

'hobby > 코딩' 카테고리의 다른 글

파이썬 온라인클래스 자동 출석 프로그램  (1) 2020.08.10
아두이노 다크 테마 적용법  (0) 2019.12.31

아두이노에도 다크 테마가 있다.

물론 유저 커스터마이징

 

블루라이트 차단으로 눈의 피로 줄이고

멋있어보이는 시각효과는 덤

 

적용법은 매우 간단하다.

https://github.com/jeffThompson/DarkArduinoTheme

 

jeffThompson/DarkArduinoTheme

A dark theme for the Arduino IDE. Contribute to jeffThompson/DarkArduinoTheme development by creating an account on GitHub.

github.com

위 사이트(깃헙)에 들어가서 파일을 받고 압축을 해제한 후 나오는 theme 폴더를

Arduino IDE가 설치된 폴더(보통 "C:\Program Files (x86)\Arduino") 속 lib 폴더 안에 덮어씌운다.

물론 기존 폴더는 백업을 권장한다.

 

기존 theme 폴더 이름을 바꿔놓고 다크테마 theme을 넣어주면 아래와 같은 현상이 일어나지 않는다.

이러면 다크 테마 적용 끝인데, 필자의 경우 괄호를 강조하는 효과 색상만 다크 테마 적용이 안되길래 찾아보니깐

theme 폴더 속 syntax 폴더 안에 있는 default.xml 파일을 지워버리던 이름만 바꾸던 한 후에

같이 있던 dark.xml 파일을 default.xml로 이름을 바꿔주면 해결된다고 한다.

 

아래는 다크 테마가 적용된 모습.

눈을 보호하도록 하자