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, 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

View File

@ -8,3 +8,13 @@ class ExamRound(str, Enum):
""" 分散考试 """ """ 分散考试 """
Concentration = "3" 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 NoExamData(CQWUWebVPNError):
""" 没有检索到对应的考试记录 """ """ 没有检索到对应的考试记录 """
class NoScoreDetailData(CQWUWebVPNError):
""" 没有检索到对应的成绩明细记录 """

View File

@ -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,

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 .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

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( 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.", # 包的简述