mirror of
https://github.com/cqwu-ehall/cqwu-ehall.git
synced 2024-11-25 11:46:46 +00:00
✨ Support get score detail
This commit is contained in:
parent
35e34caa35
commit
e79b7dc429
@ -15,7 +15,7 @@ class Client(Methods):
|
|||||||
password: str = None,
|
password: str = None,
|
||||||
cookie: str = None,
|
cookie: str = None,
|
||||||
cookie_file_path: str = "cookie.txt",
|
cookie_file_path: str = "cookie.txt",
|
||||||
timeout: int = 10,
|
timeout: int = 60,
|
||||||
):
|
):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
|
@ -8,3 +8,13 @@ class ExamRound(str, Enum):
|
|||||||
""" 分散考试 """
|
""" 分散考试 """
|
||||||
Concentration = "3"
|
Concentration = "3"
|
||||||
""" 集中考试 """
|
""" 集中考试 """
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreSearchType(str, Enum):
|
||||||
|
""" 成绩查询类型 """
|
||||||
|
All = "1"
|
||||||
|
"""入学以来"""
|
||||||
|
XUENIAN = "2"
|
||||||
|
"""学年"""
|
||||||
|
XUEQI = "3"
|
||||||
|
"""学期"""
|
||||||
|
@ -7,3 +7,7 @@ class CQWUWebVPNError(CQWUEhallError):
|
|||||||
|
|
||||||
class NoExamData(CQWUWebVPNError):
|
class NoExamData(CQWUWebVPNError):
|
||||||
""" 没有检索到对应的考试记录 """
|
""" 没有检索到对应的考试记录 """
|
||||||
|
|
||||||
|
|
||||||
|
class NoScoreDetailData(CQWUWebVPNError):
|
||||||
|
""" 没有检索到对应的成绩明细记录 """
|
||||||
|
@ -3,6 +3,7 @@ from httpx import URL
|
|||||||
from .get_calendar import GetCalendar
|
from .get_calendar import GetCalendar
|
||||||
from .get_calendar_change import GetCalendarChange
|
from .get_calendar_change import GetCalendarChange
|
||||||
from .get_exam_calendar import GetExamCalendar
|
from .get_exam_calendar import GetExamCalendar
|
||||||
|
from .get_score_detail import GetScoreDetail
|
||||||
from .get_selected_courses import GetSelectedCourses
|
from .get_selected_courses import GetSelectedCourses
|
||||||
from .login_jwmis import LoginJwmis
|
from .login_jwmis import LoginJwmis
|
||||||
from .login_webvpn import LoginWebVPN
|
from .login_webvpn import LoginWebVPN
|
||||||
@ -12,6 +13,7 @@ class WebVPN(
|
|||||||
GetCalendar,
|
GetCalendar,
|
||||||
GetCalendarChange,
|
GetCalendarChange,
|
||||||
GetExamCalendar,
|
GetExamCalendar,
|
||||||
|
GetScoreDetail,
|
||||||
GetSelectedCourses,
|
GetSelectedCourses,
|
||||||
LoginJwmis,
|
LoginJwmis,
|
||||||
LoginWebVPN,
|
LoginWebVPN,
|
||||||
|
136
cqwu/methods/webvpn/get_score_detail.py
Normal file
136
cqwu/methods/webvpn/get_score_detail.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
from typing import Union, List
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup, Tag
|
||||||
|
|
||||||
|
import cqwu
|
||||||
|
from cqwu.enums import ScoreSearchType
|
||||||
|
from cqwu.errors import NoScoreDetailData
|
||||||
|
from cqwu.types import ScoreDetail, ScoreDetailInfo, ScoreDetailCourse, ScoreDetailTotal
|
||||||
|
|
||||||
|
|
||||||
|
class GetScoreDetail:
|
||||||
|
async def get_score_detail(
|
||||||
|
self: "cqwu.Client",
|
||||||
|
search_type: Union[str, ScoreSearchType] = ScoreSearchType.XUEQI,
|
||||||
|
xue_nian: int = None,
|
||||||
|
xue_qi: int = None,
|
||||||
|
use_model: bool = False,
|
||||||
|
) -> Union[str, ScoreDetail]:
|
||||||
|
""" 获取学业成绩 """
|
||||||
|
xue_nian = xue_nian or self.xue_nian
|
||||||
|
xue_qi = xue_qi or self.xue_qi
|
||||||
|
search_type = ScoreSearchType(search_type)
|
||||||
|
jw_html = await self.login_jwmis()
|
||||||
|
jw_host = self.get_web_vpn_host(jw_html.url, https=True)
|
||||||
|
jw_url = f"{jw_host}/cqwljw/student/xscj.stuckcj_data.jsp"
|
||||||
|
headers = {
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,zh-Hans;q=0.8,und;q=0.7,en;q=0.6,zh-Hant;q=0.5,ja;q=0.4',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'DNT': '1',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Referer': f'{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403',
|
||||||
|
'Sec-Fetch-Dest': 'iframe',
|
||||||
|
'Sec-Fetch-Mode': 'navigate',
|
||||||
|
'Sec-Fetch-Site': 'same-origin',
|
||||||
|
'Sec-Fetch-User': '?1',
|
||||||
|
'Upgrade-Insecure-Requests': '1',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||||
|
'sec-ch-ua': '"Chromium";v="112", "Not:A-Brand";v="99"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"Windows"',
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"sjxz": f"sjxz{search_type.value}",
|
||||||
|
"ysyx": "yxcj",
|
||||||
|
"zx": "1",
|
||||||
|
"fx": "1",
|
||||||
|
"btnExport": "%B5%BC%B3%F6",
|
||||||
|
"rxnj": str(xue_nian),
|
||||||
|
"xn": str(xue_nian),
|
||||||
|
'xn1': str(xue_nian + 1),
|
||||||
|
"xq": str(xue_qi),
|
||||||
|
"ysyxS": "on",
|
||||||
|
"sjxzS": "on",
|
||||||
|
"zxC": "on",
|
||||||
|
"fxC": "on",
|
||||||
|
"xsjd": "1",
|
||||||
|
}
|
||||||
|
jw_html = await self.request.post(jw_url, data=data, headers=headers, timeout=60, follow_redirects=True)
|
||||||
|
if "没有检索到记录!" in jw_html.text:
|
||||||
|
raise NoScoreDetailData("没有检索到记录!")
|
||||||
|
jw_html = jw_html.text.replace("""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""", "")
|
||||||
|
jw_html = jw_html.replace("""<script language='javascript' type='text/javascript' src='../js/Print.js'></script>""", "")
|
||||||
|
jw_html = jw_html.replace("charset=GBK", 'charset=UTF-8')
|
||||||
|
if not use_model:
|
||||||
|
return jw_html
|
||||||
|
return parse_html(jw_html)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_info(tag: Tag) -> ScoreDetailInfo:
|
||||||
|
divs = tag.find_all("div")
|
||||||
|
|
||||||
|
def get_text(_tag) -> str:
|
||||||
|
return (":".join(_tag.text.split(":")[1:])).strip()
|
||||||
|
|
||||||
|
return ScoreDetailInfo(
|
||||||
|
college=get_text(divs[0]),
|
||||||
|
major=get_text(divs[1]),
|
||||||
|
level=get_text(divs[2]),
|
||||||
|
class_name=get_text(divs[3]),
|
||||||
|
student_id=get_text(divs[4]),
|
||||||
|
student_name=get_text(divs[5]),
|
||||||
|
date=get_text(divs[6]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_course(tag: Tag) -> List[ScoreDetailCourse]:
|
||||||
|
trs = tag.find_all("tr")
|
||||||
|
courses = []
|
||||||
|
for tr in trs:
|
||||||
|
tds = tr.find_all("td")
|
||||||
|
course = ScoreDetailCourse(
|
||||||
|
id=tds[0].text,
|
||||||
|
name=tds[1].text,
|
||||||
|
credit=tds[2].text,
|
||||||
|
period=tds[3].text,
|
||||||
|
type=tds[4].text,
|
||||||
|
nature=tds[5].text,
|
||||||
|
exam_method=tds[6].text,
|
||||||
|
score=tds[7].text,
|
||||||
|
score_point=tds[8].text,
|
||||||
|
grade_point=tds[9].text,
|
||||||
|
gp=tds[10].text,
|
||||||
|
remark=tds[11].text,
|
||||||
|
)
|
||||||
|
courses.append(course)
|
||||||
|
return courses
|
||||||
|
|
||||||
|
|
||||||
|
def parse_total(tag: Tag) -> ScoreDetailTotal:
|
||||||
|
tr = tag.find_all("tr")[-1]
|
||||||
|
tds = tr.find_all("td")
|
||||||
|
return ScoreDetailTotal(
|
||||||
|
num=tds[1].text,
|
||||||
|
credit=tds[2].text,
|
||||||
|
get_credit=tds[3].text,
|
||||||
|
grade_point=tds[4].text,
|
||||||
|
gp=tds[5].text,
|
||||||
|
gpa=tds[6].text,
|
||||||
|
score_avg=tds[7].text,
|
||||||
|
weighted_grade_avg=tds[8].text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_html(html: str) -> ScoreDetail:
|
||||||
|
soup = BeautifulSoup(html, "lxml")
|
||||||
|
group_div = soup.find("div", {"group": "group"})
|
||||||
|
info = parse_info(group_div)
|
||||||
|
tbody_s = soup.find_all("tbody")
|
||||||
|
courses = []
|
||||||
|
for tbody in tbody_s[:-1]:
|
||||||
|
courses += parse_course(tbody)
|
||||||
|
total = parse_total(tbody_s[-1])
|
||||||
|
return ScoreDetail(info=info, courses=courses, total=total)
|
@ -4,4 +4,5 @@ from .epay import PayBill, PayBillPage
|
|||||||
from .exam import AiExam
|
from .exam import AiExam
|
||||||
from .pay import PayProject, PayProjectDetail, PayUser
|
from .pay import PayProject, PayProjectDetail, PayUser
|
||||||
from .score import Score
|
from .score import Score
|
||||||
|
from .score_detail import ScoreDetail, ScoreDetailInfo, ScoreDetailCourse, ScoreDetailTotal
|
||||||
from .user import User
|
from .user import User
|
||||||
|
75
cqwu/types/score_detail.py
Normal file
75
cqwu/types/score_detail.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreDetailInfo(BaseModel):
|
||||||
|
college: str = ""
|
||||||
|
"""院(系)/部"""
|
||||||
|
major: str = ""
|
||||||
|
"""专业"""
|
||||||
|
level: str = ""
|
||||||
|
"""培养层次"""
|
||||||
|
class_name: str = ""
|
||||||
|
"""行政班级"""
|
||||||
|
student_id: str = ""
|
||||||
|
"""学号"""
|
||||||
|
student_name: str = ""
|
||||||
|
"""姓名"""
|
||||||
|
date: str = ""
|
||||||
|
"""打印时间"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreDetailCourse(BaseModel):
|
||||||
|
id: int = 0
|
||||||
|
"""序号"""
|
||||||
|
name: str = ""
|
||||||
|
"""课程/环节"""
|
||||||
|
credit: float = 0.0
|
||||||
|
"""学分"""
|
||||||
|
period: int = 0
|
||||||
|
"""总学时"""
|
||||||
|
type: str = ""
|
||||||
|
"""类别"""
|
||||||
|
nature: str = ""
|
||||||
|
"""修读性质"""
|
||||||
|
exam_method: str = ""
|
||||||
|
"""考核方式"""
|
||||||
|
score: float = 0.0
|
||||||
|
"""成绩"""
|
||||||
|
score_point: float = 0.0
|
||||||
|
"""获得学分"""
|
||||||
|
grade_point: float = 0.0
|
||||||
|
"""绩点"""
|
||||||
|
gp: float = 0.0
|
||||||
|
"""学分绩点"""
|
||||||
|
remark: str = ""
|
||||||
|
"""备注"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreDetailTotal(BaseModel):
|
||||||
|
num: int = 0
|
||||||
|
"""修读课程环节数"""
|
||||||
|
credit: float = 0.0
|
||||||
|
"""学分"""
|
||||||
|
get_credit: float = 0.0
|
||||||
|
"""获得学分"""
|
||||||
|
grade_point: float = 0.0
|
||||||
|
"""获得绩点"""
|
||||||
|
gp: float = 0.0
|
||||||
|
"""获得学分绩点"""
|
||||||
|
gpa: float = 0.0
|
||||||
|
"""平均学分绩点"""
|
||||||
|
score_avg: float = 0.0
|
||||||
|
"""平均成绩"""
|
||||||
|
weighted_grade_avg: float = 0.0
|
||||||
|
"""加权平均成绩"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreDetail(BaseModel):
|
||||||
|
info: ScoreDetailInfo
|
||||||
|
"""基本信息"""
|
||||||
|
courses: List[ScoreDetailCourse]
|
||||||
|
"""课程信息"""
|
||||||
|
total: ScoreDetailTotal
|
||||||
|
"""总计"""
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="cqwu", # 用自己的名替换其中的YOUR_USERNAME_
|
name="cqwu", # 用自己的名替换其中的YOUR_USERNAME_
|
||||||
version="0.0.16", # 包版本号,便于维护版本
|
version="0.0.17", # 包版本号,便于维护版本
|
||||||
author="omg-xtao", # 作者,可以写自己的姓名
|
author="omg-xtao", # 作者,可以写自己的姓名
|
||||||
author_email="xtao@xtaolink.cn", # 作者联系方式,可写自己的邮箱地址
|
author_email="xtao@xtaolink.cn", # 作者联系方式,可写自己的邮箱地址
|
||||||
description="A cqwu ehall client.", # 包的简述
|
description="A cqwu ehall client.", # 包的简述
|
||||||
|
Loading…
Reference in New Issue
Block a user