1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
from __future__ import absolute_import, unicode_literals |
3
|
|
|
|
4
|
|
|
import six.moves.urllib.parse as urlparse |
5
|
|
|
import time |
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.schedule_parse import ScheduleParse |
11
|
|
|
from school_api.client.utils import ScheduleType |
12
|
|
|
from school_api.exceptions import ScheduleException |
13
|
|
|
|
14
|
|
|
|
15
|
|
|
class PlaceSchedule(BaseSchoolApi): |
16
|
|
|
schedule_url = None |
17
|
|
|
payload = None |
18
|
|
|
|
19
|
|
|
def get_schedule(self, |
20
|
|
|
campus_list=None, |
21
|
|
|
building_list=None, |
22
|
|
|
classroom_type_list=None, |
23
|
|
|
classroom_name_list=None, |
24
|
|
|
filter_campus_list=None, |
25
|
|
|
filter_building_list=None, |
26
|
|
|
filter_classroom_type_list=None, |
27
|
|
|
**kwargs): |
28
|
|
|
''' |
29
|
|
|
课表信息 获取入口 |
30
|
|
|
生成器一旦报错则将退出:https://codeday.me/bug/20180125/124136.html |
31
|
|
|
除了解析异常其他报错均不抛出 |
32
|
|
|
|
33
|
|
|
:param campus_list: 校区列表 |
34
|
|
|
:param building_list: 楼号列表 |
35
|
|
|
:param classroom_type_list: 教室类别列表 |
36
|
|
|
:param classroom_name_list: 教室名称列表 |
37
|
|
|
|
38
|
|
|
:param filter_campus_list: 排除校区列表 |
39
|
|
|
:param filter_building_list: 排除楼号列表 |
40
|
|
|
:param filter_classroom_type_list: 排除教室类别列表 |
41
|
|
|
:param kwargs: requests 参数 |
42
|
|
|
:yield: 生成器 |
43
|
|
|
''' |
44
|
|
|
|
45
|
|
|
self.schedule_url = self.school_url['PLACE_SCHEDULE_URL'] + \ |
46
|
|
|
urlparse.quote(self.user.account.encode('gb2312')) |
47
|
|
|
|
48
|
|
|
if not self._update_payload(**kwargs): |
49
|
|
|
yield {'error': "获取教学场地课表失败"} |
50
|
|
|
else: |
51
|
|
|
# 遍历校区 |
52
|
|
|
for campus in self.payload['campus_list']: |
53
|
|
|
if self._is_skip(campus["name"], campus_list, filter_name_list=filter_campus_list): |
54
|
|
|
continue |
55
|
|
|
if not self._update_payload(campus, **kwargs): |
56
|
|
|
continue |
57
|
|
|
|
58
|
|
|
# 遍历楼号 |
59
|
|
|
for building in self.payload['building_list']: |
60
|
|
|
kwargs['building'] = building |
61
|
|
|
if self._is_skip(building["name"], building_list, filter_name_list=filter_building_list): |
62
|
|
|
continue |
63
|
|
|
if not self._update_payload(campus, **kwargs): |
64
|
|
|
continue |
65
|
|
|
|
66
|
|
|
# 遍历教室类别 |
67
|
|
|
for classroom_type in self.payload['classroom_type_list']: |
68
|
|
|
kwargs['classroom_type'] = classroom_type |
69
|
|
|
if self._is_skip(classroom_type["name"], classroom_type_list, |
70
|
|
|
filter_name_list=filter_classroom_type_list): |
71
|
|
|
continue |
72
|
|
|
if not self._update_payload(campus, **kwargs): |
73
|
|
|
continue |
74
|
|
|
|
75
|
|
|
# 遍历教室名称 |
76
|
|
|
for classroom_name in self.payload['classroom_name_list']: |
77
|
|
|
if self._is_skip(classroom_name["name"], classroom_name_list): |
78
|
|
|
continue |
79
|
|
|
|
80
|
|
|
kwargs['classroom_name'] = classroom_name |
81
|
|
|
# 请求接口获取课表数据 |
82
|
|
|
data = self._get_result(campus, **kwargs) |
83
|
|
|
if data: |
84
|
|
|
yield data |
85
|
|
|
|
86
|
|
|
def _get_result(self, campus, **kwargs): |
87
|
|
|
""" 处理请求结果,并返回 """ |
88
|
|
|
try: |
89
|
|
|
res = self._get_api(campus, **kwargs) |
90
|
|
|
except ScheduleException: |
91
|
|
|
return |
92
|
|
|
|
93
|
|
|
schedule = ScheduleParse(res.text, self.time_list, ScheduleType.CLASS).get_schedule_dict() |
94
|
|
|
|
95
|
|
|
data = { |
96
|
|
|
"campus": campus["name"], |
97
|
|
|
"building": kwargs['building']["name"], |
98
|
|
|
"classroom_type": kwargs['classroom_type']["name"], |
99
|
|
|
"classroom_name": kwargs['classroom_name']["name"] |
100
|
|
|
} |
101
|
|
|
data.update(schedule) |
102
|
|
|
return data |
103
|
|
|
|
104
|
|
|
def _get_api(self, campus=None, **kwargs): |
105
|
|
|
""" 请求函数 """ |
106
|
|
|
if self.payload and campus: |
107
|
|
|
building = kwargs.pop('building', None) |
108
|
|
|
classroom_type = kwargs.pop('classroom_type', None) |
109
|
|
|
classroom_name = kwargs.pop('classroom_name', None) |
110
|
|
|
data = { |
111
|
|
|
"Button1": "", |
112
|
|
|
"xq": self.payload['schedule_term'], |
113
|
|
|
"xn": self.payload['schedule_year'], |
114
|
|
|
"ddlXq": campus["value"], |
115
|
|
|
'ddllh': building["value"] if building else '', |
116
|
|
|
"ddlJslb": classroom_type["value"].encode('gb2312') if classroom_type else '', |
117
|
|
|
"ddlJsmc": classroom_name["value"].encode('gb2312') if classroom_name else '', |
118
|
|
|
"__VIEWSTATE": self.payload['view_state'], |
119
|
|
|
} |
120
|
|
|
_request = self._post |
121
|
|
|
else: |
122
|
|
|
data = "" |
123
|
|
|
_request = self._get |
124
|
|
|
|
125
|
|
|
try: |
126
|
|
|
res = _request(self.schedule_url, data=data, **kwargs) |
127
|
|
|
except RequestException: |
128
|
|
|
raise ScheduleException(self.school_code, '获取教学场地课表失败') |
129
|
|
|
return res |
130
|
|
|
|
131
|
|
|
def _update_payload(self, *args, **kwargs): |
132
|
|
|
# 更新提交参数 payload |
133
|
|
|
try: |
134
|
|
|
res = self._get_api(*args, **kwargs) |
135
|
|
|
except ScheduleException: |
136
|
|
|
return None |
137
|
|
|
|
138
|
|
|
try: |
139
|
|
|
self.payload = self._get_payload(res.text) |
140
|
|
|
except AttributeError: |
141
|
|
|
return None |
142
|
|
|
except Exception: |
143
|
|
|
time.sleep(2) |
144
|
|
|
return None |
145
|
|
|
return True |
146
|
|
|
|
147
|
|
|
@staticmethod |
148
|
|
|
def _get_payload(html): |
149
|
|
|
''' 获取课表提交参数 ''' |
150
|
|
|
pre_soup = BeautifulSoup(html, "html.parser") |
151
|
|
|
view_state = pre_soup.find(attrs={"name": "__VIEWSTATE"})['value'] |
152
|
|
|
searchbox = pre_soup.find("div", {"class": "searchbox"}) |
153
|
|
|
|
154
|
|
|
schedule_year = searchbox.find("select", {"id": "xn"}).find( |
155
|
|
|
"option", {"selected": "selected"}).text |
156
|
|
|
schedule_term = searchbox.find("select", {"id": "xq"}).find( |
157
|
|
|
"option", {"selected": "selected"}).text |
158
|
|
|
|
159
|
|
|
campuses = searchbox.find(id='ddlXq').find_all('option') |
160
|
|
|
buildings = searchbox.find(id='ddllh').find_all('option') |
161
|
|
|
types = searchbox.find(id='ddlJslb').find_all('option') |
162
|
|
|
names = searchbox.find(id='ddlJsmc').find_all('option') |
163
|
|
|
|
164
|
|
|
campuses = [{"name": v.text, "value": v['value']} for v in campuses] |
165
|
|
|
buildings = [{"name": v.text, "value": v['value']} for v in buildings] |
166
|
|
|
types = [{"name": v.text, "value": v['value']} for v in types] |
167
|
|
|
names = [{"name": v.text, "value": v['value']} for v in names] |
168
|
|
|
|
169
|
|
|
payload = { |
170
|
|
|
'view_state': view_state, |
171
|
|
|
'schedule_term': schedule_term, |
172
|
|
|
'schedule_year': schedule_year, |
173
|
|
|
'campus_list': campuses, |
174
|
|
|
'building_list': buildings, |
175
|
|
|
'classroom_type_list': types, |
176
|
|
|
'classroom_name_list': names, |
177
|
|
|
} |
178
|
|
|
return payload |
179
|
|
|
|
180
|
|
|
@staticmethod |
181
|
|
|
def _is_skip(name, name_list, filter_name_list=None): |
182
|
|
|
''' 检查是否跳过 ''' |
183
|
|
|
if (name_list and name not in name_list) or (filter_name_list and name in filter_name_list): |
184
|
|
|
return True |
185
|
|
|
return None |
186
|
|
|
|