너무 조잡한 코드라 비공개할까 한다..ㅋㅋㅋ
지금은 셀레니움과 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(0, len(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
pyinstaller를 이용한 컴파일과 ui 포함방법
>https://stackoverflow.com/questions/59719083/pyinstaller-ui-files?noredirect=1&lq=1
'hobby > 코딩' 카테고리의 다른 글
비주얼 스튜디오 2017 프로페셔널 웹인스톨러 (0) | 2020.05.11 |
---|---|
아두이노 다크 테마 적용법 (0) | 2019.12.31 |