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

지금은 셀레니움과 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

Ender 5 Plus는 비싸기도 하고 구매 후 커스터마이징에 돈이 더 들것이기 때문에

Pro 사서 남는 돈으로(남을까?) 업그레이드를 하는것이 좋겠다고 결론을 내렸다.

 

- Ender 5 Pro 본체

https://ko.aliexpress.com/item/4000479849113.html?spm=a2g0o.productlist.0.0.437a1bd0GaB6XA

Pro 버전은 2019년 이후 TMC 장착된 버전 1.1.5 사일런트 메인보드, 메탈 익스트루더,

카프리콘사의 테프론 튜브가 기본 포함되어 나오기 때문에 관련 부품은 구매가 필요없을듯 하다.

 

 

- Creality PEI 베드

https://www.aliexpress.com/item/4000180731747.html?spm=a2g0o.productlist.0.0.58704bdeszpJMC&algo_pvid=06c6c762-4cd0-49ef-a7ec-9f7a10069edb&algo_expid=06c6c762-4cd0-49ef-a7ec-9f7a10069edb-0&btsid=61c63e49-120d-4881-bb48-007e6d6a72f8&ws_ab_test=searchweb0_0,searchweb201602_5,searchweb201603_53

에폭시 베드도 좋다고 하는데 좀 더 찾아볼 필요 있음

 

 

- Creality BLTouch

https://www.aliexpress.com/item/32903813956.html?spm=a2g0o.productlist.0.0.2ff746426J0H2J&algo_pvid=e344ec96-73d8-4a32-a702-8a712746e28d&algo_expid=e344ec96-73d8-4a32-a702-8a712746e28d-9&btsid=aebbc5b5-ce8e-4f2f-8e0e-de4e9a735923&ws_ab_test=searchweb0_0,searchweb201602_5,searchweb201603_53

 

 

- Mellow MK8 노즐

https://www.aliexpress.com/item/32957162791.html?spm=a2g0o.productlist.0.0.5f2b135co564Mj&s=p&algo_pvid=8c6ea149-97be-4e2e-9ebb-1cc409373c34&algo_expid=8c6ea149-97be-4e2e-9ebb-1cc409373c34-0&btsid=79cb7186-2094-4b3a-82c7-120bfe797327&ws_ab_test=searchweb0_0,searchweb201602_5,searchweb201603_53

노즐은 굳이 Mellow 제품같은거 쓸 필요 없다고 하긴 한다.

 

 

 

[나머지 부품 및 참고자료]

 

[출처] 엔더3, 5를 위한 고효율 저비용 업그레이드 리스트 (대한민국 No.1 3D 프린터 커뮤니티) |작성자 굿디

cafe.naver.com/makerfac/86975

 

- All3DP 포스트

https://all3dp.com/1/best-creality-ender-5-pro-upgrades-mods/

'etc > 말말말' 카테고리의 다른 글

Creality 엔더 5 플러스 (Ender-5 plus) 스펙  (0) 2020.01.03
전동보드는 보내주기로 했다  (0) 2020.01.01

가성비 3D프린터들을 선보여 인기가 많은 Creality 에서

기존 Ender-5 프린터를 향상시켜 새로운 제품을 출시했다.

바로 Ender-5 Plus 이다.

 

 

그런데 개인적으로 Ender-5 와 Ender-5 plus는 큐브 프레임인 부분은 같지만

나머지 부분은 보완/향상된 점이 많아 그냥 제품명을 달리 했어도 괜찮았을것 같다.

 

아래는 Creality가 강조하는 향상된 부분들이다.

 

-먼저 출력물의 크기가 최대 350 x 350 x 400 mm 으로 대폭 늘어났다.

기존 Ender-3와 Ender-5는 일반/프로 제품 모두 220 x 220 x 300 mm의 최대 출력물 크기를 가졌었기에

