init2
This commit is contained in:
244
main.py
244
main.py
@ -1,38 +1,32 @@
|
|||||||
import base64
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import dotenv
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from time import sleep,time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup, Tag
|
||||||
|
from os import environ
|
||||||
|
import pandas as pd
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from os import environ
|
|
||||||
from pathlib import Path
|
|
||||||
from time import sleep, time
|
|
||||||
from typing import Callable, List, Optional
|
|
||||||
|
|
||||||
import dotenv
|
|
||||||
import pandas as pd
|
|
||||||
import requests
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from serverchan_sdk import sc_send as _sc_send
|
from serverchan_sdk import sc_send as _sc_send
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志
|
||||||
logging.basicConfig(
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
Path("logs").mkdir(exist_ok=True) # 确保日志目录存在
|
|
||||||
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
file = logging.FileHandler(f"logs/lessons_{now}.log", mode="w", encoding="utf-8")
|
file = logging.FileHandler(f'logs/lessons_{now}.log', mode='w', encoding='utf-8')
|
||||||
file.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
file.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||||
file.setLevel(logging.INFO)
|
file.setLevel(logging.INFO)
|
||||||
logger.addHandler(file) # 日志输出到文件
|
logger.addHandler(file) # 日志输出到文件
|
||||||
|
|
||||||
|
|
||||||
class LessonsException(Exception):
|
class LessonsException(Exception):
|
||||||
"""自定义异常类"""
|
"""自定义异常类"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -46,52 +40,59 @@ def sc_send(title: str, desp: str):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"发送失败: {e}")
|
logger.error(f"发送失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
class Lessons:
|
class Lessons:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = requests.session()
|
self.session = requests.session()
|
||||||
|
self.lessons_list: List[Dict[str, str]] = []
|
||||||
|
self.username: Optional[str] = None
|
||||||
self.term: Optional[str] = None
|
self.term: Optional[str] = None
|
||||||
self.fajhh: Optional[str] = None
|
self.fajhh: Optional[str] = None
|
||||||
|
self.token: Optional[str] = None
|
||||||
|
|
||||||
# 加载环境变量
|
# 加载环境变量
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
# 检查必需的环境变量
|
# 检查必需的环境变量
|
||||||
required_keys = [
|
required_keys = ["uname", "password", "recap_username", "recap_password"]
|
||||||
"uname",
|
|
||||||
"password",
|
|
||||||
"recap_username",
|
|
||||||
"recap_password",
|
|
||||||
"FILE",
|
|
||||||
"SC_KEY",
|
|
||||||
]
|
|
||||||
for key in required_keys:
|
for key in required_keys:
|
||||||
if not environ.get(key):
|
if not environ.get(key):
|
||||||
raise LessonsException(f"请在环境变量中设置{key}")
|
raise LessonsException(f"请在环境变量中设置{key}")
|
||||||
|
|
||||||
self.base = environ.get("base", "http://jwstudent.lnu.edu.cn")
|
self.base = environ.get("base", "http://jwstudent.lnu.edu.cn")
|
||||||
|
|
||||||
def _retry_request(
|
def _retry(self, max_retries: int = 5):
|
||||||
self, func, max_retries: int = 10, error_msg: str = "请求失败"
|
retry = 0
|
||||||
) -> requests.Response:
|
def _wrapper(func, *args, **kwargs):
|
||||||
|
nonlocal retry
|
||||||
|
"""装饰器:重试请求"""
|
||||||
|
try:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"重试因为 {e}")
|
||||||
|
retry += 1
|
||||||
|
if retry > max_retries:
|
||||||
|
raise LessonsException("重试次数超过限制")
|
||||||
|
else:
|
||||||
|
return _wrapper(func, *args, **kwargs)
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _retry_request(self, func, max_retries: int = 10, error_msg: str = "请求失败") -> requests.Response:
|
||||||
"""通用的请求重试方法"""
|
"""通用的请求重试方法"""
|
||||||
for attempt in range(1, max_retries + 1):
|
for attempt in range(1, max_retries + 1):
|
||||||
try:
|
try:
|
||||||
response = func()
|
response = func()
|
||||||
self.judge_logout(response)
|
self.judge_logout(response)
|
||||||
return response
|
return response
|
||||||
except (
|
except (requests.ConnectionError, requests.HTTPError, requests.Timeout) as e:
|
||||||
requests.ConnectionError,
|
|
||||||
requests.HTTPError,
|
|
||||||
requests.Timeout,
|
|
||||||
) as e:
|
|
||||||
logger.warning(f"{error_msg}!{type(e).__name__}: {e}")
|
logger.warning(f"{error_msg}!{type(e).__name__}: {e}")
|
||||||
if attempt < max_retries:
|
if attempt < max_retries:
|
||||||
logger.info(f"第{attempt}次重试")
|
logger.info(f'第{attempt}次重试')
|
||||||
else:
|
else:
|
||||||
raise LessonsException(f"{error_msg}!请检查网络连接!")
|
raise LessonsException(f"{error_msg}!请检查网络连接!")
|
||||||
|
|
||||||
# 这行不会被执行,但为了类型检查
|
# 这行不会被执行,但为了类型检查
|
||||||
raise LessonsException(f"{error_msg}!重试次数耗尽")
|
raise LessonsException(f"{error_msg}!重试次数耗尽")
|
||||||
|
|
||||||
@ -100,43 +101,46 @@ class Lessons:
|
|||||||
"""验证码识别"""
|
"""验证码识别"""
|
||||||
recap_username = environ.get("recap_username")
|
recap_username = environ.get("recap_username")
|
||||||
recap_password = environ.get("recap_password")
|
recap_password = environ.get("recap_password")
|
||||||
|
|
||||||
if not recap_username or not recap_password:
|
if not recap_username or not recap_password:
|
||||||
raise LessonsException("验证码识别服务配置不完整")
|
raise LessonsException("验证码识别服务配置不完整")
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"username": recap_username,
|
"username": recap_username,
|
||||||
"password": recap_password,
|
"password": recap_password,
|
||||||
"ID": "04897896",
|
"ID": "04897896",
|
||||||
"b64": b64,
|
"b64": b64,
|
||||||
"version": "3.1.1",
|
"version": "3.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post("http://www.fdyscloud.com.cn/tuling/predict",
|
||||||
"http://www.fdyscloud.com.cn/tuling/predict", json=data, timeout=10
|
json=data, timeout=10)
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
result = response.json()
|
result = response.json()
|
||||||
return result["data"]["result"]
|
return result["data"]["result"]
|
||||||
except (requests.RequestException, KeyError, json.JSONDecodeError) as e:
|
except (requests.RequestException, KeyError, json.JSONDecodeError) as e:
|
||||||
raise LessonsException(f"验证码识别失败: {e}")
|
raise LessonsException(f"验证码识别失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pwd_md5(string: str) -> str:
|
def pwd_md5(string: str) -> str:
|
||||||
md5_part1 = hashlib.md5((string + "{Urp602019}").encode()).hexdigest().lower()
|
md5_part1 = hashlib.md5((string + "{Urp602019}").encode()).hexdigest().lower()
|
||||||
md5_part2 = hashlib.md5(string.encode()).hexdigest().lower()
|
md5_part2 = hashlib.md5(string.encode()).hexdigest().lower()
|
||||||
final_result = md5_part1 + "*" + md5_part2
|
final_result = md5_part1 + '*' + md5_part2
|
||||||
return final_result
|
return final_result
|
||||||
|
|
||||||
|
# @self._r
|
||||||
def _login(self):
|
def _login(self):
|
||||||
"""登录模块"""
|
"""登录模块"""
|
||||||
username = environ.get("uname")
|
username = environ.get("uname")
|
||||||
password = environ.get("password")
|
password = environ.get("password")
|
||||||
|
|
||||||
if not username or not password:
|
if not username or not password:
|
||||||
raise LessonsException("用户名或密码未设置")
|
raise LessonsException("用户名或密码未设置")
|
||||||
|
|
||||||
|
self.username = username
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 获取登录页面的token
|
# 获取登录页面的token
|
||||||
req = self.session.get("http://jwstudent.lnu.edu.cn/login")
|
req = self.session.get("http://jwstudent.lnu.edu.cn/login")
|
||||||
@ -151,9 +155,12 @@ class Lessons:
|
|||||||
req = self.session.get(f"{self.base}/img/captcha.jpg")
|
req = self.session.get(f"{self.base}/img/captcha.jpg")
|
||||||
req.raise_for_status()
|
req.raise_for_status()
|
||||||
im = req.content
|
im = req.content
|
||||||
b64 = base64.b64encode(im).decode("utf-8")
|
b64 = base64.b64encode(im).decode('utf-8')
|
||||||
captcha_code = self.recapture(b64=b64)
|
captcha_code = self.recapture(b64=b64)
|
||||||
|
|
||||||
|
# 保存验证码图片用于调试
|
||||||
|
# with open("captcha.jpg", "wb") as f:
|
||||||
|
# f.write(im)
|
||||||
logger.info(f"验证码识别结果: {captcha_code}")
|
logger.info(f"验证码识别结果: {captcha_code}")
|
||||||
|
|
||||||
hashed_password = self.pwd_md5(password)
|
hashed_password = self.pwd_md5(password)
|
||||||
@ -163,35 +170,53 @@ class Lessons:
|
|||||||
"j_username": username,
|
"j_username": username,
|
||||||
"j_password": hashed_password,
|
"j_password": hashed_password,
|
||||||
"j_captcha": captcha_code,
|
"j_captcha": captcha_code,
|
||||||
"tokenValue": token_value,
|
"tokenValue": token_value
|
||||||
}
|
}
|
||||||
|
|
||||||
# 发送 POST 请求
|
# 发送 POST 请求
|
||||||
url = f"{self.base}/j_spring_security_check"
|
url = f"{self.base}/j_spring_security_check"
|
||||||
headers = {
|
headers = {
|
||||||
"User-Agent": "Mozilla/5.0",
|
"User-Agent": "Mozilla/5.0",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
}
|
}
|
||||||
response = self.session.post(url, data=payload, headers=headers)
|
response = self.session.post(url, data=payload, headers=headers)
|
||||||
|
|
||||||
if "发生错误" in response.text:
|
if "发生错误" in response.text:
|
||||||
err = re.search(r"<strong>发生错误!</strong>(.+)", response.text)
|
err = re.search(r'<strong>发生错误!</strong>(.+)', response.text)
|
||||||
if err:
|
if err:
|
||||||
error_message = err.group(1).strip()
|
error_message = err.group(1).strip()
|
||||||
raise LessonsException(f"登录失败: {error_message}")
|
raise LessonsException(f"登录失败: {error_message}")
|
||||||
raise LessonsException("登录失败")
|
raise LessonsException("登录失败")
|
||||||
|
|
||||||
logger.info("登录成功")
|
logger.info("登录成功")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
raise LessonsException(f"登录过程中网络错误: {e}")
|
raise LessonsException(f"登录过程中网络错误: {e}")
|
||||||
|
|
||||||
|
|
||||||
def judge_logout(self, response: requests.Response):
|
def judge_logout(self, response: requests.Response):
|
||||||
"""检查账号是否在其他地方被登录"""
|
"""检查账号是否在其他地方被登录"""
|
||||||
if response.url == f"{self.base}/login?errorCode=concurrentSessionExpired":
|
if response.url == f"{self.base}/login?errorCode=concurrentSessionExpired":
|
||||||
raise LessonsException("有人登录了您的账号!")
|
raise LessonsException("有人登录了您的账号!")
|
||||||
|
|
||||||
|
def judge_choose(self, bs: BeautifulSoup):
|
||||||
|
"""判断是否可以选课"""
|
||||||
|
alert = bs.find("div", {"class": "alert alert-block alert-danger"})
|
||||||
|
if alert is not None:
|
||||||
|
raise LessonsException("对不起,当前为非选课阶段!")
|
||||||
|
|
||||||
|
def get_tokenvalue(self, bs: BeautifulSoup) -> str:
|
||||||
|
"""获取token值"""
|
||||||
|
token_element = bs.find("input", {"type": "hidden", "id": "tokenValue"})
|
||||||
|
if not token_element or not isinstance(token_element, Tag):
|
||||||
|
raise LessonsException("未找到tokenValue元素")
|
||||||
|
|
||||||
|
value = token_element.get("value")
|
||||||
|
if not value:
|
||||||
|
raise LessonsException("未找到tokenValue")
|
||||||
|
return str(value)
|
||||||
|
|
||||||
def get_base_info(self):
|
def get_base_info(self):
|
||||||
res = self.session.get(f"{self.base}/student/courseSelect/gotoSelect/index")
|
res = self.session.get(f"{self.base}/student/courseSelect/gotoSelect/index")
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
@ -201,10 +226,8 @@ class Lessons:
|
|||||||
print(html)
|
print(html)
|
||||||
raise LessonsException("未找到培养方案编号")
|
raise LessonsException("未找到培养方案编号")
|
||||||
self.fajhh = match.group(1)
|
self.fajhh = match.group(1)
|
||||||
|
|
||||||
res = self.session.get(
|
res = self.session.get(f"{self.base}/student/courseSelect/planCourse/index?fajhh={self.fajhh}")
|
||||||
f"{self.base}/student/courseSelect/planCourse/index?fajhh={self.fajhh}"
|
|
||||||
)
|
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
html = res.text
|
html = res.text
|
||||||
bs = BeautifulSoup(html, "html.parser")
|
bs = BeautifulSoup(html, "html.parser")
|
||||||
@ -212,26 +235,12 @@ class Lessons:
|
|||||||
if not match:
|
if not match:
|
||||||
raise LessonsException("未找到学期信息")
|
raise LessonsException("未找到学期信息")
|
||||||
self.term = str(match.get("value"))
|
self.term = str(match.get("value"))
|
||||||
|
|
||||||
print(self.fajhh, self.term)
|
print(self.fajhh, self.term)
|
||||||
|
|
||||||
def read_lessons(self) -> List[tuple[str, str, str]]:
|
def read_lessons(self) -> List[tuple[str, str, str]]:
|
||||||
classes = []
|
classes = []
|
||||||
file = Path(environ.get("FILE", "class.xlsx"))
|
df = pd.read_csv("class.csv")
|
||||||
if not file.is_file():
|
|
||||||
raise LessonsException(f"课程文件 {file} 不存在,请检查路径")
|
|
||||||
d: dict[str, Callable[[Path], pd.DataFrame]] = {
|
|
||||||
".csv": pd.read_csv,
|
|
||||||
".xlsx": pd.read_excel,
|
|
||||||
".xls": pd.read_excel,
|
|
||||||
".json": pd.read_json,
|
|
||||||
}
|
|
||||||
func = d.get(file.suffix.lower())
|
|
||||||
if func is None:
|
|
||||||
raise LessonsException(
|
|
||||||
f"不支持的文件格式: {file.suffix}. 仅支持 .csv, .xlsx, .xls, .json 格式"
|
|
||||||
)
|
|
||||||
df = func(file)
|
|
||||||
df.columns = df.columns.str.strip() # 去除列名两端的空格
|
df.columns = df.columns.str.strip() # 去除列名两端的空格
|
||||||
for col in ["课程号", "课序号", "课程名"]:
|
for col in ["课程号", "课序号", "课程名"]:
|
||||||
if col not in df.columns:
|
if col not in df.columns:
|
||||||
@ -240,10 +249,10 @@ class Lessons:
|
|||||||
df.columns = ["id", "kxh", "name"] # 重命名列
|
df.columns = ["id", "kxh", "name"] # 重命名列
|
||||||
df = df.drop_duplicates(subset=["id", "kxh"]) # 去重
|
df = df.drop_duplicates(subset=["id", "kxh"]) # 去重
|
||||||
for line in df.itertuples(index=False):
|
for line in df.itertuples(index=False):
|
||||||
classes.append((line.id, "%02d" % line.kxh, line.name))
|
classes.append((line.id, "%02d"%line.kxh, line.name))
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
def get_left(self, cl: tuple[str, list[str], str]) -> list[int] | None:
|
def get_left(self, cl: tuple[str, list[str], str]) -> list[int]|None:
|
||||||
"""获取课程余量"""
|
"""获取课程余量"""
|
||||||
url = f"{self.base}/student/courseSelect/planCourse/courseList"
|
url = f"{self.base}/student/courseSelect/planCourse/courseList"
|
||||||
params = {
|
params = {
|
||||||
@ -257,29 +266,27 @@ class Lessons:
|
|||||||
"kzh": "",
|
"kzh": "",
|
||||||
"xqh": "",
|
"xqh": "",
|
||||||
"xq": 0,
|
"xq": 0,
|
||||||
"jc": 0,
|
"jc": 0
|
||||||
}
|
}
|
||||||
response = self._retry_request(lambda: self.session.post(url, data=params))
|
response = self._retry_request(lambda: self.session.post(url, data=params))
|
||||||
data = response.json()["kylMap"]
|
data = response.json()["kylMap"]
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
logger.error(f"课程 {cl[2]} 的余量信息为空: {data}")
|
logger.error(f"课程 {cl[2]} 的余量信息为空: {data}")
|
||||||
return
|
return
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for kxh in cl[1]:
|
for kxh in cl[1]:
|
||||||
key = f"{self.term}_{cl[0]}_{kxh}"
|
key = f"{self.term}_{cl[0]}_{kxh}"
|
||||||
left = data.get(key, None)
|
left = data.get(key, None)
|
||||||
if left is None:
|
if left is None:
|
||||||
logger.error(
|
logger.error(f"课程 {cl[2]} 的余量信息不存在: {key} not in {data.keys()}")
|
||||||
f"课程 {cl[2]} 的余量信息不存在: {key} not in {data.keys()}"
|
|
||||||
)
|
|
||||||
ret.append(-1)
|
ret.append(-1)
|
||||||
ret.append(int(left))
|
ret.append(int(left))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def select(self, cl: tuple[str, str, str]) -> bool:
|
def select(self,cl: tuple[str,str,str]) -> bool:
|
||||||
"""选课"""
|
"""选课"""
|
||||||
|
|
||||||
url = f"{self.base}/student/courseSelect/gotoSelect/index"
|
url = f"{self.base}/student/courseSelect/gotoSelect/index"
|
||||||
response = self._retry_request(lambda: self.session.get(url))
|
response = self._retry_request(lambda: self.session.get(url))
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -289,10 +296,10 @@ class Lessons:
|
|||||||
logger.error("未找到 tokenValue")
|
logger.error("未找到 tokenValue")
|
||||||
return False
|
return False
|
||||||
token = match.group(1)
|
token = match.group(1)
|
||||||
|
|
||||||
url = f"{self.base}/student/courseSelect/selectCourse/checkInputCodeAndSubmit"
|
url = f"{self.base}/student/courseSelect/selectCourse/checkInputCodeAndSubmit"
|
||||||
cms = f"{cl[2]}_{cl[1]}"
|
cms = f"{cl[2]}_{cl[1]}"
|
||||||
cms = ",".join(map(lambda x: str(ord(x)), cms))
|
cms = ",".join(map(lambda x:str(ord(x)),cms))
|
||||||
params = {
|
params = {
|
||||||
"dealType": 2,
|
"dealType": 2,
|
||||||
"fajhh": self.fajhh,
|
"fajhh": self.fajhh,
|
||||||
@ -304,15 +311,15 @@ class Lessons:
|
|||||||
"xqh": "",
|
"xqh": "",
|
||||||
}
|
}
|
||||||
sel_data = params.copy()
|
sel_data = params.copy()
|
||||||
sel_data.update({"inputCode": "undefined", "tokenValue": token})
|
sel_data.update({"inputCode":"undefined", "tokenValue": token})
|
||||||
response = self._retry_request(lambda: self.session.post(url, data=sel_data))
|
response = self._retry_request(lambda: self.session.post(url, data=sel_data))
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
if response.json().get("result") != "ok":
|
if response.json().get("result") != "ok":
|
||||||
logger.error(f"选课时发生错误: {response}")
|
logger.error(f"选课时发生错误: {response}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info("选课请求已发送,等待结果...")
|
logger.info("选课请求已发送,等待结果...")
|
||||||
|
|
||||||
url = f"{self.base}/student/courseSelect/selectCourses/waitingfor"
|
url = f"{self.base}/student/courseSelect/selectCourses/waitingfor"
|
||||||
response = self._retry_request(lambda: self.session.post(url, data=params))
|
response = self._retry_request(lambda: self.session.post(url, data=params))
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -321,15 +328,15 @@ class Lessons:
|
|||||||
if not redisKey:
|
if not redisKey:
|
||||||
print(html)
|
print(html)
|
||||||
logger.error(f"选课 {cl[2]} 时未找到 redisKey")
|
logger.error(f"选课 {cl[2]} 时未找到 redisKey")
|
||||||
return False
|
return False
|
||||||
redisKey = redisKey.group(1)
|
redisKey = redisKey.group(1)
|
||||||
|
|
||||||
parms = {
|
parms = {
|
||||||
"kcNum": 1,
|
"kcNum": 1,
|
||||||
"redisKey": redisKey,
|
"redisKey": redisKey,
|
||||||
}
|
}
|
||||||
cnt = 1
|
cnt = 1
|
||||||
while cnt <= 10:
|
while cnt<=10:
|
||||||
url = f"{self.base}/student/courseSelect/selectResult/query"
|
url = f"{self.base}/student/courseSelect/selectResult/query"
|
||||||
response = self._retry_request(lambda: self.session.post(url, data=parms))
|
response = self._retry_request(lambda: self.session.post(url, data=parms))
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -346,12 +353,12 @@ class Lessons:
|
|||||||
logger.info(f"选课成功: {text}")
|
logger.info(f"选课成功: {text}")
|
||||||
else:
|
else:
|
||||||
print(f"第{cnt}次查询中...")
|
print(f"第{cnt}次查询中...")
|
||||||
cnt += 1
|
cnt+=1
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
logger.warning(f"选课 {cl[2]} 结果查询超时,可能未成功选课")
|
logger.warning(f"选课 {cl[2]} 结果查询超时,可能未成功选课")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
logger.info("尝试登录")
|
logger.info("尝试登录")
|
||||||
flag = False
|
flag = False
|
||||||
@ -364,7 +371,7 @@ class Lessons:
|
|||||||
logger.error(f"登录失败: {e}")
|
logger.error(f"登录失败: {e}")
|
||||||
if not flag:
|
if not flag:
|
||||||
raise LessonsException("登录失败,无法获取token")
|
raise LessonsException("登录失败,无法获取token")
|
||||||
|
|
||||||
def auto_spider(self):
|
def auto_spider(self):
|
||||||
"""自动选课主程序"""
|
"""自动选课主程序"""
|
||||||
try:
|
try:
|
||||||
@ -372,21 +379,21 @@ class Lessons:
|
|||||||
logger.info("获取基础信息")
|
logger.info("获取基础信息")
|
||||||
self.get_base_info()
|
self.get_base_info()
|
||||||
classes_src = self.read_lessons()
|
classes_src = self.read_lessons()
|
||||||
classes: dict[str, tuple[str, list[str], str]] = {}
|
classes:dict[str,tuple[str,list[str],str]] = {}
|
||||||
mp: dict[str, str] = {} # {"课程号": "课程名称"}
|
mp:dict[str,str] = {} # {"课程号": "课程名称"}
|
||||||
for id, kxh, name in classes_src:
|
for id, kxh, name in classes_src:
|
||||||
mp[id] = name
|
mp[id] = name
|
||||||
if classes.get(id) is None:
|
if classes.get(id) is None:
|
||||||
classes[id] = (id, [kxh], name)
|
classes[id] = (id, [kxh], name)
|
||||||
else:
|
else:
|
||||||
classes[id][1].append(kxh)
|
classes[id][1].append(kxh)
|
||||||
|
|
||||||
logger.info(f"读取课程信息,共有 {len(classes)} 门课程")
|
logger.info(f"读取课程信息,共有 {len(classes)} 门课程")
|
||||||
for cl in classes.values():
|
for cl in classes.values():
|
||||||
logger.info(f"课程 {cl[2]} ({cl[0]}) 的可选课序号: {', '.join(cl[1])}")
|
logger.info(f"课程 {cl[2]} ({cl[0]}) 的可选课序号: {', '.join(cl[1])}")
|
||||||
|
|
||||||
logger.info("开始自动选课")
|
logger.info("开始自动选课")
|
||||||
|
|
||||||
errs = deque(maxlen=5)
|
errs = deque(maxlen=5)
|
||||||
master_err = 0
|
master_err = 0
|
||||||
while classes:
|
while classes:
|
||||||
@ -412,12 +419,10 @@ class Lessons:
|
|||||||
errs.appendleft(time())
|
errs.appendleft(time())
|
||||||
logger.error(f"获取课程 {lcl[2]} 余量时发生错误: {e}")
|
logger.error(f"获取课程 {lcl[2]} 余量时发生错误: {e}")
|
||||||
continue
|
continue
|
||||||
for i, left in enumerate(lefts):
|
for i,left in enumerate(lefts):
|
||||||
cl = (lcl[0], lcl[1][i], lcl[2])
|
cl = (lcl[0], lcl[1][i], lcl[2])
|
||||||
if left > 0:
|
if left > 0:
|
||||||
logger.info(
|
logger.info(f"课程 {cl[2]}_{cl[1]} 有余量: {left},开始选课")
|
||||||
f"课程 {cl[2]}_{cl[1]} 有余量: {left},开始选课"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
ret = self.select(cl)
|
ret = self.select(cl)
|
||||||
if ret:
|
if ret:
|
||||||
@ -434,18 +439,14 @@ class Lessons:
|
|||||||
else:
|
else:
|
||||||
logger.info(f"课程 {cl[2]}_{cl[1]} 无余量")
|
logger.info(f"课程 {cl[2]}_{cl[1]} 无余量")
|
||||||
sleep(2)
|
sleep(2)
|
||||||
logger.info(
|
logger.info(f"当前还有{len(classes)}门课程未选上,分别为{','.join(mp[i] for i in classes.keys())}。等待10秒后继续检查")
|
||||||
f"当前还有{len(classes)}门课程未选上,分别为{','.join(mp[i] for i in classes.keys())}。等待10秒后继续检查"
|
|
||||||
)
|
|
||||||
if suc:
|
if suc:
|
||||||
sc_send(
|
sc_send("选课成功", desp=f"已成功选上课程: {', '.join(f'{i[2]}_{i[1]}' for i in suc)}")
|
||||||
"选课成功",
|
|
||||||
desp=f"已成功选上课程: {', '.join(f'{i[2]}_{i[1]}' for i in suc)}",
|
|
||||||
)
|
|
||||||
sleep(10) # 等待10秒后继续检查
|
sleep(10) # 等待10秒后继续检查
|
||||||
|
|
||||||
|
|
||||||
logger.info("自动选课完成")
|
logger.info("自动选课完成")
|
||||||
|
|
||||||
except LessonsException as e:
|
except LessonsException as e:
|
||||||
logger.error(f"选课过程中发生错误: {e}")
|
logger.error(f"选课过程中发生错误: {e}")
|
||||||
sc_send("选课异常", desp=f"选课过程中发生错误: {e}")
|
sc_send("选课异常", desp=f"选课过程中发生错误: {e}")
|
||||||
@ -455,7 +456,6 @@ class Lessons:
|
|||||||
sc_send("选课异常", desp=f"选课过程中发生意外错误: {e}")
|
sc_send("选课异常", desp=f"选课过程中发生意外错误: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
les = Lessons()
|
les = Lessons()
|
||||||
les.auto_spider()
|
les.auto_spider()
|
Reference in New Issue
Block a user