Support get score detail

This commit is contained in:
xtaodada 2023-10-17 21:54:16 +08:00
parent 35e34caa35
commit e79b7dc429
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
8 changed files with 230 additions and 2 deletions

View File

@ -15,7 +15,7 @@ class Client(Methods):
password: str = None,
cookie: str = None,
cookie_file_path: str = "cookie.txt",
timeout: int = 10,
timeout: int = 60,
):
self.username = username
self.password = password

View File

@ -8,3 +8,13 @@ class ExamRound(str, Enum):
""" 分散考试 """
Concentration = "3"
""" 集中考试 """
class ScoreSearchType(str, Enum):
""" 成绩查询类型 """
All = "1"
"""入学以来"""
XUENIAN = "2"
"""学年"""
XUEQI = "3"
"""学期"""

View File

@ -7,3 +7,7 @@ class CQWUWebVPNError(CQWUEhallError):
class NoExamData(CQWUWebVPNError):
""" 没有检索到对应的考试记录 """
class NoScoreDetailData(CQWUWebVPNError):
""" 没有检索到对应的成绩明细记录 """

View File

@ -3,6 +3,7 @@ from httpx import URL
from .get_calendar import GetCalendar
from .get_calendar_change import GetCalendarChange
from .get_exam_calendar import GetExamCalendar
from .get_score_detail import GetScoreDetail
from .get_selected_courses import GetSelectedCourses
from .login_jwmis import LoginJwmis
from .login_webvpn import LoginWebVPN
@ -12,6 +13,7 @@ class WebVPN(
GetCalendar,
GetCalendarChange,
GetExamCalendar,
GetScoreDetail,
GetSelectedCourses,
LoginJwmis,
LoginWebVPN,

View 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)

View File

@ -4,4 +4,5 @@ from .epay import PayBill, PayBillPage
from .exam import AiExam
from .pay import PayProject, PayProjectDetail, PayUser
from .score import Score
from .score_detail import ScoreDetail, ScoreDetailInfo, ScoreDetailCourse, ScoreDetailTotal
from .user import User

View 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
"""总计"""

View File

@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup(
name="cqwu", # 用自己的名替换其中的YOUR_USERNAME_
version="0.0.16", # 包版本号,便于维护版本
version="0.0.17", # 包版本号,便于维护版本
author="omg-xtao", # 作者,可以写自己的姓名
author_email="xtao@xtaolink.cn", # 作者联系方式,可写自己的邮箱地址
description="A cqwu ehall client.", # 包的简述