1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
from __future__ import absolute_import, unicode_literals |
3
|
|
|
|
4
|
|
|
from six.moves.urllib import parse |
5
|
|
|
|
6
|
|
|
from bs4 import BeautifulSoup |
7
|
|
|
from requests import RequestException |
8
|
|
|
|
9
|
|
|
from school_api.client.api.base import BaseSchoolApi |
10
|
|
|
from school_api.client.api.utils import get_alert_tip, get_view_state_from_html |
11
|
|
|
from school_api.client.api.utils.schedule_parse import ScheduleParse |
12
|
|
|
from school_api.client.utils import ScheduleType, UserType |
13
|
|
|
from school_api.utils import to_text |
14
|
|
|
from school_api.exceptions import ScheduleException |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
class Schedule(BaseSchoolApi): |
18
|
|
|
schedule_type = None |
19
|
|
|
schedule_year = None |
20
|
|
|
schedule_term = None |
21
|
|
|
schedule_url = None |
22
|
|
|
|
23
|
|
|
def get_schedule(self, schedule_year=None, schedule_term=None, schedule_type=None, **kwargs): |
24
|
|
|
''' 课表信息 获取入口 |
25
|
|
|
:param schedule_year: 课表学年 |
26
|
|
|
:param schedule_term: 课表学期 |
27
|
|
|
:param schedule_type: 0.个人课表 1.班级课表 |
28
|
|
|
:param kwargs: requests模块参数 |
29
|
|
|
:return: |
30
|
|
|
''' |
31
|
|
|
self.schedule_type = ScheduleType.CLASS if self.user_type \ |
32
|
|
|
else schedule_type or ScheduleType.PERSON |
33
|
|
|
self.schedule_year = schedule_year |
34
|
|
|
self.schedule_term = str(schedule_term) if schedule_term else schedule_term |
35
|
|
|
self.schedule_url = self.school_url["SCHEDULE_URL"][self.schedule_type] |
36
|
|
|
|
37
|
|
|
if self.user_type != UserType.DEPT: |
38
|
|
|
self.schedule_url += self.account |
39
|
|
|
data = self._get_api(**kwargs) |
40
|
|
|
else: |
41
|
|
|
self.schedule_url += parse.quote(self.account.encode('gb2312')) |
42
|
|
|
data = self._get_api_by_bm(**kwargs) |
43
|
|
|
if self.schedule_term and self.schedule_year and ( |
44
|
|
|
self.schedule_term != data["schedule_term"] or self.schedule_year != data["schedule_year"]): |
45
|
|
|
raise ScheduleException(self.code, '暂无课表信息') |
46
|
|
|
return data |
47
|
|
|
|
48
|
|
|
def _get_api(self, **kwargs): |
49
|
|
|
coding = 'gbk' if self.user_type else 'GB18030' |
50
|
|
|
try: |
51
|
|
|
res = self._get(self.schedule_url, **kwargs) |
52
|
|
|
|
53
|
|
|
if res.status_code == 302: |
54
|
|
|
raise ScheduleException(self.code, '课表接口已关闭') |
55
|
|
|
elif res.status_code != 200: |
56
|
|
|
raise RequestException |
57
|
|
|
except RequestException: |
58
|
|
|
raise ScheduleException(self.code, '获取课表请求参数失败') |
59
|
|
|
|
60
|
|
|
html = res.content.decode(coding) |
61
|
|
|
tip = get_alert_tip(html) |
62
|
|
|
if tip: |
63
|
|
|
raise ScheduleException(self.code, tip) |
64
|
|
|
|
65
|
|
|
schedule = ScheduleParse(html, self.time_list, self.schedule_type).get_schedule_dict() |
66
|
|
|
# 第一次请求的时候,教务系统默认返回当前学年学期课表 |
67
|
|
|
# 如果设置了学年跟学期,则获取指定学年学期的课表 |
68
|
|
|
if self.schedule_year and self.schedule_term and ( |
69
|
|
|
self.schedule_year != schedule['schedule_year'] or self.schedule_term != schedule['schedule_term']): |
70
|
|
|
|
71
|
|
|
payload = self._get_payload(res.text) |
72
|
|
|
|
73
|
|
|
try: |
74
|
|
|
res = self._post(self.schedule_url, data=payload, **kwargs) |
75
|
|
|
if res.status_code != 200: |
76
|
|
|
raise RequestException |
77
|
|
|
except RequestException: |
78
|
|
|
raise ScheduleException(self.code, '获取课表信息失败') |
79
|
|
|
|
80
|
|
|
html = res.content.decode(coding) |
81
|
|
|
schedule = ScheduleParse( |
82
|
|
|
html, |
83
|
|
|
self.time_list, |
84
|
|
|
self.schedule_type |
85
|
|
|
).get_schedule_dict() |
86
|
|
|
|
87
|
|
|
return schedule |
88
|
|
|
|
89
|
|
|
def _get_payload(self, html): |
90
|
|
|
''' 获取课表post 的参数 ''' |
91
|
|
|
view_state = get_view_state_from_html(html) |
92
|
|
|
payload = { |
93
|
|
|
'__VIEWSTATE': view_state, |
94
|
|
|
['xnd', 'xn'][self.schedule_type]: self.schedule_year, |
95
|
|
|
['xqd', 'xq'][self.schedule_type]: self.schedule_term |
96
|
|
|
} |
97
|
|
|
return payload |
98
|
|
|
|
99
|
|
|
def _get_api_by_bm(self, class_name, **kwargs): |
100
|
|
|
''' 部门教师 查询学生班级课表 共3个请求''' |
101
|
|
|
|
102
|
|
|
# steps 1: 获取课表 view_state |
103
|
|
|
try: |
104
|
|
|
res = self._get(self.schedule_url, **kwargs) |
105
|
|
|
if res.status_code != 200: |
106
|
|
|
raise RequestException |
107
|
|
|
view_state = get_view_state_from_html(res.text) |
108
|
|
|
except RequestException: |
109
|
|
|
raise ScheduleException(self.code, '获取课表请求参数失败') |
110
|
|
|
|
111
|
|
|
# steps 2: 选择课表 学年学期 |
112
|
|
|
if self.schedule_year and self.schedule_term: |
113
|
|
|
payload = { |
114
|
|
|
'__VIEWSTATE': view_state, |
115
|
|
|
'xn': self.schedule_year, |
116
|
|
|
'xq': self.schedule_term |
117
|
|
|
} |
118
|
|
|
try: |
119
|
|
|
res = self._post(self.schedule_url, data=payload, **kwargs) |
120
|
|
|
if res.status_code != 200: |
121
|
|
|
raise RequestException |
122
|
|
|
except RequestException: |
123
|
|
|
raise ScheduleException(self.code, '获取课表请求参数失败') |
124
|
|
|
|
125
|
|
|
# steps 3: 获取课表数据 |
126
|
|
|
payload = self._get_payload_by_bm( |
127
|
|
|
res.content.decode('gbk'), |
128
|
|
|
class_name |
129
|
|
|
) |
130
|
|
|
try: |
131
|
|
|
res = self._post(self.schedule_url, data=payload, **kwargs) |
132
|
|
|
if res.status_code != 200: |
133
|
|
|
raise RequestException |
134
|
|
|
except RequestException: |
135
|
|
|
raise ScheduleException(self.code, '获取课表信息失败') |
136
|
|
|
|
137
|
|
|
html = res.content.decode('gbk') |
138
|
|
|
schedule = ScheduleParse( |
139
|
|
|
html, |
140
|
|
|
self.time_list, |
141
|
|
|
self.schedule_type |
142
|
|
|
).get_schedule_dict() |
143
|
|
|
|
144
|
|
|
return schedule |
145
|
|
|
|
146
|
|
|
@staticmethod |
147
|
|
|
def _get_payload_by_bm(html, class_name): |
148
|
|
|
''' 提取页面参数用于请求课表 ''' |
149
|
|
|
pre_soup = BeautifulSoup(html, "html.parser") |
150
|
|
|
view_state = pre_soup.find(attrs={"name": "__VIEWSTATE"})['value'] |
151
|
|
|
schedule_id_list = pre_soup.find(id='kb').find_all('option') |
152
|
|
|
class_name = to_text(class_name) |
153
|
|
|
schedule_id = '' |
154
|
|
|
for name in schedule_id_list: |
155
|
|
|
if name.text == class_name: |
156
|
|
|
schedule_id = name['value'] |
157
|
|
|
break |
158
|
|
|
|
159
|
|
|
# 获取班级课表 |
160
|
|
|
payload = { |
161
|
|
|
'__VIEWSTATE': view_state, |
162
|
|
|
'kb': schedule_id |
163
|
|
|
} |
164
|
|
|
return payload |
165
|
|
|
|