diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..067b30c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 SourceFree
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 2d7e0a8..a4c6149 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,99 @@
-# KlasCroller
-OSS
+# KlasCroller (가제)
+
+*version 1.0.1
+
+
+

+
+
+ ### KlasCroller
+ #### -광운대학교 재학생 전용 PC 프로그램입니다.-
+
+

+

+

+

+

+
+
+
+
+## 프로젝트 설명
+광운대학교 학부생들이 학업 성취도에 대한 진단을 스스로 할 수 있도록 Window 기반 GUI 프로그램을 만들었습니다.
+
+사용자의 요청에 따라 파라미터를 기준으로 단일 학기 분석, 두 개 학기 비교, 전체 학기(완료된 학기 기준) 진단 등의 기능을 수행 합니다.
+
+klas 로그인 시 사용하는 id,pw를 입력받아, 학부생의 학업 성취 데이터를 추출하여 아래와 같은 파라미터를 구성합니다.
+
+
+### [프로젝트 기간]
+- (2022-09-12 ~ 2022-11-26)
+
+
+### [기능]
+- KLAS (광운대학교 웹어플리케이션, https://klas.kw.ac.kr) 접근 및 로그인
+- 학기별 출석율, 과제제출율, 성적 등의 학생 Data Scraping 및 파라미터 값 추출
+
+ [파라미터]
+
+ - 의지력 = (출석 / (출석+지각+결석)) X 100
+
+ - 지능 = ((실제 취득 학점) / (취득 가능 학점) ) X 100
+
+ - 생존력 = (*수강과목 갯수 에 따른 점수) + 총 과제,퀴즈 갯수(50개 이상 이면 50점)
+
+ - 근명성 = (제출한 과제 수 + 제출한 퀴즈 수) / (총 과제 수 + 총 퀴즈 수) X 100
+
+ - 가성비 = (지능 /의지력)
+
+
+ *수강과목 갯수 에 따른 점수:
+ 6 개 이상: 50점
+ 5 개 : 40점
+ 4 개 : 32점
+ 3 개 : 24점
+ 2 개 : 16점
+ 1 개 : 8점
+
+- 로컬 PC에 cache file 생성 후 파라미터 값 저장 (이후 프로그램 구동 시, Scraping 과정 생략)
+- 파라미터를 근거로 사용자가 원하는 학기에 대해 그래프 출력
+
+ 단일 학기 분석 시, 오각형 방사 그래프(Radiation Graph Of A Pentagon) 출력
+
+ 두개 학기 분석 시, 막대 그래프(Bar Graph) 출력
+
+- 완료된 학기에 분석하여 사용자의 학업 성취 스타일 판단
+
+ 1. 5개의 파라미터 각각의 중앙값으로 가장 높은 값, 두번째로 높은 값을 가지는 파라미터를 확인
+
+ 2. 가장 높은 값을 가지는 파라미터로 적절한 동물 이미지를 매치
+
+ 3. 두번째로 높은 값을 가지는 파라미터로 적절한 배경 이미지를 매치
+
+ 4. 2,3 번 으로 장점이 부각된 사용자의 학업 성취 스타일 출력
+
+ 5. 5개의 파라미터 각각의 중앙값으로 가장 낮은 값, 두 번째로 낮은 값을 가지는 파라미터를 확인
+
+ 6. 가장 낮은 값을 가지는 파라미터로 적절한 동물 이미지를 매치
+
+ 7. 두번째로 낮은 값을 가지는 파라미터로 적절한 배경 이미지를 매치
+
+ 8. 6,7 번 으로 단점이 부각된 사용자의 학업 성취 스타일 출력
+
+- 사용자 요청에 따른 cache file 초기화
+
+
+### [사용법]
+- KLAS 로그인 시 사용하는 id,pw로 프로그램에 로그인 해주세요.
+- 프로그램이 정상 작동이 되었다면 아래와 같은 화면에 사용자의 학번이 출력됩니다.
+
+- <단일 학기 분석> 버튼을 클릭한다면, 해당 기능에 대한 설명과 학기 선택 콤보 박스가 있는 학기선택 창을 보실 수 있습니다.
+선택된 학기에 대해서 사용자의 파라미터 값으로 오각형의 방사그래프(Radiation Graph Of A Pentagon)가 출력됩니다.
+
+- <두개 학기 비교> 버튼을 클릭한다면, 해당 기능에 대한 설명과 학기 선택 콤보 박스가 있는 학기선택 창을 보실 수 있습니다.
+선택된 두개 학기에 대해서 사용자의 파라미터 값으로 막대 그래프(Bar Graph)가 출력됩니다.
+
+- <종합 평가> 버튼을 클릭한다면, 해당 기능에 대한 설명 창을 보실 수 있습니다.
+
+- 새 학기를 업데이트 하려거나, KLAS 정보를 local PC에 저장하고 싶지 않을 경우, <내 정보 초기화> 버튼을 클릭 해주세요.
+
diff --git a/__pycache__/caching.cpython-310.pyc b/__pycache__/caching.cpython-310.pyc
new file mode 100644
index 0000000..30bc37a
Binary files /dev/null and b/__pycache__/caching.cpython-310.pyc differ
diff --git a/__pycache__/displaying.cpython-310.pyc b/__pycache__/displaying.cpython-310.pyc
new file mode 100644
index 0000000..df58318
Binary files /dev/null and b/__pycache__/displaying.cpython-310.pyc differ
diff --git a/__pycache__/scraping.cpython-310.pyc b/__pycache__/scraping.cpython-310.pyc
new file mode 100644
index 0000000..0981704
Binary files /dev/null and b/__pycache__/scraping.cpython-310.pyc differ
diff --git a/caching.py b/caching.py
new file mode 100644
index 0000000..dc94399
--- /dev/null
+++ b/caching.py
@@ -0,0 +1,31 @@
+import pickle
+import gzip
+import os
+
+class CacheManager:
+ def __init__(self,path,id):
+ # 파일이름
+ self.file_path = os.path.join(path,id+".plk")
+
+ def SaveCache(self, data):
+ # 저장, 압축
+ with gzip.open(self.file_path, "wb") as f:
+ pickle.dump(data, f)
+
+ # 저장된 데이터 초기화
+ def InitCache(self):
+ empty_dic = {}
+ with gzip.open(self.file_path, "wb") as f:
+ pickle.dump(empty_dic, f)
+
+ # 저장된 데이터 가져오기
+ def GetCache(self):
+ dic = {}
+ with gzip.open(self.file_path,'rb') as f:
+ dic = pickle.load(f)
+ return dic
+
+ # 파일 삭제
+ def DestoryFile(self):
+ if os.path.exists(self.file_path):
+ os.remove(self.file_path)
\ No newline at end of file
diff --git a/displaying.py b/displaying.py
new file mode 100644
index 0000000..75c1cbc
--- /dev/null
+++ b/displaying.py
@@ -0,0 +1,838 @@
+from tkinter import *
+from tkinter import ttk
+
+import os
+import sys
+import tkinter.font
+
+import webbrowser
+
+from math import pi
+
+import numpy as np
+import matplotlib
+import matplotlib.pyplot as plt
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+matplotlib.rcParams['font.family']='NanumGothic'
+matplotlib.rcParams['axes.unicode_minus'] = False
+
+from caching import CacheManager
+
+# 상대 경로 -> 절대 경로
+def resource_path(relative_path):
+ """ Get absolute path to resource, works for dev and for PyInstaller """
+ try:
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
+ base_path = sys._MEIPASS
+ except Exception:
+ base_path = os.path.abspath(".")
+
+ return os.path.join(base_path, relative_path)
+
+class WindowManager():
+ def __init__(self, user_info = {}, id = ''):
+ self.id = id
+ self.pw = ''
+
+ self.category = ['의지력', '사고력' , '생존력' , '근면성' , '가성비']
+ # 유저 정보 ( 의지력 , 사고력 , 생존력 , 근명성 , 가성비)
+ self.user_info = user_info
+ self.user_copy = user_info
+ # 전체(완료+현재) 학기 이름 (ex) 2022년 1학기)
+ self.seme_list = list(user_info.keys())
+ # 전체(완료+현재) 학기 수
+ self.cnt_seme = len(user_info)
+
+ # 계정을 삭제하고 종료하는지, 계정을 저장한 상태로 종료하는지
+ self.close_type = 0
+
+
+ # 로그인 용 Window 열기
+ def OpenWindow_Login(self):
+
+ # 창 설정
+ self.win_lo = Tk()
+ self.win_lo.title("Klas Log-in")
+ self.win_lo.geometry("400x500-100+50")
+
+ self.win_lo.protocol("WM_DELETE_WINDOW", self.CloseWindow_Login)
+
+ # 폰트들 설정
+ self.font1=tkinter.font.Font(family="나눔스퀘어라운드 ExtraBold", size=20, weight="bold")
+ self.font2=tkinter.font.Font(family="나눔스퀘어라운드 ExtraBold", size=10, weight="bold")
+
+ self.can_1 = Canvas(self.win_lo, width=400, height=500, background='#7C1B0F', highlightthickness = 0)
+ self.can_1.pack(padx=0, pady=0)
+ self.can_1.place(x=0, y=0)
+
+ self.can_2 = Canvas(self.win_lo, width=300, height=85, background='#7C1B0F')
+ self.can_2.pack(padx=0, pady=0)
+ self.can_2.place(x=50, y=55)
+
+ # 배경 이미지
+ img = PhotoImage(file=resource_path("KlasCroller\\img\\back.png"))
+ self.can_1.create_image(200, 250, image=img)
+
+ file_img2 = resource_path("KlasCroller\\img\\campus_logo.png")
+ img2 = PhotoImage(file=file_img2)
+ self.can_2.create_image(157, 45, image=img2)
+
+ # id 입력창
+ self.ent1 = Entry(self.win_lo, relief="groove")
+ self.ent1.insert(0, "ID : 학번을 입력하세요")
+ def clear(event):
+ # 좌클릭 했을 때 입력창의 내용 다 지우기
+ if self.ent1.get() == "ID : 학번을 입력하세요":
+ self.ent1.delete(0, len(self.ent1.get()))
+ self.ent1.bind("", clear)
+ self.ent1.place(x=50,y=200,width=300,height=35)
+
+ # pw 입력창
+ self.ent2 = Entry(self.win_lo)
+ self.ent2.config(show="*")
+ self.ent2.place(x=50,y=270,width=300,height=35)
+
+ btn = Button(self.win_lo, font=self.font2, bg="white", fg="black")
+ btn.config(text = "로그인")
+ btn.place(x=150,y=350,width=100,height=40)
+ btn.config(command=self.EventHandler_Login)
+
+ self.win_lo.mainloop()
+
+ def CloseWindow_Login(self):
+ self.win_lo.destroy()
+ exit(0)
+
+ def GetIdPw(self):
+ self.OpenWindow_Login()
+ return self.id,self.pw
+
+ # 로그인 이벤트 핸들러
+ def EventHandler_Login(self):
+ self.id = self.ent1.get()
+ self.pw = self.ent2.get()
+
+ self.can_1.destroy()
+ self.can_2.destroy()
+
+ self.win_lo.destroy()
+
+ def Run_Main(self):
+ self.OpenWindow_MainMenu()
+ path_user_file = resource_path("KlasCroller\\usr")
+ CacheManager(path_user_file, self.id).DestoryFile()
+ SubBoxManager().MessageBox("KlasCroller가 수집한\n 이용자님의 정보가 삭제되었습니다.")
+
+ exit(0)
+
+ # 메인메뉴 창 열기
+ def OpenWindow_MainMenu(self):
+
+ # 하나의 기능 창만 열릴 수 있도록
+ # boolean 형 변수 초기화
+ self.is_opend_win_func1 = False
+ self.is_opend_win_func2 = False
+ self.is_opend_win_func3 = False
+ self.is_opend_win_func4 = False
+
+ # 알림 창 선언
+ self.win_notice_func1 = -1
+ self.win_notice_func2 = -1
+ self.win_notice_func3 = -1
+ self.win_notice_func4 = -1
+
+ # 하위 창 선언
+ self.win_func1 = -1
+ self.win_func2 = -1
+ self.win_func3 = -1
+
+ # 창 설정
+ self.win_main = Tk()
+ self.win_main.title("Main Menu")
+ self.win_main.geometry("500x560-100+50") # 가로 세로
+ self.win_main.resizable(True, True)
+
+ self.win_main.protocol("WM_DELETE_WINDOW", self.CloseWindow_MainMenu)
+
+ can = Canvas(self.win_main, width=500, height=230,background='red', highlightthickness = 0)
+ can.pack(padx=0, pady=0)
+ can.place(x=0, y=0)
+
+ # 바탕 광운대 사진 삽입
+ file_back = resource_path("KlasCroller\\img\\back.png")
+ back = PhotoImage(file=file_back)
+ can.create_image(0,0,image=back)
+
+ # 각 기능 버튼에 해당하는 이미지들
+ img1 = PhotoImage(file=resource_path("KlasCroller\img\img_analysis_black.png"))
+
+ img2 = PhotoImage(file=resource_path("KlasCroller\img\img_compare_black.png"))
+
+ img3 = PhotoImage(file=resource_path("KlasCroller\img\img_mbti_black.png"))
+
+ img4 = PhotoImage(file=resource_path("KlasCroller\img\img_search_black.png"))
+
+ # 폰트들 설정
+ self.font0=tkinter.font.Font(family="나눔스퀘어라운드 ExtraBold", size=30, weight="bold")
+ self.font1=tkinter.font.Font(family="나눔스퀘어라운드 ExtraBold", size=20, weight="bold")
+ self.font2=tkinter.font.Font(family="나눔스퀘어라운드 ExtraBold", size=10, weight="bold")
+
+ lab1 = Label(self.win_main)
+ lab1.config(text = "KlasCroller", font=self.font0, foreground='white', background='#7C1B0F')
+ lab1.place(x=25,y=25)
+
+ # 사용자 학번
+ lab2 = Label(self.win_main)
+ lab2.config(text = str(self.id) + " 님, 반갑습니다.", font=self.font1, foreground='white', background='#7C1B0F')
+ lab2.place(x=25,y=170)
+
+ b1 = Button(self.win_main, text = "단일 학기\n 분석",font=self.font2, bg="white", fg="black",image=img1, compound="left",command=self.OpenWindow_Notice_Function1)
+ b2 = Button(self.win_main, text = "두개 학기\n 비교", font=self.font2, bg="white", fg="black",image=img2, compound="left",command=self.OpenWindow_Notice_Function2)
+ b3 = Button(self.win_main, text = "SF\nMBTI", font=self.font2, bg="white", fg="black",image=img3, compound="left",command=self.OpenWindow_Notice_Function3)
+ b4 = Button(self.win_main, text = "취업정보 확인", font=self.font2, bg="white", fg="black",image=img4, compound="left",command=self.OpenWindow_Notice_Function4)
+ b1.place(x=0,y=220,width=250, height=170)
+ b2.place(x=250,y=220,width=250, height=170)
+ b3.place(x=0,y=390,width=250, height=170)
+ b4.place(x=250,y=390,width=250, height=170);
+
+ # 캐시파일 삭제
+ Button(self.win_main,
+ text="계정 초기화",
+ bg='#7C1B0F',
+ fg='snow',
+ font=self.font2,
+ command = self.Delete_userInfo
+ ).place(x=400,y=20,width=95,height=25)
+
+ self.win_main.mainloop()
+
+ def Delete_userInfo(self):
+ self.close_type = 1
+ self.win_main.destroy()
+
+ def CloseWindow_MainMenu(self):
+ self.close_type = 0
+ self.win_main.destroy()
+ exit(0)
+
+ # 첫번째 기능 알림 창 open
+ def OpenWindow_Notice_Function1(self):
+ # main 창 제외하고 열린 창은 모두 닫기
+ if self.is_opend_win_func1 :
+ self.win_notice_func1.destroy()
+ self.win_notice_func1 = -1
+ self.is_opend_win_func1 = False
+ if self.is_opend_win_func2 :
+ self.win_notice_func2.destroy()
+ self.win_notice_func2 = -1
+ self.is_opend_win_func2 = False
+ if self.is_opend_win_func3 :
+ self.win_notice_func3.destroy()
+ self.win_notice_func3 = -1
+ self.is_opend_win_func3 = False
+ if self.is_opend_win_func4 :
+ self.win_notice_func4.destroy()
+ self.win_notice_func4 = -1
+ self.is_opend_win_func4 = False
+
+ # func1 창이 열림
+ self.is_opend_win_func1 = True
+
+ # 하위 창 초기화
+ if self.win_func1 != -1:
+ self.win_func1.destroy()
+ self.win_func1 = -1
+ if self.win_func2 != -1:
+ self.win_func2.destroy()
+ self.win_func2 = -1
+ if self.win_func3 != -1:
+ self.win_func3.destroy()
+ self.win_func3 = -1
+
+ # 창 설정
+ self.win_notice_func1 = Toplevel(self.win_main)
+ self.win_notice_func1.title("한 한기 분석 알림")
+ self.win_notice_func1.geometry("400x450+100+50")
+ self.win_notice_func1.resizable(width = FALSE, height = FALSE)
+
+ # 배경
+ canvas = Canvas(self.win_notice_func1,
+ width=400,
+ height=450,
+ background='#7C1B0F',
+ highlightthickness = 0)
+ canvas.pack(padx=0, pady=0)
+ canvas.place(x=0, y=0)
+ img1 = PhotoImage(file=resource_path("KlasCroller\\img\\back.png"))
+ canvas.create_image(200, 230, image=img1)
+
+ # 제목 옆에 이미지
+ img2 = PhotoImage(file=resource_path("KlasCroller\\img\\img_analysis_white.png"))
+ Label(self.win_notice_func1, image=img2, background='#7C1B0F').place(x=70, y=95)
+
+
+ # 창 Title
+ Label(master=self.win_notice_func1,text = "단일 학기 분석",
+ font=self.font1,foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=130,y=100)
+
+ # 설명글
+ Label(master=self.win_notice_func1,
+ text = "아래의 드롭다운 메뉴에서\n\n분석하고 싶은 학기를 선택해주세요.\n\n\n 선택된 학기에 대해 \n\n오각 방사 그래프를 출력해드립니다.",
+ font=self.font2,foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=95,y=150)
+
+ # 비교 학기 선택창에 콤보박스 생성
+ self.combobox_1_1 = ttk.Combobox(self.win_notice_func1)
+ self.combobox_1_1.config(value = self.seme_list)
+ self.combobox_1_1.current(0)
+ self.combobox_1_1.place(x=70,y=300,width=260,height=30)
+
+
+ # 분석하기 버튼
+ Button(self.win_notice_func1,
+ text="분석하기",
+ bg='#7C1B0F',
+ fg='snow',
+ font=self.font2,
+ command = self.OpenWindow_OneSemesterAnalysis).place(x=160,y=400,width=80,height=25)
+
+
+ self.win_notice_func1.mainloop()
+
+ def OpenWindow_OneSemesterAnalysis(self):
+ self.sem = self.combobox_1_1.get()
+
+ self.data_list = self.user_info[self.sem].copy()
+ self.data_list += self.data_list[:1]
+
+ angles = [n/float(5) * 2 *pi for n in range(5)]
+ angles += angles[:1]
+
+ fig,ax = plt.subplots(1,1,figsize=(3,5),subplot_kw=dict(polar=True))
+ ax.patch.set_facecolor('#7C1B0F')
+ fig.patch.set_facecolor('#7C1B0F')
+ ax.set_theta_offset(pi / 2) ## 시작점
+ ax.set_theta_direction(-1) ## 그려지는 방향 시계방향
+ plt.title(self.sem, size=15, color='white')
+
+ plt.xticks(angles[:-1],self.category,color='white',size=10)
+ ax.tick_params(axis='x', which='major', pad=5)
+ plt.yticks([20,40,60,80],['','','',''],color='white',size=10)
+ plt.ylim(0,100)
+ ax.set_rlabel_position(100)
+
+ ax.plot(angles,self.data_list,linewidth=3,linestyle='solid',color='#FFFF00')
+ ax.fill(angles,self.data_list,'#FFFF00',alpha=0.5)
+
+ self.win_func1 = Toplevel(self.win_notice_func1)
+ self.win_func1.config(bg='#7C1B0F')
+ self.win_func1.title(self.sem+" 학기 분석")
+ self.win_func1.geometry("650x400+150+100")
+ self.win_func1.resizable(width = FALSE, height = FALSE)
+
+ parameter_label = [Label(self.win_func1) for _ in range(5)]
+ value_label = [Label(self.win_func1) for _ in range(5)]
+
+
+ for i in range(5):
+ parameter_label[i].config(text = self.category[i], font = self.font1, bg='#69180D', fg = 'yellow')
+ parameter_label[i].place(x=420, y= 20 + 80*i)
+
+ value_label[i].config(text = self.user_info[self.sem][i], font = self.font1, bg='#69180D', fg = 'snow')
+ value_label[i].place(x=550, y= 20 + 80*i)
+
+ canvas = FigureCanvasTkAgg(fig, master=self.win_func1)
+ canvas.get_tk_widget().pack(anchor='w')
+
+
+
+
+
+
+
+
+
+
+
+ # 두번째 기능 알림 창 open
+ def OpenWindow_Notice_Function2(self):
+ # main 창 제외하고 열린 창은 모두 닫기
+ if self.is_opend_win_func1 :
+ self.win_notice_func1.destroy()
+ self.win_notice_func1 = -1
+ self.is_opend_win_func1 = False
+ if self.is_opend_win_func2 :
+ self.win_notice_func2.destroy()
+ self.win_notice_func2 = -1
+ self.is_opend_win_func2 = False
+ if self.is_opend_win_func3 :
+ self.win_notice_func3.destroy()
+ self.win_notice_func3 = -1
+ self.is_opend_win_func3 = False
+ if self.is_opend_win_func4 :
+ self.win_notice_func4.destroy()
+ self.win_notice_func4 = -1
+ self.is_opend_win_func4 = False
+
+ # func2 창이 열림
+ self.is_opend_win_func2 = True
+
+ # 하위 창 초기화
+ if self.win_func1 != -1:
+ self.win_func1.destroy()
+ self.win_func1 = -1
+ if self.win_func2 != -1:
+ self.win_func2.destroy()
+ self.win_func2 = -1
+ if self.win_func3 != -1:
+ self.win_func3.destroy()
+ self.win_func3 = -1
+
+ # 창 설정
+ self.win_notice_func2 = Toplevel(self.win_main)
+ self.win_notice_func2.title("두 학기 비교 알림")
+ self.win_notice_func2.geometry("400x450+100+50")
+ self.win_notice_func2.resizable(width = FALSE, height = FALSE)
+
+ # 배경
+ canvas = Canvas(self.win_notice_func2,
+ width=400,
+ height=450,
+ background='#7C1B0F',
+ highlightthickness = 0)
+ canvas.pack(padx=0, pady=0)
+ canvas.place(x=0, y=0)
+ img1 = PhotoImage(file=resource_path("KlasCroller\\img\\back.png"))
+ canvas.create_image(200, 230, image=img1)
+
+ # 제목 옆에 이미지
+ img2 = PhotoImage(file=resource_path("KlasCroller\\img\\img_compare_white.png"))
+ Label(self.win_notice_func2, image=img2, background='#7C1B0F').place(x=70, y=95)
+
+ # 창 Title
+ Label(master=self.win_notice_func2,
+ text = "두 개 학기 비교 ",
+ font=self.font1,
+ foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=130,y=100)
+
+ # 설명글
+ Label(master=self.win_notice_func2,
+ text = "아래의 드롭다운 메뉴에서\n\n비교하고 싶은 학기를 선택해주세요.\n\n\n 선택된 학기에 대해 \n\n막대 그래프를 출력해드립니다.",
+ font=self.font2,foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=95,y=150)
+
+ # 비교 학기 선택창에 콤보박스 생성
+ self.combobox_2_1 = ttk.Combobox(self.win_notice_func2)
+ self.combobox_2_2 = ttk.Combobox(self.win_notice_func2)
+
+ self.combobox_2_1.config(value = self.seme_list)
+ self.combobox_2_1.current(0)
+ self.combobox_2_1.place(x=70,y=300,width=260,height=30)
+
+ self.combobox_2_2.config(value = self.seme_list)
+ self.combobox_2_2.current(0)
+ self.combobox_2_2.place(x=70,y=350,width=260,height=30)
+
+ # 비교하기 버튼
+ Button(self.win_notice_func2,
+ text="비교하기",
+ bg='#7C1B0F',
+ fg='snow',
+ font=self.font2,
+ command = self.OpenWindow_TwoSemesterCompare).place(x=160,y=400,width=80,height=25)
+
+ self.win_notice_func2.mainloop()
+
+ def OpenWindow_TwoSemesterCompare(self):
+ self.sem1 = self.combobox_2_1.get()
+ self.sem2 = self.combobox_2_2.get()
+ self.list_1 = self.user_info[self.sem1].copy()
+ self.list_2 = self.user_info[self.sem2].copy()
+
+ x = np.arange(len(self.category))
+ width = 0.3
+
+ fig, ax =plt.subplots(figsize=(4,5))
+ ax.patch.set_facecolor('#7C1B0F')
+ fig.patch.set_facecolor('#7C1B0F')
+ plt.bar(x, self.list_1, width, color='#FFFFFF',alpha = 0.5, edgecolor = '#FFFFFF', linewidth = 2)
+ plt.bar(x + width+0.1, self.list_2, width, color='#FFFF00',alpha = 0.5, edgecolor = '#FFFF00', linewidth = 2)
+ plt.xticks(x+width, self.category, color ='white')
+ plt.yticks(color='white')
+
+ plt.title(self.sem1+' vs '+self.sem2,color='white')
+
+ self.win_func2 = Toplevel(self.win_notice_func2)
+ self.win_func2.config(bg='#7C1B0F')
+ self.win_func2.title(self.sem1+','+self.sem2+" 학기 비교")
+ self.win_func2.geometry("650x400+150+100")
+ self.win_func2.resizable(width = FALSE, height = FALSE)
+
+
+
+
+ parameter_label = [Label(self.win_func2) for _ in range(5)]
+ # seme_1_label = [Label(window_func_2) for _ in range(5)]
+ # seme_1_label = [Label(window_func_2) for _ in range(5)]
+ value_1_label = [Label(self.win_func2) for _ in range(5)]
+ value_2_label = [Label(self.win_func2) for _ in range(5)]
+
+ for i in range(5):
+ parameter_label[i].config(text = self.category[i], font = self.font1, bg='snow', fg = 'black')
+ parameter_label[i].place(x=420, y= 22 + 80*i)
+
+ string_1 = self.sem1+" "+str(self.list_1[i])
+ value_1_label[i].config(text = string_1, font = self.font2, bg='#69180D', fg = 'snow')
+ value_1_label[i].place(x=505, y= 15 + 80*i)
+
+ string_2 = self.sem2+" "+ str(self.list_2[i])
+ value_2_label[i].config(text = string_2, font = self.font2, bg='#69180D', fg = 'yellow')
+ value_2_label[i].place(x=505, y= 40 + 80*i)
+ canvas = FigureCanvasTkAgg(fig, master=self.win_func2)
+ canvas.get_tk_widget().pack(anchor='w')
+
+
+
+
+
+ # 세번째 기능 알림 창 open
+ def OpenWindow_Notice_Function3(self):
+ # main 창 제외하고 열린 창은 모두 닫기
+ if self.is_opend_win_func1 :
+ self.win_notice_func1.destroy()
+ self.win_notice_func1 = -1
+ self.is_opend_win_func1 = False
+ if self.is_opend_win_func2 :
+ self.win_notice_func2.destroy()
+ self.win_notice_func2 = -1
+ self.is_opend_win_func2 = False
+ if self.is_opend_win_func3 :
+ self.win_notice_func3.destroy()
+ self.win_notice_func3 = -1
+ self.is_opend_win_func3 = False
+ if self.is_opend_win_func4 :
+ self.win_notice_func4.destroy()
+ self.win_notice_func4 = -1
+ self.is_opend_win_func4 = False
+
+ # func3 창이 열림
+ self.is_opend_win_func3 = True
+
+ # 하위 창 초기화
+ if self.win_func1 != -1:
+ self.win_func1.destroy()
+ self.win_func1 = -1
+ if self.win_func2 != -1:
+ self.win_func2.destroy()
+ self.win_func2 = -1
+ if self.win_func3 != -1:
+ self.win_func3.destroy()
+ self.win_func3 = -1
+
+ # 창 설정
+ self.win_notice_func3 = Toplevel(self.win_main)
+ self.win_notice_func3.title("학업 스타일 분석")
+ self.win_notice_func3.geometry("400x450+100+50")
+ self.win_notice_func3.resizable(width = FALSE, height = FALSE)
+
+ # 배경
+ canvas = Canvas(self.win_notice_func3,
+ width=400,
+ height=450,
+ background='#7C1B0F',
+ highlightthickness = 0)
+ canvas.pack(padx=0, pady=0)
+ canvas.place(x=0, y=0)
+ img1 = PhotoImage(file=resource_path("KlasCroller\\img\\back.png"))
+ canvas.create_image(200, 230, image=img1)
+
+ # 제목 옆에 이미지
+ img2 = PhotoImage(file=resource_path("KlasCroller\\img\\img_mbti_white.png"))
+ Label(self.win_notice_func3, image=img2, background='#7C1B0F').place(x=80, y=85)
+
+ # 창 Title
+ Label(master=self.win_notice_func3,
+ text = "SF MBTI",
+ font=self.font1,
+ foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=140,y=100)
+
+ # 설명글
+ Label(master=self.win_notice_func3,
+ text = " 지난 학기동안 당신은 어떻게\n\n학업을 수행하셨을까요?\n\n\n당신의 학업 성취 스타일과 맞는\n\n동물 사진을 출력해드립니다.",
+ font=self.font2,
+ foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=95,y=150)
+
+ # 확인하기 버튼
+ Button(self.win_notice_func3,
+ text="확인하기",
+ bg='#7C1B0F',
+ fg='snow',
+ font=self.font2,
+ command = self.OpenWindow_PrintUserStyle).place(x=150,y=300,width=80,height=25)
+
+ self.win_notice_func3.mainloop()
+
+ def OpenWindow_PrintUserStyle(self):
+
+ self.win_func3 = Toplevel(self.win_notice_func3)
+ self.win_func3.config(bg='#7C1B0F')
+ self.win_func3.title('SF MBTI')
+ self.win_func3.geometry("650x400+150+100")
+ self.win_func3.resizable(width = FALSE, height = FALSE)
+
+ Label(self.win_func3,text="학업 스타일 분석",font=self.font1,bg='#7C1B0F',fg='snow').place(x=220,y=10)
+
+
+
+ # 사용자 style 뽑아내는 로직
+ good_adjective_list = ['열정많은','저명한','끈질긴','부지런한','운 좋은']
+ good_noun_list = ['소나무','돌고래','쥐','개미','네잎클로버']
+
+ bad_adjective_list = ['노력상실','우둔한','포기 빠른','게으른','불운한']
+ bad_noun_list = ['베짱이','금붕어','게복치','나무늘보','까마귀']
+
+ good_back_imgs = ['KlasCroller\\img\\back_p_1.png',
+ 'KlasCroller\\img\\back_p_2.png',
+ 'KlasCroller\\img\\back_p_3.png',
+ 'KlasCroller\\img\\back_p_4.png',
+ 'KlasCroller\\img\\back_p_5.png']
+ bad_back_imgs = ['KlasCroller\\img\\back_n_1.png',
+ 'KlasCroller\\img\\back_n_2.png',
+ 'KlasCroller\\img\\back_n_3.png',
+ 'KlasCroller\\img\\back_n_4.png',
+ 'KlasCroller\\img\\back_n_5.png']
+
+ good_ani_imgs = ['KlasCroller\\img\\img_p_1.png',
+ 'KlasCroller\\img\\img_p_2.png',
+ 'KlasCroller\\img\\img_p_3.png',
+ 'KlasCroller\\img\\img_p_4.png',
+ 'KlasCroller\\img\\img_p_5.png']
+ bad_ani_imgs = ['KlasCroller\\img\\img_n_1.png',
+ 'KlasCroller\\img\\img_n_2.png',
+ 'KlasCroller\\img\\img_n_3.png',
+ 'KlasCroller\\img\\img_n_4.png',
+ 'KlasCroller\\img\\img_n_5.png']
+
+ list_a =[] # 의지력 list
+ list_b =[] # 사고력 list
+ list_c =[] # 생존력 list
+ list_d =[] # 근명성 list
+ list_e =[] # 가성비 list
+
+
+ for seme in self.seme_list:
+ list_a.append(self.user_info[seme][0])
+ list_b.append(self.user_info[seme][1])
+ list_c.append(self.user_info[seme][2])
+ list_d.append(self.user_info[seme][3])
+ list_e.append(self.user_info[seme][4])
+
+ list_median = []
+ list_median.append(np.median(list_a))
+ list_median.append(np.median(list_b))
+ list_median.append(np.median(list_c))
+ list_median.append(np.median(list_d))
+ list_median.append(np.median(list_e))
+ del list_a, list_b, list_c, list_d, list_e
+
+ # (best, second_best): list_median에서 최고, 차고 값의 인덱스
+ # (worst, second_worst): list_median에서 최저, 차저 값의 인덱스
+ best = second_best = worst = second_worst = 0
+ # tmp: 차고, 차저 값의 인덱스 추출시 최고,최저 값을 잠시 저장하기 위해 쓰는 변수
+ tmp = 0
+
+ # (best, second_best): list_median에서 최고, 차고 값의 인덱스
+ best = list_median.index(max(list_median))
+ tmp = list_median[best]
+ list_median[best] = -1
+ second_best = list_median.index(max(list_median))
+ list_median[best] = tmp
+
+ # (worst, second_worst): list_median에서 최저, 차저 값의 인덱스
+ worst = list_median.index(min(list_median))
+ tmp = list_median[worst]
+ list_median[worst] = 101
+ second_worst = list_median.index(min(list_median))
+ list_median[worst] = tmp
+ del list_median
+
+ str_good = good_adjective_list[second_best] +' '+ good_noun_list[best]
+ str_bad = bad_adjective_list[second_worst] +' '+ bad_noun_list[worst]
+
+
+ img1 = PhotoImage(file=resource_path("KlasCroller\\img\\img_board.png"))
+
+ canvas_card1 = Canvas(self.win_func3, width=246, height=328,background='#7C1B0F', highlightthickness = 0)
+ canvas_card1.place(x=50, y=65)
+ canvas_card1.create_image(123, 164, image=img1)
+
+ canvas_card2 = Canvas(self.win_func3, width=246, height=328,background='#7C1B0F', highlightthickness = 0)
+ canvas_card2.place(x=365, y=65)
+ canvas_card2.create_image(123, 164, image=img1)
+
+ canvas_pos_animal = Canvas(self.win_func3, width=185, height=185 ,background='white', highlightthickness = 0)
+ canvas_pos_animal.place(x=80, y=185)
+ background_img_1 = PhotoImage(file=resource_path(good_back_imgs[best]))
+ foreground_img_1 = PhotoImage(file=resource_path(good_ani_imgs[best]))
+ canvas_pos_animal.create_image(185//2,185//2,image=background_img_1)
+ canvas_pos_animal.create_image(185//2,185//2,image=foreground_img_1)
+
+ canvas_neg_animal = Canvas(self.win_func3, width=185, height=185 ,background='white', highlightthickness = 0)
+ canvas_neg_animal.place(x=396, y=185)
+ background_img_2 = PhotoImage(file=resource_path(bad_back_imgs[worst]))
+ foreground_img_2 = PhotoImage(file=resource_path(bad_ani_imgs[worst]))
+ canvas_neg_animal.create_image(185//2,185//2,image=background_img_2)
+ canvas_neg_animal.create_image(185//2,185//2,image=foreground_img_2)
+
+ Label(self.win_func3,text=str_good,font=self.font2,bg='white',fg='black').place(x=80,y=125)
+ Label(self.win_func3,text="- "+self.category[best] + " 이/가 제일 훌륭합니다.",font=self.font2,bg='white',fg='black').place(x=80,y=145)
+ Label(self.win_func3,text="- "+self.category[second_best] + " 이/가 뛰어납니다.",font=self.font2,bg='white',fg='black').place(x=80,y=165)
+
+ Label(self.win_func3,text=str_bad ,font=self.font2,bg='white',fg='black').place(x=395,y=125)
+ Label(self.win_func3,text="- "+self.category[worst] + " 이/가 제일 필요합니다.",font=self.font2,bg='white',fg='black').place(x=395,y=145)
+ Label(self.win_func3,text="- "+self.category[second_worst] + " 이/가 부족합니다.",font=self.font2,bg='white',fg='black').place(x=395,y=165)
+
+
+ self.win_func3.mainloop()
+
+
+
+
+
+
+
+
+# 네번째 기능 알림 창 open
+ def OpenWindow_Notice_Function4(self):
+ # main 창 제외하고 열린 창은 모두 닫기
+ if self.is_opend_win_func1 :
+ self.win_notice_func1.destroy()
+ self.win_notice_func1 = -1
+ self.is_opend_win_func1 = False
+ if self.is_opend_win_func2 :
+ self.win_notice_func2.destroy()
+ self.win_notice_func2 = -1
+ self.is_opend_win_func2 = False
+ if self.is_opend_win_func3 :
+ self.win_notice_func3.destroy()
+ self.win_notice_func3 = -1
+ self.is_opend_win_func3 = False
+ if self.is_opend_win_func4 :
+ self.win_notice_func4.destroy()
+ self.win_notice_func4 = -1
+ self.is_opend_win_func4 = False
+
+ # func4 창이 열림
+ self.is_opend_win_func4 = True
+
+ # 창 설정
+ self.win_notice_func4 = Toplevel(self.win_main)
+ self.win_notice_func4.title("학업 스타일 분석")
+ self.win_notice_func4.geometry("400x450+100+50")
+ self.win_notice_func4.resizable(width = FALSE, height = FALSE)
+
+ # 배경
+ canvas = Canvas(self.win_notice_func4,
+ width=400,
+ height=450,
+ background='#7C1B0F',
+ highlightthickness = 0)
+ canvas.pack(padx=0, pady=0)
+ canvas.place(x=0, y=0)
+ img1 = PhotoImage(file=resource_path("KlasCroller\\img\\back.png"))
+ canvas.create_image(200, 230, image=img1)
+
+ # 제목 옆에 이미지
+ img2 = PhotoImage(file=resource_path("KlasCroller\\img\\img_search_white.png"))
+ Label(self.win_notice_func4, image=img2, background='#7C1B0F').place(x=80, y=85)
+
+ # 창 Title
+ Label(master=self.win_notice_func4,
+ text = "취업 공고 분야",
+ font=self.font1,
+ foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=140,y=100)
+
+ # 설명글
+ Label(master=self.win_notice_func4,
+ text = "\n\n입력하신 분야/직업\n\n잡코리아 취업공고가 출력됩니다.",
+ font=self.font2,
+ foreground='white',
+ background='#7C1B0F',
+ justify= CENTER).place(x=95,y=150)
+
+ # 입력창
+ self.entry_search = Entry(master=self.win_notice_func4, relief = "groove")
+ self.entry_search.insert(0,"예: 웹 개발, 앱 개발 ..")
+ def clear(event):
+ # 좌클릭 했을 때 입력차의 내용 다 지우기
+ if self.entry_search.get() == "예: 웹 개발, 앱 개발 ..":
+ self.entry_search.delete(0,len(self.entry_search.get()))
+ self.entry_search.bind("",clear)
+ self.entry_search.place(x=85,y=300,width=210,height=40)
+
+ # 검색하기 버튼
+ Button(self.win_notice_func4,
+ text="검색하기",
+ bg='#7C1B0F',
+ fg='snow',
+ font=self.font2,
+ command = self.OpenWebsite).place(x=150,y=360,width=80,height=25)
+
+ self.win_notice_func4.mainloop()
+
+ def OpenWebsite(self):
+ keyword = self.entry_search.get()
+ url = 'https://www.jobkorea.co.kr/Search/?stext={}&tabType=recruit&Page_No=1'.format(keyword)
+ webbrowser.open(url)
+
+
+class SubBoxManager():
+ def __init__(self):
+ print()
+ def __del__(self):
+ print()
+ def MessageBox(self,string):
+ self.win_message = Tk()
+ self.win_message.title("Message")
+ self.win_message.geometry("384x128")
+ self.win_message.option_add("*Font", "맑은고딕 12")
+
+ label_message = Label(master=self.win_message)
+ button_message = Button(master=self.win_message)
+
+ label_message.config(text = string,justify= CENTER, wraplength= 300)
+ button_message.config(text="확인",command=self.Check_Ok)
+
+ label_message.pack(expand=True, fill="both")
+ button_message.pack(side="bottom", anchor="s",pady=10)
+ self.win_message.mainloop()
+
+ def LoadingBox(self ,str="...", max_min = "5"):
+ self.win_loading = Tk()
+ self.win_loading.title("Loading")
+ self.win_loading.geometry("384x128")
+ self.win_loading.option_add("*Font", "맑은고딕 12")
+
+ Label(self.win_loading,text=str).pack(fill="both",pady=10)
+ Label(self.win_loading,text="( 최대 소요시간: "+max_min+"분 )").pack(fill="both")
+
+ progress = ttk.Progressbar(self.win_loading,orient=HORIZONTAL,length=300,mode='determinate')
+ progress.pack(pady=10)
+ progress.start(10)
+ self.win_loading.mainloop()
+
+ def Check_Ok(self):
+ self.win_message.destroy()
+
\ No newline at end of file
diff --git a/img/back.png b/img/back.png
new file mode 100644
index 0000000..104f173
Binary files /dev/null and b/img/back.png differ
diff --git a/img/back_n_1.png b/img/back_n_1.png
new file mode 100644
index 0000000..b785f3d
Binary files /dev/null and b/img/back_n_1.png differ
diff --git a/img/back_n_2.png b/img/back_n_2.png
new file mode 100644
index 0000000..906a07a
Binary files /dev/null and b/img/back_n_2.png differ
diff --git a/img/back_n_3.png b/img/back_n_3.png
new file mode 100644
index 0000000..088dd99
Binary files /dev/null and b/img/back_n_3.png differ
diff --git a/img/back_n_4.png b/img/back_n_4.png
new file mode 100644
index 0000000..d055e21
Binary files /dev/null and b/img/back_n_4.png differ
diff --git a/img/back_n_5.png b/img/back_n_5.png
new file mode 100644
index 0000000..4e561bb
Binary files /dev/null and b/img/back_n_5.png differ
diff --git a/img/back_p_1.png b/img/back_p_1.png
new file mode 100644
index 0000000..e8fe03a
Binary files /dev/null and b/img/back_p_1.png differ
diff --git a/img/back_p_2.png b/img/back_p_2.png
new file mode 100644
index 0000000..df90106
Binary files /dev/null and b/img/back_p_2.png differ
diff --git a/img/back_p_3.png b/img/back_p_3.png
new file mode 100644
index 0000000..ffc8c04
Binary files /dev/null and b/img/back_p_3.png differ
diff --git a/img/back_p_4.png b/img/back_p_4.png
new file mode 100644
index 0000000..18dc132
Binary files /dev/null and b/img/back_p_4.png differ
diff --git a/img/back_p_5.png b/img/back_p_5.png
new file mode 100644
index 0000000..798f358
Binary files /dev/null and b/img/back_p_5.png differ
diff --git a/img/campus_logo.png b/img/campus_logo.png
new file mode 100644
index 0000000..f3ea7cc
Binary files /dev/null and b/img/campus_logo.png differ
diff --git a/img/img_analysis_black.png b/img/img_analysis_black.png
new file mode 100644
index 0000000..3b3beb2
Binary files /dev/null and b/img/img_analysis_black.png differ
diff --git a/img/img_analysis_white.png b/img/img_analysis_white.png
new file mode 100644
index 0000000..6a64bcf
Binary files /dev/null and b/img/img_analysis_white.png differ
diff --git a/img/img_board.png b/img/img_board.png
new file mode 100644
index 0000000..965c768
Binary files /dev/null and b/img/img_board.png differ
diff --git a/img/img_compare_black.png b/img/img_compare_black.png
new file mode 100644
index 0000000..dd61e78
Binary files /dev/null and b/img/img_compare_black.png differ
diff --git a/img/img_compare_white.png b/img/img_compare_white.png
new file mode 100644
index 0000000..d4ef642
Binary files /dev/null and b/img/img_compare_white.png differ
diff --git a/img/img_mbti_black.png b/img/img_mbti_black.png
new file mode 100644
index 0000000..edca13b
Binary files /dev/null and b/img/img_mbti_black.png differ
diff --git a/img/img_mbti_white.png b/img/img_mbti_white.png
new file mode 100644
index 0000000..f3e0186
Binary files /dev/null and b/img/img_mbti_white.png differ
diff --git a/img/img_n_1.png b/img/img_n_1.png
new file mode 100644
index 0000000..aa7d073
Binary files /dev/null and b/img/img_n_1.png differ
diff --git a/img/img_n_2.png b/img/img_n_2.png
new file mode 100644
index 0000000..451f2ac
Binary files /dev/null and b/img/img_n_2.png differ
diff --git a/img/img_n_3.png b/img/img_n_3.png
new file mode 100644
index 0000000..adf383d
Binary files /dev/null and b/img/img_n_3.png differ
diff --git a/img/img_n_4.png b/img/img_n_4.png
new file mode 100644
index 0000000..452334c
Binary files /dev/null and b/img/img_n_4.png differ
diff --git a/img/img_n_5.png b/img/img_n_5.png
new file mode 100644
index 0000000..8a5b830
Binary files /dev/null and b/img/img_n_5.png differ
diff --git a/img/img_p_1.png b/img/img_p_1.png
new file mode 100644
index 0000000..766332e
Binary files /dev/null and b/img/img_p_1.png differ
diff --git a/img/img_p_2.png b/img/img_p_2.png
new file mode 100644
index 0000000..9ec89be
Binary files /dev/null and b/img/img_p_2.png differ
diff --git a/img/img_p_3.png b/img/img_p_3.png
new file mode 100644
index 0000000..b25d1f4
Binary files /dev/null and b/img/img_p_3.png differ
diff --git a/img/img_p_4.png b/img/img_p_4.png
new file mode 100644
index 0000000..e4433e7
Binary files /dev/null and b/img/img_p_4.png differ
diff --git a/img/img_p_5.png b/img/img_p_5.png
new file mode 100644
index 0000000..d5e4475
Binary files /dev/null and b/img/img_p_5.png differ
diff --git a/img/img_search_black.png b/img/img_search_black.png
new file mode 100644
index 0000000..416baba
Binary files /dev/null and b/img/img_search_black.png differ
diff --git a/img/img_search_white.png b/img/img_search_white.png
new file mode 100644
index 0000000..473d147
Binary files /dev/null and b/img/img_search_white.png differ
diff --git a/img/kwang.png b/img/kwang.png
new file mode 100644
index 0000000..c297f9e
Binary files /dev/null and b/img/kwang.png differ
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..5a3b9de
--- /dev/null
+++ b/main.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+import sys
+import io
+import os
+sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
+sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
+
+import multiprocessing as mp
+
+# 버전정보
+# python 3.10.7
+# chrome 105.0.5195.127
+
+from scraping import Scraper
+from displaying import *
+from caching import CacheManager
+
+# 상대 경로 -> 절대 경로
+def resource_path(relative_path):
+ """ Get absolute path to resource, works for dev and for PyInstaller """
+ try:
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
+ base_path = sys._MEIPASS
+ except Exception:
+ base_path = os.path.abspath(".")
+
+ return os.path.join(base_path, relative_path)
+
+if __name__ == "__main__":
+
+ # 유저 정보가 담긴 파일 위치
+ path_user_file = resource_path("KlasCroller\\usr")
+
+ loginWin = WindowManager()
+ alert = SubBoxManager()
+
+ # 사용자로부터 id,pw를 받아온다.
+ id,pw = loginWin.GetIdPw()
+ del loginWin
+
+ # loading box 출력(SubProcess, 동시실행을 위해서)
+ proc = mp.Process(target=alert.LoadingBox, args=("ID,PW 유효성 검사 중 입니다..","1",), daemon=True)
+ proc.start()
+
+ # klas에 접속해서 id, pw 가 유효한지 먼저 확인
+ scraper = Scraper(True)
+ cache = CacheManager(path_user_file,id)
+ file_name = os.path.join(path_user_file,str(id)+".plk")
+
+ # 유효성 검사 중, 네트웨크에 제대로 연결되지 않았다면 강제 종료
+ if(scraper.AcceseKlas(id,pw) == -1):
+ proc.kill()
+ del scraper, cache
+ alert.MessageBox("인터넷 연결 불안으로 비정상 종료되었습니다. 다시 시도해주세요.")
+ del alert
+ os._exit(0)
+
+ # id,pw가 유효하지 않다면 강제 종료
+ if(scraper.AcceseKlas(id,pw) == -2):
+ proc.kill()
+ del scraper, cache
+ alert.MessageBox("ID 혹은 Passward가 유효하지 않습니다. 다시 접속해주세요.")
+ del alert
+ os._exit(0)
+
+ # 유효하다면 id로 캐시파일이 있는지 확인
+ else:
+ proc.kill()
+ del scraper
+
+ proc1 = mp.Process(target=alert.LoadingBox, args=("Klas에서 정보를 가져옵니다..","5",), daemon=True)
+ proc1.start()
+ scraper = Scraper()
+
+ # (학번).plk 파일 이 없다면, 스크래핑 후 파일을 만든다.
+ if not os.path.isfile(file_name):
+
+ # 로그인 중, 네트웨크에 제대로 연결되지 않았다면 강제 종료
+ if(scraper.AcceseKlas(id,pw) == -1):
+ proc.kill()
+ del scraper, cache
+ alert.MessageBox("인터넷 연결 불안으로 비정상 종료되었습니다. 다시 시도해주세요.")
+ del alert
+ os._exit(0)
+
+ user_info = scraper.ProcessingUserData()
+ proc1.kill()
+ # 정보를 가져오는 동안, 네트웨크에 제대로 연결되지 않았다면 강제 종료
+ if(user_info == -1):
+ del scraper,cache
+ alert.MessageBox("인터넷 연결 불안으로 비정상 종료되었습니다. 다시 시도해주세요.")
+ del alert
+ os._exit(0)
+ # 정상적으로 읽어왔다면 캐시파일 생성
+ del scraper
+ cache.SaveCache(user_info)
+
+ # (학번).plk 파일 이 있다면, 로딩 창 끄기.
+ else:
+ del scraper
+ proc1.kill()
+
+ user_info = cache.GetCache()
+ display_main = WindowManager(user_info,id)
+ display_main.Run_Main()
+
+
+
+
diff --git a/scraping.py b/scraping.py
new file mode 100644
index 0000000..9d03272
--- /dev/null
+++ b/scraping.py
@@ -0,0 +1,355 @@
+# -*- coding: utf-8 -*-
+import sys
+import io
+sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
+sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
+
+import time
+import re
+
+from selenium import webdriver
+from webdriver_manager.chrome import ChromeDriverManager
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import Select
+from bs4 import BeautifulSoup
+
+
+class Scraper:
+ def __init__(self,headless = False):
+ self.url = "https://klas.kw.ac.kr/"
+ '''
+ 창을 띄우지 않으려면 아래 # options.add_argument('headless') 코드의 주석을 해제하면 됩니다.
+ '''
+ options = webdriver.ChromeOptions()
+ if headless:
+ options.add_argument('headless')
+ self.browser = webdriver.Chrome(ChromeDriverManager().install(),options=options)
+
+ # 화면 전환시 데이터가 바로 로드되지 않아 scraping 되지 않는 문제를 막기위해
+ # scaping 대상 페이지에 도착시 sleep() 사용, 아래 변수는 sleep()에 전달할 인자
+ self.sleeping_time = 0.8
+
+ # self.num_semester: (진행 + 완료) 학기 갯수
+ self.num_semester = 0
+
+ def __del__(self):
+ print()
+ # id와 pw를 입력받아 klas에서 로그인 실행
+ def AcceseKlas(self,id,pw):
+
+ try:
+ # 1. chrome창을 띄어, klas로 이동
+ self.browser.get(self.url)
+ # 2. id, pw 입력
+ self.browser.find_element(By.ID,"loginId").send_keys(id)
+ self.browser.find_element(By.ID,"loginPwd").send_keys(pw)
+ # 3. 로그인 버튼 클릭
+ self.browser.find_element(By.XPATH,"/html/body/div[1]/div/div/div[2]/form/div[2]/button").click()
+
+ try:
+ WebDriverWait(self.browser,3).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'ax-dialog-header')))
+ return -2
+ except:
+ print()
+ except:
+ return -1
+
+ # Klas에서 사용자의 Data를 가져와 가공한다.
+ def ProcessingUserData(self):
+ try:
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'scheduletitle')))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+
+ html = self.browser.page_source
+ soup = BeautifulSoup(html, 'lxml') #html.parser
+ semesters = soup.find("select",{"class":"form-control form-control-sm"}).findAll("option")
+
+ # res: 최종결과(dictionary)
+ # - key : 학기 이름
+ # - value : [팀플,과제,퀴즈,출석정보,성적] (list)
+ self.num_semester = len(semesters)
+ res = {}
+ for sem in semesters:
+ res.update({sem.text:[]})
+ # print(res)
+
+ # academic_participation: 학기별 학업 참여도 (팀플,과제,퀴즈,출석율)
+ # grade_information: 성적정보
+ academic_participation = list()
+ grade_information = list()
+ for semester_i in range(self.num_semester):
+ academic_participation.append(self.ScrapingSubjectData(semester_i))
+ # print('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')
+ # print("학업참여정보",academic_participation)
+ grade_information = self.ScrapingGradeData()
+ # print('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')
+ # print("성적정보",grade_information)
+
+ seme_name = list(res.keys())
+ for i in range(self.num_semester):
+ # res[seme_name[i]] += grade_information[i]
+
+ # 의지력 = (출석 / (출석+지각+결석)) * 100
+ will_power = (academic_participation[i][6] * 100)// (academic_participation[i][6] + academic_participation[i][7] + academic_participation[i][8])
+ res[seme_name[i]].append(int(will_power))
+
+ # 지능 = ((실제 취득 학점) / (취득 가능 학점) ) * 100
+ intellect = grade_information[i][1]
+ res[seme_name[i]].append(int(intellect))
+
+ # 생존력 = (수강과목 갯수 에 따른 점수) + 총 과제,퀴즈 갯수(50개 이상 이면 50점)
+ # 수강 과목 갯수:
+ # (
+ # 6 개 이상: 50점
+ # 5 개 : 40점
+ # 4 개 : 32점
+ # 3 개 : 24점
+ # 2 개 : 16점
+ # 1 개 : 8점
+ # )
+ viability = academic_participation[i][1] + academic_participation[i][3] + academic_participation[i][5]
+ if viability > 50:
+ viability = 50
+ if grade_information[i][0] >= 6:
+ viability += 50
+ elif grade_information[i][0] == 5:
+ viability += 40
+ elif grade_information[i][0] == 4:
+ viability += 32
+ elif grade_information[i][0] == 3:
+ viability += 24
+ elif grade_information[i][0] == 2:
+ viability += 16
+ else:
+ viability += 8
+ res[seme_name[i]].append(int(viability))
+
+ # 근명성 = (제출한 과제 수 + 제출한 퀴즈 수) / (총 과제 수 + 총 퀴즈 수) * 100
+ diligence = 100*(academic_participation[i][0] + academic_participation[i][2] + academic_participation[i][4])
+ diligence = diligence // (academic_participation[i][1] + academic_participation[i][3] + academic_participation[i][5])
+ res[seme_name[i]].append(int(diligence))
+
+ # 가성비 = (지능 / 의지력)
+ # 100 이상이면 100으로 넣는다.
+ luck = (intellect*100) // will_power
+ if luck > 100 : luck = 100
+ res[seme_name[i]].append(int(luck))
+
+ # 파라미터 중, 음수 값이 하나라도 있다면 성적처리가 아직 안됐거나, 잘못 기입된 된 것
+ # 해당 학기 정보는 삭제한다.
+ if (viability<0 or intellect<0 or viability<0 or diligence<0 or luck<0 ) :
+ del(res[seme_name[i]])
+ return res
+ except:
+ return -1
+
+ # 특정학기를 입력받아 학업 참여도를 가져온다.
+ def ScrapingSubjectData(self,semester_idx):
+ self.ChangeSemester(semester_idx)
+
+ html = self.browser.page_source
+ soup = BeautifulSoup(html, 'lxml') #html.parser
+
+ # each_subject_list: 과목별 정보
+ # [첫번째 과목정보, 두번째 과목정보,…] 순으로 정리되어있다.
+ each_subject_list = list()
+
+ # res: 반환할 결과
+ # [수행한 팀플과제 수, 총 팀플과제 수,
+ # 제출한 개인과제 수, 총 개인과제 수,
+ # 제출한 퀴즈 수, 총 퀴즈 수,
+ # 출석횟수, 결석횟수, 지각횟수] 순으로 정리되어있다.
+ res = [0 for _ in range(9)]
+
+ # subjects: 특정 학기에 들은 과목들에 정보가 들어있다.
+ subjects = soup.find("ul",{"class":"subjectlist listbox"}).findAll("li")
+
+ for i in range(1,len(subjects)+1):
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'scheduletitle')))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+ self.ChangeSemester(semester_idx)
+ xpath = "/html/body/main/div/div/div/div/div[1]/div[2]/ul/li["+str(i)+"]"
+ each_subject_list.append(self.GetEachSubjectsData(xpath))
+
+ for i in range(len(subjects)):
+ if each_subject_list[i] == -1: #오류가 반환되었을 시, 일단 그냥 패쓰
+ continue
+ for j in range(9):
+ res[j] += each_subject_list[i][j]
+
+ return res
+
+ # 학기별 학업 참여도를 추출할 때, 학기 인덱스를 입력받아 해당 학기 페이지로 이동
+ def ChangeSemester(self, semester_idx):
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'scheduletitle')))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+ element_dropdown = self.browser.find_element(By.CLASS_NAME,"form-control.form-control-sm")
+ select_semster = Select(element_dropdown)
+ select_semster.select_by_index(semester_idx)
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'scheduletitle')))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+
+ # 특정 한 학기의 특정 한 과목의 학업 참여도를 종합하여 반환한다. (팀플, 과제, 퀴즈, 출석율)
+ # 가장 최근 학기부터 첫 학기 순으로 반환한다.
+ def GetEachSubjectsData(self, xpath):
+
+ # # 만약 e-learing이면 0000000 반환하고 걍 패쓰
+ # if (self.WaitPageChange_Xpath(xpath+"/div[2]/button")):
+ # return [0,0,0,0,0,0,0,0,0]
+
+ self.browser.find_element(By.XPATH,xpath).click()
+
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,'oval')))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+
+ html = self.browser.page_source
+ soup = BeautifulSoup(html, 'lxml')
+
+ res = list() # 최종 결과를 담을 변수
+
+ # 학업참여정보
+ present = soup.select('a > span.oval')
+ team_project = re.findall(r'\d+', str(present[1])) # 팀플
+ assignment = re.findall(r'\d+', str(present[5])) # 과제
+ quiz = re.findall(r'\d+', str(present[6])) # 퀴즈
+
+ # 출결 정보
+ attendance = len(soup.select('table > tbody > tr > td > span.memberft01')) # 출석
+ absent = len(soup.select('table > tbody > tr > td > span.memberft04')) # 결석
+ late = len(soup.select('table > tbody > tr > td > span.memberft02')) # 지각
+ vacancy = len(soup.select('table > tbody > tr > td > span.memberft05')) # 공결
+
+ self.browser.get(self.url)
+
+ res = [int(x) for x in team_project]
+ res += [int(x) for x in assignment]
+ res += [int(x) for x in quiz]
+ res.append((attendance + vacancy)) # 공결은 출석으로 포함
+ res.append(absent)
+ res.append(late)
+
+ return res
+
+ # 성적정보를 가져온다.
+ # 가장 최근 학기부터 첫 학기 순으로 반환한다.
+ def ScrapingGradeData(self):
+ try:
+ WebDriverWait(self.browser,8).until(EC.presence_of_all_elements_located((By.CLASS_NAME,"scheduletitle")))
+ time.sleep(self.sleeping_time)
+ except:
+ return 0
+
+ self.browser.find_element(By.XPATH,"/html/body/header/div[1]/div/div[1]/button").click()
+ time.sleep(self.sleeping_time)
+ self.browser.find_element(By.XPATH,"/html/body/header/div[2]/div/div/div[1]/ul/li[2]/ul/li[2]/a").click()
+ time.sleep(self.sleeping_time)
+
+ html = self.browser.page_source
+ soup = BeautifulSoup(html, 'lxml') #html.parser
+
+ tables = soup.select('div.tablelistbox > div#hakbu > table.AType')
+
+ # 학점 가져오기
+ credit = [[] for _ in range(len(tables))]
+ for ta_idx,table in enumerate(tables):
+ tbodies = table.select('td')
+ for tbody in tbodies:
+ if (' 3' in tbody.text):
+ credit[ta_idx].append(3)
+ elif (' 2' in tbody.text):
+ credit[ta_idx].append(2)
+ elif (' 1' in tbody.text):
+ credit[ta_idx].append(1)
+
+ # 등급 가져오기
+ grade = [[] for _ in range(len(tables))]
+ for ta_idx,table in enumerate(tables):
+ tbodies = table.select('td')
+ for tbody in tbodies:
+ if ('A+' in tbody.text):
+ grade[ta_idx].append(4.5)
+ elif ('A0' in tbody.text):
+ grade[ta_idx].append(4.0)
+ elif ('B+' in tbody.text):
+ grade[ta_idx].append(3.5)
+ elif ('B0' in tbody.text):
+ grade[ta_idx].append(3.0)
+ elif ('C+' in tbody.text):
+ grade[ta_idx].append(2.5)
+ elif ('C0' in tbody.text):
+ grade[ta_idx].append(2.0)
+ elif ('D+' in tbody.text):
+ grade[ta_idx].append(1.5)
+ elif ('D0' in tbody.text):
+ grade[ta_idx].append(1.0)
+ elif ('F' in tbody.text):
+ grade[ta_idx].append(0)
+ elif ('P' in tbody.text):
+ grade[ta_idx].append(4.5)
+ elif ('NP' in tbody.text):
+ grade[ta_idx].append(0)
+
+ # res_0: 수강 과목 갯수
+ res_0 = [0 for _ in range(self.num_semester)]
+ for i in range(self.num_semester):
+ res_0[i] = len(credit[i])
+
+ # res_1: 학기별 취득 가능 학점
+ res_1 = [0 for _ in range(self.num_semester)]
+ for i in range(self.num_semester):
+ res_1[i] = sum(credit[i]) * 4.5
+
+ # res_2: 학기별 실제 취득 학점
+ res_2 = [0 for _ in range(self.num_semester)]
+ for i in range(self.num_semester):
+ # 성적처리가 안되었을 경우, -1 삽입
+ if (len(grade[i]) != len(credit[i])):
+ res_2[i] = -1
+ continue
+ for j in range(len(grade[i])):
+ res_2[i] += (grade[i][j] * credit[i][j])
+
+ # res : res_0 , res_1 , res_2 종합
+ # 학기별로 (수강과목 갯수 , 100 * (실제 취득 학점) / (취득 가능 학점) ) 출력
+ res = [[] for _ in range(self.num_semester)]
+ for i in range(self.num_semester):
+ res[i].append( res_0[i] )
+ res[i].append( (res_2[i]* 100) // res_1[i])
+
+ return res
+
+ # 어떤 class 이름을 입력으로 주면 해당 class 이름이 현재 url 페이지에 나타날 때까지 기다린다.
+ # 10 초 안에 나타나면 데이터 로드할 시간을 좀 더 주고 True 반환, 10 초 이상 안나타나며 False 반환
+ def WaitPageChange(self,class_name,term=8):
+ try:
+ WebDriverWait(self.browser,term).until(EC.presence_of_all_elements_located((By.CLASS_NAME,class_name)))
+ time.sleep(self.sleeping_time)
+ except:
+ return False
+ return True
+
+ def WaitPageChange_Xpath(self,xpath_name,term=8):
+ try:
+ WebDriverWait(self.browser,term).until(EC.presence_of_all_elements_located((By.XPATH,xpath_name)))
+ time.sleep(self.sleeping_time)
+ except:
+ return False
+ return True
+
\ No newline at end of file
diff --git a/usr/2017203088.plk b/usr/2017203088.plk
new file mode 100644
index 0000000..546c46b
Binary files /dev/null and b/usr/2017203088.plk differ