본체 자체도 상당히 커진 것을 볼 수 있다. 덕분에 큰 출력물을 분할 출력한 후 이어붙일 일이 줄어들게 되었다.

크다.

 

-오토레벨링 기능이 완전히 구현되었다.

Ender-5 pro 버전에서 오토레벨링이 추가되긴 했었지만, 이는 세미-오토레벨링 기능으로

노즐과 베드 사이에 명함을 껴넣어 조정하는 작업은 여전히 필요했고 손이 조금 덜 갈 뿐이었다.

 

그래서 오토레벨링 기능이 없는 프린터기에 BL-Touch라는 오토레벨링 센서를 사용자가 따로 구매하여

장착을 해 줘야지만 완벽한 오토레벨링 사용이 가능했었다.

 

 

하지만 이번 Ender-5 plus 제품에는 BL-Touch가 기본으로 장착되어 나와 편의성을 높혔다.

BL-Touch 센서이다.

 

-Z축이 2배가 되었다.

기존의 Ender-5는 Ender-3와는 다르게 큐브 프레임으로 형태가 바뀌면서 베드 또한 Z축을 따라

위 아래로만 움직이게 되었다. Ender-3에서 노즐 부분과 베드가 X,Y축을 따라 서로 엇갈려 움직이며 생기는

문제점들을 보완한 것이다.

 

그러나 Ender-5의 Z축 2개와 리드스크류 1개는 빌드플레이트의 뒤쪽만을 지지해 출력물이 무거워질수록

베드가 앞쪽으로 미세하게 기울어지는 또 다른 문제점이 있었다.

 

Ender-5 plus 는 Z축과 리드스크류 모두 기존에서 2배 증가시켜 좌/우측에 배치하여

빌드플레이트를 양쪽에서 지지해줌으로서 더욱 안정적인 출력이 가능하게 되었다.

잘 안보이지만 둘 다 Z축 사이에 리드 스크류가 있다.

 

-출력을 정지 후 재개할 수 있게 되었다.

정전에도 끄떡없다고 광고를 하더라. 말 그대로 전원이 끊기거나 사용자가 잠시 출력을 중단한 후

이후에 이어서 출력을 할 수 있는 기능이 생겼다.

 

 

 

아래는 전체적인 스펙이다.

 

가격대는 약 70만원으로 Ender-5 pro 제품이 40~50만원대인것을 보면 꽤 비싸졌다.

지갑 여유가 있다면 Ender-5 pro 보단 Ender-5 plus를 구매하는것이 좋겠다.

 

돈이 없어서 Ender-5 pro를 사야하는 현실 흑흑

'etc > 말말말' 카테고리의 다른 글

Ender 5 Pro 및 업그레이드 부속품 구매 링크  (0) 2020.01.09
전동보드는 보내주기로 했다  (0) 2020.01.01

전동보드 제작하려고 30만원쯤 썼는데,

아직도 70만원이나 더 필요하더라.

 

그래서 데크하고 트럭은 도로 팔아버리기로 했다.

데크는 나무판자고 트럭은 바퀴와 데크를 이어주는 부품이라 보면 된다

중고로 팔아버리고 3D 프린터를 사야겠다.

 

아래 사진이 트럭이다. 보기보다 엄청 크다

하필 트럭도 트람파 트럭이었다. 비싼 브랜드 제품이라는 말

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

물론 유저 커스터마이징

 

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

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

 

적용법은 매우 간단하다.

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로 이름을 바꿔주면 해결된다고 한다.

 

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

눈을 보호하도록 하자

 

hobby/그림

올빼미

2019. 8. 13. 22:38


지금보면 한없이 부족한 그림

'hobby > 그림' 카테고리의 다른 글

  (0) 2019.08.12

hobby/그림

2019. 8. 12. 08:04


손이다

공간지각능력은 인물화 관련된것들 그리는데에 별 도움이 안되는듯 하다

'hobby > 그림' 카테고리의 다른 글

올빼미  (0) 2019.08.13