Season Study has officially been released and is maintained as open source !
1 https://github.com/cykzht/season-zhixue
Introduction
Season Study – a beautiful grade query website.
Just like its name suggests, it is a program that changes with the seasons, randomly selecting a new season as the theme each time it starts.
Program Principles
The overall framework of the website is developed using Python + Flask + HTML + layui. The birth of this project is inseparable from open-source libraries such as zhixuewang , layui , Flask , etc. Thank you to these developers for their contributions to the open-source community.
Web Services
Using Python’s Flask framework to return page information.
1 2 3 4 5 6 7 8 9 from flask import *app = Flask(__name__, static_url_path='' ) @app.route('/' , methods=['GET' , 'POST' ] ) def index (): """Return the login page""" return render_template('index.html' ) app.run(host='0.0.0.0' , port=5000 )
Season Update
Randomly select a season and return the corresponding colors and background images.
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 def get_season (): ''' Randomly select a season slog: Season name pic: Background image color: Theme color ''' ran = random.randint(1 , 4 ) if (ran == 1 ): slog = 'Graduation Network – Autumn' color = '#e5bc00' pic = 'images/web_login_bg1.webp' elif (ran == 2 ): slog = 'Graduation Network – Summer' color = '#50e45c' pic = 'images/web_login_bg2.webp' elif (ran == 3 ): slog = 'Graduation Network – Spring' color = '#f1beea' pic = 'images/web_login_bg3.webp' elif (ran == 4 ): slog = 'Graduation Network – Winter' color = '#27A9E3' pic = 'images/web_login_bg4.webp' return slog, pic, color
User Login
Retrieve the website’s session and user information (from the zhixuewang library).
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 from zhixuewang.student import StudentAccountfrom zhixuewang.exceptions import UserNotFoundError, UserOrPassError, LoginError, RoleErrorimport jsonimport requestsdef get_session (username: str , password: str , _type : str = "auto" ) -> requests.Session: """Get session through username and password""" if len (password) != 32 : password = encode_password(password) session = get_basic_session() r = session.get(Url.SSO_URL) json_obj = json.loads(r.text.strip().replace("\\" , "" ).replace("'" , "" )[1 :-1 ]) if json_obj["code" ] != 1000 : raise LoginError(json_obj["data" ]) lt = json_obj["data" ]["lt" ] execution = json_obj["data" ]["execution" ] r = session.get(Url.SSO_URL, params={ "encode" : "true" , "sourceappname" : "tkyh,tkyh" , "_eventId" : "submit" , "appid" : "zx-container-client" , "client" : "web" , "type" : "loginByNormal" , "key" : _type , "lt" : lt, "execution" : execution, "customLogoutUrl" : "https://www.zhixue.com/login.html" , "username" : username, "password" : password }) json_obj = json.loads(r.text.strip().replace("\\" , "" ).replace("'" , "" )[1 :-1 ]) if json_obj["code" ] != 1001 : if json_obj["code" ] == 1002 : raise UserOrPassError() if json_obj["code" ] == 2009 : raise UserNotFoundError() raise LoginError(json_obj["data" ]) ticket = json_obj["data" ]["st" ] session.post(Url.SERVICE_URL, data={ "action" : "login" , "ticket" : ticket, }) session.cookies.set ("uname" , base64_encode(username)) session.cookies.set ("pwd" , base64_encode(password)) return session def login_student (username: str , password: str ) -> StudentAccount: """Log in to the student account through username and password""" session = get_session(username, password) student = StudentAccount(session) return student.set_base_info() zxw = login_student(username, password)
Login Implementation
Using HTML’s form submission to POST requests, returning to Flask for login.
Frontend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <form class ="layui-form" method ="post" action ="{{url_for('index')}}" autocomplete ="on" > <div class ="layui-form-item" > <input name ="username" class ="layui-input" style ="height: 45px;" placeholder ="Username" required ="" type ="text" > </div > <div class ="layui-form-item" > <input name ="password" class ="layui-input" style ="height: 45px;" placeholder ="Password" required ="" type ="password" > </div > <label > Remember username</label > <input type ="checkbox" name ="remember_user" id ="remember_user" lay-skin ="primary" > <div class ="layui-form-item" > <p align ="center" style ="font-size:20px;color:#FF0000" > {{ message }}</p > </div > <div class ="layui-form-item" > <input lay-submit lay-filter ="login" class ="layui-btn layui-btn-lg layui-btn-normal" value ="Login" style ="width:100%;font-size: 18px;height: 48px;" type ="submit" name ="login" > </div > </form >
Backend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from zhixuewang import login_studentif (request.method == 'POST' ): username = request.form.get("username" ) password = request.form.get("password" ) session['username' ] = username session['password' ] = password username = session.get('username' ) password = session.get('password' ) exam = request.args.get('exam' ) try : zxw = login_student(username, password) exams = zxw.get_exams() except Exception as e: session.clear() return render_template("index.html" , season=season, message=e)
Exam Retrieval
Call the API to retrieve all exams.
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 import jsonfrom typing import List , Tuple , Union from zhixuewang.models import (ExtendedList, Exam, Mark, Subject, SubjectScore, StuClass, School, Sex, Grade, Phase, ExtraRank, ExamInfo, StuPerson, StuPersonList) from zhixuewang.exceptions import UserDefunctError, PageConnectionError, PageInformationErrorfrom json import JSONDecodeErrordef get_page_exam (self, page_index: int ) -> Tuple [ExtendedList[Exam], bool ]: """Get the exam list for the specified page number""" self .update_login_status() exams: ExtendedList[Exam] = ExtendedList() r = self ._session.get(Url.GET_EXAM_URL, params={ "pageIndex" : page_index, "pageSize" : 10 }, headers=self ._get_auth_header()) if not r.ok: raise PageConnectionError( f"get_page_exam出错, 状态码为{r.status_code} " ) try : json_data = r.json()["result" ] for exam_data in json_data["examList" ]: exam = Exam( id =exam_data["examId" ], name=exam_data["examName" ] ) exam.create_time = exam_data["examCreateDateTime" ] exams.append(exam) hasNextPage: bool = json_data["hasNextPage" ] except (JSONDecodeError, KeyError) as e: raise PageInformationError( f"get_page_exam中网页内容发生改变, 错误为{e} , 内容为\n{r.text} " ) return exams, hasNextPage def get_exams (self ) -> ExtendedList[Exam]: """Get all exams""" if len (self .exams) > 0 : latest_exam = self .get_latest_exam() if self .exams[0 ].id == latest_exam.id : return self .exams exams: ExtendedList[Exam] = ExtendedList() i = 1 check = True while check: cur_exams, check = self .get_page_exam(i) exams.extend(cur_exams) i += 1 self .exams = exams return exams
Grade Retrieval
Call the API to retrieve the latest grade or the grade for a specified exam (this version is a modified version).
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 import jsonfrom typing import List , Tuple , Union from zhixuewang.models import (ExtendedList, Exam, Mark, Subject, SubjectScore, StuClass, School, Sex, Grade, Phase, ExtraRank, ExamInfo, StuPerson, StuPersonList) from zhixuewang.exceptions import UserDefunctError, PageConnectionError, PageInformationErrorfrom json import JSONDecodeErrordef __get_self_mark (self, exam: Exam, has_total_score: bool ) -> Mark: self .update_login_status() mark = Mark(exam=exam, person=self ) r = self ._session.get(Url.GET_MARK_URL, params={"examId" : exam.id }, headers=self ._get_auth_header()) if not r.ok: raise PageConnectionError( f"__get_self_mark中出错, 状态码为{r.status_code} " ) try : json_data = r.json() json_data = json_data["result" ] for subject in json_data["paperList" ]: subject_score = SubjectScore( score=subject["userScore" ], subject=Subject( id =subject["paperId" ], name=subject["subjectName" ], code=subject["subjectCode" ], standard_score=subject["standardScore" ], exam_id=exam.id ), person=StuPerson() ) mark.append(subject_score) total_score = json_data.get("totalScore" ) if has_total_score and total_score: subject_score = SubjectScore( score=total_score["userScore" ], subject=Subject( id ="" , name=total_score["subjectName" ], code="99" , standard_score=total_score["standardScore" ], exam_id=exam.id , ), person=StuPerson(), class_rank=exam.class_rank, grade_rank=exam.grade_rank ) mark.append(subject_score) self ._set_exam_rank(mark) except (JSONDecodeError, KeyError) as e: if (not mark): raise PageInformationError("The grades for this exam cannot be queried" ) return mark return mark def get_self_mark (self, exam_data: Union [Exam, str ] = "" , has_total_score: bool = True ) -> Mark: """Get the grade for the specified exam""" exam = self .get_exam(exam_data) if exam is None : return Mark() return self .__get_self_mark(exam, has_total_score) exams = zxw.get_exams() a = zxw.get_self_mark(exams[0 ])
Table Presentation
Generate a table based on the returned grades.
Frontend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div class ="login" > <center > <table border ="1px" width ="300" > <caption style ="font-size: 23px" > {{title}} </caption > <tr style ="background: #ee0000" > <th style ="font-size: 20px" > Subject</th > <th style ="font-size: 20px" > Score</th > </tr > {% for row in data %} <tr > <th style ="font-size: 20px" > {{ row[0] }}</th > <th style ="font-size: 20px" > {{ row[1] }}</th > </tr > {% endfor %} </table > </center > </div >
Backend
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 if len (exams) != 0 : data = [] examlist = [] for i in exams: data.append(i.id ) examlist.append((i.id , i.name)) if (exam): if (exam in data): try : a = zxw.get_self_mark(exam) except Exception as e: return render_template("examlist.html" , data=examlist, season=season, avatar=avatar, message="The exam has not been scanned for this session" ) msg = a.person.name+'-' +a.exam.name data = [] for i in a: if (i.class_rank): data.append((i.subject.name, i.score, i.class_rank)) else : data.append((i.subject.name, i.score)) return render_template("zhixue.html" , data=data, season=season, title=msg, avatar=avatar) else : return render_template("examlist.html" , data=examlist, season=season, avatar=avatar, message="This exam cannot be queried" ) else : return render_template("examlist.html" , data=examlist, season=season, avatar=avatar) else : return render_template("index.html" , season=season, message='No exams detected' )
Automatic Login
Using layui framework for secondary development. Store the account and password in the user’s device via cookies, and automatically fill them in each time you log in (some browsers do not support cookies).
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 <script src="js/jquery-3.6.0.min.js" ></script> <script src ="js/jquery.cookie.js" > </script > <script src ="js/layui.js" > </script > <script > layui.use (['form' ], function ( ) { var form = layui.form , layer = layui.layer ; if ($.cookie ("remember_user" )) { $("#remember_user" ).prop ("checked" , true ); form.val ("add_form" , { "username" : $.cookie ("user_name" ), "password" : $.cookie ("user_password" ) }) } form.on ('submit(login)' , function (data ) { data = data.field ; if (data.username == '' ) { layer.msg ('Username cannot be empty' ); return false ; } if (data.password == '' ) { layer.msg ('Password cannot be empty' ); return false ; } if (data.remember_user == "on" ) { var user_name = data.username ; var user_password = data.password ; $.cookie ("remember_user" , "true" , { expires : 7 }); $.cookie ("user_name" , user_name, { expires : 7 }); $.cookie ("user_password" , user_password, { expires : 7 }); } else { $.cookie ("remember_user" , "false" , { expires : -1 }); $.cookie ("user_name" , '' , { expires : -1 }); $.cookie ("user_password" , '' , { expires : -1 }); } return true ; }); }); </script >
Usage Instructions
Required Environment
Install using pip commands.
1 2 pip3 install flask pip3 install zhixuewang
Enable the Program
After installing the required libraries, run the Python program (ready to use out of the box).
Accesshttp://127.0.0.1:5000/ to visit the Season Study website.
Enter the account and password for a school’s website to query the latest exam results.
Deploy the Program
If you want to run the program on a server, it is recommended to start it using WSGI method.
Add the following to the code:
1 2 3 4 5 from gevent import pywsgiserver = pywsgi.WSGIServer(('0.0.0.0' , 5000 ), app) server.serve_forever()
Or use gunicorn to start.
app is the flask startup python file, we use app.py here,
app is the flask application instance, we named it app in app.py .