def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): ''' :param urls: :param user_config_path: :param args: :param kwargs: 目前仍支持从settings中读取设备,后续考虑移除 ''' self._logger = LogHandler("Loginer") self._S = requests.session() self._user_config_path = user_config_path self._user_info_from_settings = kwargs.get('user_info') self._cfg = get_cfg(self._user_config_path) self._urls = urls self.headers = { 'Connection': 'keep-alive', 'sec-ch-ua': '"Google Chrome";v="87", "\\"Not;A\\\\Brand";v="99", "Chromium";v="87"', 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', 'sec-ch-ua-mobile': '?0', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://onestop.ucas.ac.cn', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', 'Referer': 'https://onestop.ucas.ac.cn/', 'Accept-Language': 'zh-CN,zh;q=0.9', }
def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler('GradeObserver')
def __init__(self, urls=None, user_config_path='../conf/user_config.ini', assess_msgs=[], *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler('Assesser') self._assess_msgs = assess_msgs self._id_pattern = re.compile('/evaluate/.*?/(?P<id>.*?)$') self._course_assess_url = None # 动态获取课程评估地址
def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler("Downloader") self._resource_path_from_settings = kwargs.get('resource_path') self._filter_list = kwargs.get('filter_list') self._update_sources = [] self._l_course_info = [] self._d_source_info = {} self._cur_course_info = None self._collection_id_pattern = re.compile( "value='(?P<collection_id>.*?)';") # 获取collection id 信息正则 self._dir_pattern = re.compile( "value='/group/[0-9]*/(?P<dir>.*?)';") # 获取文件夹目录信息正则
def __init__(self, welcome_msg, record_path='../conf/record.ini', *args, **kwargs): self._logger = LogHandler("Init") self._welcome_msg = welcome_msg self._record_path = record_path self._cfg = get_cfg(config_path=self._record_path) self._name_of_update_section = 'update_info' self._name_of_update_time = 'last_update_time' # 记录上次更新的时间 self._name_of_tag = 'tag' # 当前版本号 # update api info self.__update_info_api = "https://api.github.com/repos/GentleCP/UCAS-Helper" self.__latest_tag_api = "https://api.github.com/repos/GentleCP/UCAS-Helper/tags" self._wifiLoginer = WifiLoginer(accounts_path=settings.ACCOUNTS_PATH) self._downloader = Downloader( user_info=settings.USER_INFO, # 未来删除 urls=settings.URLS, user_config_path=settings.USER_CONFIG_PATH, resource_path=settings.SOURCE_DIR, # 未来删除 filter_list=settings.FILTER_LIST) self._assesser = Assesser( user_info=settings.USER_INFO, # 未来删除 user_config_path=settings.USER_CONFIG_PATH, urls=settings.URLS, assess_msgs=settings.ASSESS_MSG) self._gradeObserver = GradeObserver( user_config_path=settings.USER_CONFIG_PATH, user_info=settings.USER_INFO, # 未来删除 urls=settings.URLS)
class GradeObserver(Loginer): """ 课程成绩查看器 """ def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler('GradeObserver') def _show_grade(self): try: res = self._S.get(self._urls['grade_url']['http'], headers=self.headers, timeout=5) except requests.Timeout: res = self._S.get(self._urls['grade_url']['https'], headers=self.headers) bs4obj = BeautifulSoup(res.text, 'html.parser') thead = bs4obj.find('thead') pd = PrettyTable() pd.field_names = [x.string for x in thead.find_all('th')] tbody = bs4obj.find('tbody') for tr in tbody.find_all('tr'): # tr:每一门课程信息 pd.add_row([x.string.strip() for x in tr.find_all('td')]) self._logger.info('成绩查询结果如下') print(pd) def run(self): self.login() self._show_grade()
class Init(object): """ 用于检查一切配置信息是否合理正确 """ def __init__(self, welcome_msg, record_path='../conf/record.ini', *args, **kwargs): self._logger = LogHandler("Init") self._welcome_msg = welcome_msg self._record_path = record_path self._cfg = get_cfg(config_path=self._record_path) self._name_of_update_section = 'update_info' self._name_of_update_time = 'last_update_time' # 记录上次更新的时间 self._name_of_tag = 'tag' # 当前版本号 # update api info self.__update_info_api = "https://api.github.com/repos/GentleCP/UCAS-Helper" self.__latest_tag_api = "https://api.github.com/repos/GentleCP/UCAS-Helper/tags" self._wifiLoginer = WifiLoginer(accounts_path=settings.ACCOUNTS_PATH) self._downloader = Downloader( user_info=settings.USER_INFO, # 未来删除 urls=settings.URLS, user_config_path=settings.USER_CONFIG_PATH, resource_path=settings.SOURCE_DIR, # 未来删除 filter_list=settings.FILTER_LIST) self._assesser = Assesser( user_info=settings.USER_INFO, # 未来删除 user_config_path=settings.USER_CONFIG_PATH, urls=settings.URLS, assess_msgs=settings.ASSESS_MSG) self._gradeObserver = GradeObserver( user_config_path=settings.USER_CONFIG_PATH, user_info=settings.USER_INFO, # 未来删除 urls=settings.URLS) def __get_tag(self): ''' 从配置文件或在线获取当前版本号 :return: tag, e.g. v2.3.1 ''' if not self._cfg.has_section(self._name_of_update_section): self._cfg.add_section(self._name_of_update_section) try: local_tag = self._cfg.get(self._name_of_update_section, self._name_of_tag) except configparser.NoOptionError: self._logger.info('getting latest tag') return json.loads(requests.get( self.__latest_tag_api).text)[0].get('name') else: if not local_tag: self._logger.info('getting latest tag') return json.loads(requests.get( self.__latest_tag_api).text)[0].get('name') else: return local_tag def _show_welcome(self): ''' :return: ''' tag = self.__get_tag() print(self._welcome_msg.format(tag=tag)) def __check_update(self): ''' check the latest code from github repo api, if detect the new version, update the demo :return: {}, if need update, return True and latest_update_time, else return False ''' self._logger.info("Checking update...") try: latest_update_time = requests.get( self.__update_info_api).json()["updated_at"] latest_tag = json.loads(requests.get( self.__latest_tag_api).text)[0].get('name') except Exception: self._logger.error("checking update faild.") return {'need_update': False} try: last_update_time = self._cfg.get(self._name_of_update_section, self._name_of_update_time) except (configparser.NoSectionError, configparser.NoOptionError) as e: self._logger.info("Available updates detected, start updating...") return { 'need_update': True, 'latest_update_time': latest_update_time, 'latest_tag': latest_tag } else: if latest_update_time == last_update_time: # already up to date self._logger.info("Already up to date.") return {'need_update': False} else: self._logger.info( "Available updates detected, start updating...") return { 'need_update': True, 'latest_update_time': latest_update_time, 'latest_tag': latest_tag } def _do_update(self): if not settings.ALLOW_AUTO_UPDATE: # not allow update return check_update_res = self.__check_update() if check_update_res['need_update']: # need to update try: os.system( "git stash && git fetch --all && git merge && git stash pop" ) except KeyboardInterrupt: # update interrupt, nothing to do. self._logger.error("Update Interrupt by user.") else: # update complete, update the local update time. if not self._cfg.has_section(self._name_of_update_section): self._cfg.add_section(self._name_of_update_section) # update latest_update_time self._cfg.set(self._name_of_update_section, self._name_of_update_time, check_update_res['latest_update_time']) # update latest_tag self._cfg.set(self._name_of_update_section, self._name_of_tag, check_update_res['latest_tag']) self._cfg.write(open(self._record_path, 'w')) self._logger.info("Update compelte.") def _cmd(self): while True: time.sleep(0.1) option = input("输入你的操作:") if option == 'q': print("欢迎使用,下次再会~") exit(ExitStatus.OK) elif not (option.isdigit() and 1 <= int(option) <= 5): self._logger.error("非法操作,请重新输入") else: option = int(option) if option == 1: try: self._downloader.run() except BackToMain: pass elif option == 2: try: self._wifiLoginer.login() except WifiError: pass elif option == 3: try: self._wifiLoginer.logout() except WifiError: pass elif option == 4: self._assesser.run() elif option == 5: self._gradeObserver.run() def run(self): self._show_welcome() self._do_update() self._cmd()
class Loginer(object): """ 登录课程网站 """ def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): ''' :param urls: :param user_config_path: :param args: :param kwargs: 目前仍支持从settings中读取设备,后续考虑移除 ''' self._logger = LogHandler("Loginer") self._S = requests.session() self._user_config_path = user_config_path self._user_info_from_settings = kwargs.get('user_info') self._cfg = get_cfg(self._user_config_path) self._urls = urls self.headers = { 'Connection': 'keep-alive', 'sec-ch-ua': '"Google Chrome";v="87", "\\"Not;A\\\\Brand";v="99", "Chromium";v="87"', 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', 'sec-ch-ua-mobile': '?0', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://onestop.ucas.ac.cn', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', 'Referer': 'https://onestop.ucas.ac.cn/', 'Accept-Language': 'zh-CN,zh;q=0.9', } def _set_user_info(self): ''' set user info from conf/user_config.ini or from settings.py :return: None ''' from_settings_warning_msg = ('Note: you are using the user info from settings.py which may remove in the future, ' 'I suggest you to save the user info in conf/user_config.ini') try: username = self._cfg.get('user_info', 'username') password = self._cfg.get('user_info', 'password') except (configparser.NoSectionError, configparser.NoOptionError) as e: self._logger.warning('Can not read user info from {}, try to get it from settings.py'.format(self._user_config_path)) self._logger.warning(from_settings_warning_msg) self._user_info = self._user_info_from_settings else: if not username or not password: # 用户名或密码信息为空 self._logger.warning(from_settings_warning_msg) self._user_info = self._user_info_from_settings else: self._user_info = { 'username': username, 'password': password, 'remember': 'undefined' } def __keep_session(self): try: res = self._S.get(url=self._urls['course_select_url']['http'], headers = self.headers, timeout=5) except requests.Timeout: res = self._S.get(url=self._urls['course_select_url']['https'], headers = self.headers) course_select_url = re.search(r"window.location.href='(?P<course_select_url>.*?)'", res.text).groupdict().get( "course_select_url") self._S.get(course_select_url,headers=self.headers) def login(self): self._set_user_info() self._S.get(url=self._urls['home_url']['https'], headers=self.headers, verify=False) # 获取identity res = None try: res = self._S.post(url=self._urls["login_url"]['https'], data=self._user_info, headers=self.headers, timeout=10) except (requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout): self._logger.error("网络连接失败,请确认你的网络环境后重试!") exit(ExitStatus.NETWORK_ERROR) if res.status_code != 200: self._logger.error('sep登录失败,未知错误,请到github提交issue,等待作者修复.') exit(ExitStatus.UNKNOW_ERROR) else: json_res = res.json() if json_res["f"]: self._S.get(res.json()["msg"], headers=self.headers) self._logger.info("sep登录成功!") self.__keep_session() else: self._logger.error("sep登录失败,请检查你的用户名和密码设置是否正确!") exit(ExitStatus.CONFIG_ERROR)
class Downloader(Loginer): def __init__(self, urls=None, user_config_path='../conf/user_config.ini', *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler("Downloader") self._resource_path_from_settings = kwargs.get('resource_path') self._filter_list = kwargs.get('filter_list') self._update_sources = [] self._l_course_info = [] self._d_source_info = {} self._cur_course_info = None self._collection_id_pattern = re.compile( "value='(?P<collection_id>.*?)';") # 获取collection id 信息正则 self._dir_pattern = re.compile( "value='/group/[0-9]*/(?P<dir>.*?)';") # 获取文件夹目录信息正则 def _set_resource_path(self): ''' set resource path from conf/user_config.ini or from settings.py :return: None ''' from_settings_warning_msg = ( 'Note: you are using the resource path from settings.py which may remove in the future, ' 'I suggest you to save the resource path in conf/user_config.ini') try: resource_path = self._cfg.get('course_info', 'resource_path') except (configparser.NoSectionError, configparser.NoOptionError) as e: self._logger.warning( 'Can not read resource path from {}, try to get it from settings.py' .format(self._user_config_path)) self._logger.warning(from_settings_warning_msg) self._resource_path = self._resource_path_from_settings else: if not resource_path: self._logger.warning(from_settings_warning_msg) self._resource_path = self._resource_path_from_settings else: self._resource_path = resource_path def __update_source_info(self, course_info, bs4obj, dir): i = 1 for e in bs4obj.findAll('a'): try: if 'course.ucas.ac.cn/access/content/group' in e["href"]: filename = e.find('span', { 'class': 'hidden-sm hidden-xs' }).get_text() self._d_source_info[course_info["name"]].append({ 'id': i, 'name': dir + filename, 'url': e["href"] }) i += 1 except (KeyError, AttributeError): continue def _recur_dir(self, course_info, source_url, bs4obj): ''' 递归获取文件夹下文件信息 :param source_url: :param bs4obj: :return: ''' l_dir_objs = bs4obj.findAll('a', {'title': '文件夹'}) if len(l_dir_objs) > 1: # 存在其他文件夹,添加当前目录资源信息,接着递归文件夹下内容 cur_dir = self._dir_pattern.findall( l_dir_objs[0]["onclick"])[0] # 获取了课程文件夹信息 self.__update_source_info(course_info, bs4obj, cur_dir) csrf_token = bs4obj.find('input', { 'name': 'sakai_csrf_token' }).get("value") # 获取token,用于请求文件夹资源 for e in bs4obj.findAll('a', {'title': '文件夹'})[1:]: # 第一个是当前目录忽略 collection_id = self._collection_id_pattern.findall( e["onclick"])[1] # 获取了课程文件夹信息 data = { 'source': 0, 'collectionId': collection_id, 'navRoot': '', 'criteria': 'title', 'sakai_action': 'doNavigate', 'rt_action': '', 'selectedItemId': '', 'itemHidden': 'false', 'itemCanRevise': 'false', 'sakai_csrf_token': csrf_token } res = self._S.post(source_url, data=data, headers=self.headers) # 获取文件夹下资源信息 bs4obj = BeautifulSoup(res.text, 'html.parser') self._recur_dir(course_info, source_url, bs4obj) else: # 没有更深层文件夹了,添加资源信息 cur_dir = self._dir_pattern.findall( l_dir_objs[0]["onclick"])[0] # 获取了课程文件夹信息 self.__update_source_info(course_info, bs4obj, cur_dir) return def _set_course_info(self): if not self._l_course_info: # 减少后续多次请求课程信息耗时 try: res = self._S.get(url=self._urls['course_info_url']['http'], headers=self.headers, timeout=5) except requests.Timeout: res = self._S.get(url=self._urls['course_info_url']['https'], headers=self.headers) bsobj = BeautifulSoup(res.text, "html.parser") refresh_url = bsobj.find("noscript").meta.get("content")[ 6:] # 获取新的定向url res = self._S.get(refresh_url, headers=self.headers) bsobj = BeautifulSoup(res.text, "html.parser") new_course_url = bsobj.find('a', { "title": "我的课程 - 查看或加入站点" }).get("href") # 获取到新的课程信息url res = self._S.get(new_course_url, headers=self.headers) bsobj = BeautifulSoup(res.text, "html.parser") course_list = bsobj.findAll('tr') # 尚未筛选的杂乱信息 i = 1 for course in course_list: a = course.find('a') course_url = a.get("href") course_name = a.get_text() if "课程名称" not in course_name: self._l_course_info.append({ 'id': i, 'name': course_name, 'url': course_url }) self._d_source_info.update({course_name: []}) # 为该课程开辟一个位置 i += 1 def _set_source_info(self, course_info): ''' 给定一门课(name+url),获取该课的所有课程资源 :param course: :return: ''' if not self._d_source_info[course_info["name"]]: # 该门课的资源信息尚未存储到内存 res = self._S.get(course_info["url"], headers=self.headers) bs4obj = BeautifulSoup(res.text, "html.parser") source_url = bs4obj.find('a', { 'title': '资源 - 上传、下载课件,发布文档,网址等信息' }).get("href") res = self._S.get(source_url, headers=self.headers) # 获取课程资源页面 bs4obj = BeautifulSoup(res.text, "lxml") self._recur_dir(course_info, source_url, bs4obj) def _download_one(self, course_info, source_info): ''' 给定一门课,下载该门课指定一个资源 :return: ''' # 按季度划分课程 if "秋季" in course_info['name']: base_dir = self._resource_path + '/秋季/' elif "春季" in course_info['name']: base_dir = self._resource_path + '/春季/' else: base_dir = self._resource_path + '/夏季/' if not os.path.exists(base_dir): os.mkdir(base_dir) course_dir = base_dir + course_info['name'] # 课程目录 if not os.path.exists(course_dir): os.mkdir(course_dir) dirs = source_info['name'].split('/')[0:-1] # 只取目录部分 if dirs: # 存在文件夹,递归检测文件夹 recur_mkdir(course_dir, dirs) file_path = base_dir + course_info["name"] + '/' + source_info[ 'name'] # 文件存储路径 if not os.path.isfile(file_path): # 只下载没有的文件,若存在不下载,节省流量 self._logger.info("正在下载:{}".format(source_info['name'])) download_file(url=source_info['url'], session=self._S, file_path=file_path) self._update_sources.append("[{}:{}]".format( course_info["name"], source_info['name'])) # 记录更新的课程数据 def _download_course(self, course_info): ''' 下载一门课的所有资源 :param S: :param course_info: :return: ''' print("\033[1;45m正在同步{}全部资源... \033[0m".format(course_info["name"])) for source_info in self._d_source_info[course_info["name"]]: self._download_one(course_info, source_info) def _download_all(self, season=None): for course_info in self._l_course_info: if season is None: if course_info['name'] not in self._filter_list: self._set_source_info(course_info) self._download_course(course_info) else: if season in course_info['name'] and course_info[ 'name'] not in self._filter_list: self._set_source_info(course_info) self._download_course(course_info) if self._update_sources: self._logger.info("[同步完成] 本次更新资源列表如下:") for source in self._update_sources: print('\033[1;41m' + source + '\033[0m') is_open = input("是否打开资源所在目录(默认n)?(y/n)") if is_open == 'y': if open_dir(self._resource_path) == 0: self._logger.info("已为您打开资源目录,请根据更新资源列表查询对应文件!") else: self._logger.error("打开资源目录失败,请手动开启!") else: self._logger.info("[同步完成] 本次无更新内容!") exit(ExitStatus.OK) def __check_option(self, option): if option == 'q': print("欢迎使用,下次再会~") exit(ExitStatus.OK) elif option == 'b' and self._cur_course_info: self._cur_course_info = None # 清空 return True elif option == 'd' and not self._cur_course_info: self._download_all() return False elif option == 's' and not self._cur_course_info: self._download_all(season='春季') return False elif option == 'm' and not self._cur_course_info: self._download_all(season='夏季') return False elif option == 'f' and not self._cur_course_info: self._download_all(season='秋季') return False elif option == 'a' and self._cur_course_info: self._download_course(course_info=self._cur_course_info) elif option.isdigit() and self._cur_course_info: # 课程界面操作 try: source_info = self._d_source_info[ self._cur_course_info["name"]][int(option) - 1] except IndexError: self._logger.warning("不存在该序号课程资源,请重新选择!") else: self._download_one(self._cur_course_info, source_info) elif option.isdigit(): # 主界面操作 try: self._cur_course_info = self._l_course_info[(int(option) - 1)] except IndexError: self._logger.warning("不存在该序号课程,请重新选择!") else: self._set_source_info(self._cur_course_info) return True else: self._logger.warning("非法操作,请重新输入") return False def _cmd(self): while True: print("\033[1;45m>课程列表:\033[0m", flush=True) show(self._l_course_info) print(""" *************************************** * id:显示对应课程的所有资源 * * d:一键同步所有资源 * * s:同步春季课程资源 * * m:同步夏季课程资源 * * f:同步秋季课程资源 * * q:退出 * *************************************** """) option = input("请输入你的操作:") if not self.__check_option(option): # 不进入下一级界面 continue while True: print("\033[1;45m>课程列表>{}:\033[0m".format( self._cur_course_info["name"])) show(self._d_source_info[self._cur_course_info["name"]]) print(""" ********************************* * id:下载对应id资源 * * a:下载所有 * * b:返回上一级 * * q:退出 * ******************************** """) option = input("请输入你的操作:") if self.__check_option(option): # 接收到返回上级界面信息 break def run(self): self._set_resource_path() if check_dir(self._resource_path): self._logger.error("资源存储路径非法或不正确,请检查你的resource_path配置是否正确!") exit(ExitStatus.CONFIG_ERROR) self.login() self._set_course_info() # 添加所有课程信息到内存中 self._cmd() # 进入交互界面
class Assesser(Loginer): def __init__(self, urls=None, user_config_path='../conf/user_config.ini', assess_msgs=[], *args, **kwargs): super().__init__(urls, user_config_path, *args, **kwargs) self._logger = LogHandler('Assesser') self._assess_msgs = assess_msgs self._id_pattern = re.compile('/evaluate/.*?/(?P<id>.*?)$') self._course_assess_url = None # 动态获取课程评估地址 def _get_course_ids(self): # 获取课程评估url try: res = self._S.get(url=self._urls['view_url']['http'], headers=self.headers, timeout=5) except requests.Timeout: res = self._S.get(url=self._urls['view_url']['https'], headers=self.headers) bs4obj = BeautifulSoup(res.text, 'html.parser') href = bs4obj.find('a', string=re.compile('.*学期$')).get('href') self._course_assess_url = self._urls['base_url']['http'] + href # 获取课程id try: res = self._S.get(self._course_assess_url, headers=self.headers, timeout=5) except requests.Timeout: self._course_assess_url = self._urls['base_url']['https'] + href res = self._S.get(self._course_assess_url, headers=self.headers) bs4obj = BeautifulSoup(res.text, 'html.parser') urls = [ url.get('href') for url in bs4obj.find_all('a', {'class': 'btn'}) ] course_ids = [] for url in urls: course_ids.append(self._id_pattern.search(url).groupdict()['id']) return course_ids def __assess_course(self, course_id): try: res = self._S.get(self._urls['base_evaluateCourse_url']['http'] + course_id, headers=self.headers, timeout=5) except requests.Timeout: res = self._S.get(self._urls['base_evaluateCourse_url']['https'] + course_id, headers=self.headers) s = res.text.split('?s=')[-1].split('"')[0] bs4obj = BeautifulSoup(res.text, 'html.parser') radios = bs4obj.find_all('input', attrs={'type': 'radio'}) value = radios[0]['value'] data = {} for radio in radios: data[radio['name']] = value textareas = bs4obj.find_all('textarea') for textarea, asses_msg in zip(textareas, self._assess_msgs[0:-2]): # 填写主观评价内容 item_id = textarea.get('id') data[item_id] = asses_msg subjectiveRadio = bs4obj.find('input', { 'class': 'required radio' }).get('id') subjectiveCheckbox = bs4obj.find( 'input', {'class', 'required checkbox'}).get('id') data['subjectiveRadio'] = subjectiveRadio # 教室大小合适 data['subjectiveCheckbox'] = subjectiveCheckbox # 自己需求和兴趣 try: post_url = self._urls['base_saveCourseEval_url'][ 'http'] + course_id + '?s=' + s res = self._S.post(post_url, data=data, headers=self.headers, timeout=5) except requests.Timeout: post_url = self._urls['base_saveCourseEval_url'][ 'https'] + course_id + '?s=' + s res = self._S.post(post_url, data=data, headers=self.headers) tmp = BeautifulSoup(res.text, 'html.parser') try: flag = tmp.find('label', attrs={'id': 'loginSuccess'}) if flag.string == '保存成功': print('\033[1;45m{}评估结果:[success] \033[0m'.format(course_id)) else: print('\033[1;45m{}评估结果:[fail],请手动重新评估该课 \033[0m'.format( course_id)) except AttributeError: print('\033[1;45m{}评估结果:[fail],尝试重新评估 \033[0m'.format(course_id)) self.__assess_course(course_id) def _assess_courses(self, course_ids): self._logger.info('开始评估课程') time.sleep(2) for course_id in course_ids: self.__assess_course(course_id) self._logger.info('课程评估完毕') def _get_teacher_ids(self): # 通过课程评估url得到教师评估url teacher_assess_url = self._course_assess_url.replace( 'course', 'teacher') res = self._S.get(teacher_assess_url, headers=self.headers) bs4obj = BeautifulSoup(res.text, 'html.parser') urls = [ url.get('href') for url in bs4obj.find_all('a', {'class': 'btn'}) ] teacher_ids = [] for url in urls: teacher_ids.append(self._id_pattern.search(url).groupdict()['id']) return teacher_ids def __assess_teacher(self, teacher_id): try: res = self._S.get(self._urls['base_evaluateTeacher_url']['http'] + teacher_id, headers=self.headers, timeout=5) except requests.Timeout: res = self._S.get(self._urls['base_evaluateTeacher_url']['https'] + teacher_id, headers=self.headers) bs4obj = BeautifulSoup(res.text, 'lxml') radios = bs4obj.find_all('input', attrs={'type': 'radio'}) value = radios[0]['value'] # 默认全5星好评 data = {} for radio in radios: data[radio['name']] = value textareas = bs4obj.find_all('textarea') for textarea, asses_msg in zip(textareas, self._assess_msgs[-2:]): # 填写主观评价内容 item_id = textarea.get('id') data[item_id] = asses_msg data['subjectiveCheckbox'] = '' data['subjectiveRadio'] = '' post_action = bs4obj.find('form', {'id': 'regfrm'}) try: post_url = self._urls['base_url']['http'] + post_action.get( 'action') except requests.Timeout: post_url = self._urls['base_url']['https'] + post_action.get( 'action') try: res = self._S.post(post_url, data=data, headers=self.headers, timeout=5) except requests.Timeout: res = self._S.post(post_url, data=data, headers=self.headers) tmp = BeautifulSoup(res.text, 'html.parser') try: flag = tmp.find('label', attrs={'id': 'loginSuccess'}) if flag.string == '保存成功': print('\033[1;45m{}评估结果:[success] \033[0m'.format(teacher_id)) return else: print('\033[1;45m{}评估结果:[fail],请手动评估该教师 \033[0m'.format( teacher_id)) except AttributeError: print('\033[1;45m{}评估结果:[fail],尝试重新评估 \033[0m'.format(teacher_id)) self.__assess_teacher(teacher_id) def _assess_teachers(self, teacher_ids): self._logger.info('开始评估教师') for teacher_id in teacher_ids: self.__assess_teacher(teacher_id) self._logger.info('教师评估完毕') def run(self): self.login() course_ids = self._get_course_ids() self._assess_courses(course_ids) teacher_ids = self._get_teacher_ids() self._assess_teachers(teacher_ids)