import requests
from bs4 import BeautifulSoup
from icalendar import Calendar, Event
from datetime import datetime, timedelta
import pytz
import re
CONFIG = {
"BASE_URL": "http://jwxt.xjau.edu.cn",
"LOGIN_URL": "/login",
"SCHEDULE_URL": "/student/schedule",
"USERNAME": "你的学号",
"PASSWORD": "你的密码",
"TIMEZONE": pytz.timezone('Asia/Shanghai'),
"TIME_TABLE": {
1: ("08:30", "10:05"),
2: ("10:25", "12:00"),
3: ("12:30", "14:05"),
4: ("15:30", "17:05"),
5: ("19:30", "21:05")
}
}
class XJAU_Schedule:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
def login(self):
"""处理登录流程(注意:需要自行处理验证码)"""
try:
login_page = self.session.get(CONFIG["BASE_URL"] + CONFIG["LOGIN_URL"])
soup = BeautifulSoup(login_page.text, 'html.parser')
token = soup.find('input', {'name': '_token'})['value']
login_data = {
'_token': token,
'username': CONFIG["USERNAME"],
'password': CONFIG["PASSWORD"],
'captcha': '自行处理验证码'
}
response = self.session.post(
CONFIG["BASE_URL"] + CONFIG["LOGIN_URL"],
data=login_data
)
if '登录失败' in response.text:
raise Exception("登录失败,请检查账号密码或验证码")
return True
except Exception as e:
print(f"登录异常: {str(e)}")
return False
def fetch_schedule(self):
"""获取课表数据"""
try:
response = self.session.get(CONFIG["BASE_URL"] + CONFIG["SCHEDULE_URL"])
response.encoding = 'utf-8'
return self.parse_html(response.text)
except Exception as e:
print(f"获取课表失败: {str(e)}")
return []
def parse_html(self, html):
"""解析课表页面"""
soup = BeautifulSoup(html, 'html.parser')
courses = []
for row in soup.select('#kbtable tr'):
cols = row.select('td')
if len(cols) >= 6:
day = int(re.search(r'\d', cols[0].text).group())
for i in range(1, 6):
if cols[i].text.strip():
course_info = self.parse_course_text(cols[i].text)
courses.append({
'name': course_info['name'],
'day': day,
'section': i,
'weeks': course_info['weeks'],
'location': course_info['location']
})
return courses
def parse_course_text(self, text):
"""解析课程单元格文本"""
parts = [p.strip() for p in text.split('\n') if p.strip()]
return {
'name': parts[0],
'weeks': self.parse_weeks(parts[1]),
'location': parts[2] if len(parts)>2 else ""
}
def parse_weeks(self, week_str):
"""解析周次信息"""
weeks = []
for part in week_str.replace('周', '').split(','):
if '-' in part:
start, end = map(int, part.split('-'))
weeks.extend(range(start, end+1))
else:
weeks.append(int(part))
return weeks
def generate_ics(self, courses):
"""生成iCalendar文件"""
cal = Calendar()
cal.add('prodid', '-//XJAU Schedule//mxm.dk//')
cal.add('version', '2.0')
for course in courses:
for week in course['weeks']:
base_date = datetime(2025, 3, 24)
target_date = base_date + timedelta(
weeks=(week-1),
days=(course['day']-1)
)
start_time = datetime.strptime(
CONFIG["TIME_TABLE"][course['section']][0], "%H:%M"
).time()
end_time = datetime.strptime(
CONFIG["TIME_TABLE"][course['section']][1], "%H:%M"
).time()
event = Event()
event.add('summary', f"{course['name']} @ {course['location']}")
event.add('dtstart', CONFIG["TIMEZONE"].localize(
datetime.combine(target_date, end_time)
))
event.add('location', course['location'])
cal.add_component(event)
with open('xjau_schedule.ics', 'wb') as f:
f.write(cal.to_ical())
print("课表文件已生成: xjau_schedule.ics")
if __name__ == "__main__":
print("新疆农业大学课表转换工具")
print("正在初始化...")
tool = XJAU_Schedule()
if tool.login():
print("登录成功!")
courses = tool.fetch_schedule()
if courses:
print(f"成功获取{len(courses)}条课程记录")
tool.generate_ics(courses)
print("请将生成的 xjau_schedule.ics 通过小米运动APP导入小爱同学")
else:
print("未获取到课程数据,请检查页面解析逻辑")
else:
print("登录失败,请检查网络或配置")
def handle_captcha(self):
captcha_url = CONFIG["BASE_URL"] + "/captcha"
response = self.session.get(captcha_url)
with open("captcha.jpg", "wb") as f:
f.write(response.content)
return input("请输入验证码(查看captcha.jpg):")