def login(self, account, password): """ 登录校园卡网站 :param str account: 校园卡卡号 :param str password: 校园卡查询密码 :return: {'code': 1, "msg": "登录失败,请重试"} 或 {'code': 0, 'msg': '登录成功'} """ captcha_code = CardCaptcha(self.get_image(URL.card_captcha())).crack() data = { "sno": account, "pwd": base64.b64encode(str(password).encode("utf8")), "ValiCode": captcha_code, "remember": 1, "uclass": 1, "zqcode": "", "json": True, } result = self._login_execute(url=URL.card_login(), data=data) if result['code'] == 2: # 如果验证码错误,尝试递归重复登录 return self.login(account, password) result['success'] = not result['code'] if result['success']: self.verify = True else: raise AuthenticationException(result['msg']) return result
def login(self, account, password): """ 登录教务系统 jwxt.njupt.edu.cn :param account: 南邮学号 :param password: 密码 :return: {'code': 1, "msg": "登录失败"} 或 {'code': 0, 'msg': '登录成功'} """ captcha_code = ZhengfangCaptcha(self.get_image( URL.zhengfang_captcha())).crack() data = { "__VIEWSTATE": self._get_viewstate(URL.zhengfang_login()), 'txtUserName': account, 'TextBox2': password, 'RadioButtonList1': "%D1%A7%C9%FA", "txtSecretCode": captcha_code, "Button1": "", "hidPdrs": "", "hidsc": "" } result = self._login_execute(url=URL.zhengfang_login(), data=data) if result['code'] == 2: # 如果验证码错误,尝试递归重复登录 return self.login(account, password) result['success'] = not result['code'] if result['success']: self.verify = True else: raise AuthenticationException(result['msg']) return result
def _get_captcha(self, url=URL.jwxt_captcha()): r = self.get(url) im = Image.open(BytesIO(r.content)) if url == URL.aolan_captcha(): return str(AolanCaptcha(im)) if url == URL.jwxt_captcha(): return str(ZhengfangCaptcha(im))
def get_score(self): """ 获取课程成绩和绩点等信息 :return: dict {'gpa': 4.99, 'coursers': [{ 'year': '2015-2016', 'semester': '1', 'code': '00wk00003', 'name': '从"愚昧"到"科学"-科学技术简史', 'attribute': '任选', 'belong': '全校任选课', 'credit': '2.0', 'point': '', 'score': '81', 'minorMark': '0', 'makeUpScore': '', 'retakeScore': '', 'college': '网络课程', 'note': '', 'retakeMark': '0', 'englishName': '' }, ] } """ viewstate = self._get_viewstate(url=URL.jwxt_score(self.account)) data = { 'ddlXN': '', 'ddlXQ': '', '__VIEWSTATE': viewstate, 'Button2': '%D4%DA%D0%A3%D1%A7%CF%B0%B3%C9%BC%A8%B2%E9%D1%AF' } soup = self._url2soup(method='post', url=URL.jwxt_score(self.account), data=data) result = {'gpa': float(soup.select_one('#pjxfjd > b').text[7:])} cols = [ 'year', 'semester', 'code', 'name', 'attribute', 'belong', 'credit', 'point', 'score', 'minorMark', 'makeUpScore', 'retakeScore', 'college', 'note', 'retakeMark', 'englishName' ] # 学年 学期 课程代码 课程名称 课程性质 课程归属 学分 绩点 成绩 辅修标记 补考成绩 重修成绩 开课学院 备注 重修标记 英文名称 coursers = [] for tr in soup.select('#Datagrid1 > tr')[1:]: courser = {} for col, td in zip(cols, tr.select('td')): value = td.text.strip() courser[col] = value coursers.append(courser) result['coursers'] = coursers return result
def get_score(self): """ 获取课程成绩和绩点等信息 :return: dict {'gpa': 4.99, # GPA 'courses': [{ 'year': '2015-2016', # 修读学年 'semester': '1', # 修读学期 'code': '00wk00003', # 课程编号 'name': '从"愚昧"到"科学"-科学技术简史', # 课程中文名 'attribute': '任选', # 课程性质 'belong': '全校任选课', # 课程归属 'credit': '2.0', # 学分 'point': '', # 绩点 'score': '81', # 成绩 'minorMark': '0', # 重修标记 'make_up_score': '', # 补考成绩 'retake_score': '', # 重修成绩 'college': '网络课程', # 开课学院 'note': '', # 备注 'retake_mark': '0', # 重修标记 'english_name': '' # 课程英文名 }, ] } """ viewstate = self._get_viewstate(url=URL.zhengfang_score(self.account)) data = { 'ddlXN': '', 'ddlXQ': '', '__VIEWSTATE': viewstate, 'Button2': '%D4%DA%D0%A3%D1%A7%CF%B0%B3%C9%BC%A8%B2%E9%D1%AF' } soup = self.get_soup(method='post', url=URL.zhengfang_score(self.account), data=data) result = {'gpa': float(soup.select_one('#pjxfjd').text[7:])} cols = [ 'year', 'semester', 'code', 'name', 'attribute', 'belong', 'credit', 'point', 'score', 'minor_mark', 'make_up_score', 'retake_score', 'college', 'note', 'retake_mark', 'english_name' ] courses = [] for tr in soup.select('#Datagrid1 > tr')[1:]: courser = {} for col, td in zip(cols, tr.select('td')): value = td.text.strip() courser[col] = value courses.append(courser) result['courses'] = courses return result
def login(self, account, password): """ 登录南邮图书馆 jwxt.njupt.edu.cn :param account: 南邮学号 :param password: 密码 :return: {'r': 1, "msg": "登录失败"} 或 {'r': 0, 'msg': '登录成功'} """ data = { "number": account, 'passwd': password, 'captcha': self._get_captcha(URL.lib_captcha()), 'select': "cert_no", "returnUrl": "", } return self._login_execute(url=URL.jwxt_login(), data=data)
def recharge_net(self, amount=0.01): """ 充值网费(从校园卡余额中) :param amount: 金额 如 2.33 :return: dict { 'success':True, 'code' : 0, 'Msg' : '充值成功' } """ data = { 'paytype': 1, 'aid': AIDS['net'], 'account': self._get_inner_account(), 'tran': int(amount * 100), 'netacc': '{"netacc": "", "bal": null, "pkgid": null, "lostflag": null, "freezeflag": null, ' '"expflag": null,"statustime": null, "duration": null, "starttime": null, "pkgtab": []}', 'pkgflag': 'none', 'pkg': '{}', 'acctype': '###', 'qpwd': '', 'json': True } result = self._url2json(url=URL.card_net_pay(), method='post', data=data) result = json.loads(result['Msg']) return { 'success': not int(result['pay_net_gdc']['retcode']), 'code': int(result['pay_net_gdc']['retcode']), 'msg': result['pay_net_gdc']['errmsg'] }
def _recharge_electricity(self, amount, elec_aid, building_id, building, room_id): data = { "acctype": "###", "paytype": 1, "aid": elec_aid, "account": self._get_inner_account(), "tran": int(amount * 100), "roomid": room_id, "room": "", "floorid": "", "floor": "", "buildingid": building_id, "building": building, "areaid": "", "areaname": "", "json": True } r = json.loads( self.get_json(URL.card_elec_pay(), 'post', data=data)['Msg']) return { 'success': not bool(int(r['pay_elec_gdc']['retcode'])), 'code': int(r['pay_elec_gdc']['retcode']), 'msg': r['pay_elec_gdc']['errmsg'], }
def _get_viewstate(self, url=None): response = self.get(url or URL.jwxt_login()) soup = BeautifulSoup(response.content, "lxml") viewstate = soup.find('input', attrs={ "name": "__VIEWSTATE" }).get("value") return viewstate
def recharge(self, amount=0.01): """ 从绑定的银行卡中扣款充值余额 :param amount: 充值金额,默认为0.01元 :return: dict { 'success':True # 转账是否成功 'code': 0, # 状态码 'msg': '转账成功' # 附加信息 } """ data = { 'account': self._get_inner_account(), 'acctype': '###', 'tranamt': int(100 * amount), 'qpwd': '', 'paymethod': '2', 'paytype': '使用绑定的默认账号', 'client_type': 'web', 'json': True, } info = self.get_json(URL.card_recharge(), 'post', data) info = json.loads(info['Msg'])['transfer'] result = { 'code': int(info['retcode']), 'msg': info['errmsg'], 'success': not bool(int(info['retcode'])) } return result
def get_bill(self, start_date=(datetime.datetime.now() - datetime.timedelta(days=30)).strftime("%Y-%m-%d"), end_date=datetime.datetime.now().strftime("%Y-%m-%d"), rows=100, page=1): """ 查询校园卡消费记录,默认为最近一个月的消费记录 :param start_date: 查询区间的开始时间 "2017-12-27" :param end_date: 查询区间的结束时间 "2018-01-26" :param rows: 一次查询返回的最大记录数量,默认为100条记录 :param page: 如果结果数量有多页,决定返回第几页。 :return: list {'recodes': [{'balance': 39.71, # 余额 'change': -5, # 变动 'comment': '未知系统,交电费', # 注释 'department': '仙林售电处', # 操作部门 'time': '2018-01-26 20:55:40', # 时间 'type': '代扣代缴', # 类型 'week': '星期五'}, # 星期 {'balances': 39.71, 'change': -7.5, 'comment': '', 'department': '一餐厅二楼清真食堂', 'time': '2018-01-24 17:09:36', 'type': '持卡人消费', 'week': '星期三'} ... ], 'total': 52 # 总的记录数 'total_pages':2 # 总页数 'page':1 # 当前的页码 } """ data = { "sdate": start_date, "edate": end_date, "account": self._get_inner_account(), "page": page, "rows": rows } temp = self.get_json(url=URL.card_bill(), method="post", data=data) result = { 'total': temp['total'], 'recodes': [], 'total_pages': temp['total'] // rows + 1, 'page': page } if temp['rows']: for row in temp['rows']: result['recodes'].append({ 'balance': row['CARDBAL'], 'department': row['MERCNAME'].strip(), 'comment': row['JDESC'].strip(), 'change': row['TRANAMT'], 'week': row['XQ'], 'time': row['OCCTIME'], 'type': row['TRANNAME'].strip(), }) return result
def login(self, account, password): """ 登录奥兰系统 jwxt.njupt.edu.cn :param account: 南邮学号、考生号、身份证号 :param password: 密码 :return: {'r': 1, "msg": "登录失败"} 或 {'r': 0, 'msg': '登录成功'} """ data = { "__VIEWSTATE": self._get_viewstate(URL.aolan_login()), '__VIEWSTATEGENERATOR': self._get_viewstategenerator(URL.aolan_login()), 'userbh': account, 'pas2s': hashlib.md5(password.upper().encode('utf8')).hexdigest(), "vcode": self._get_captcha(URL.aolan_captcha()), "cw": "", "xzbz": "1", } return self._login_execute(url=URL.aolan_login(), data=data)
def login(self, account, password): """ 登录教务系统 jwxt.njupt.edu.cn :param account: 南邮学号 :param password: 密码 :return: {'r': 1, "msg": "登录失败"} 或 {'r': 0, 'msg': '登录成功'} """ data = { "__VIEWSTATE": self._get_viewstate(), 'txtUserName': account, 'TextBox2': password, 'RadioButtonList1': "%D1%A7%C9%FA", "txtSecretCode": self._get_captcha(URL.jwxt_captcha()), "Button1": "", "hidPdrs": "", "hidsc": "" } return self._login_execute(url=URL.jwxt_login(), data=data)
def _get_inner_account(self): """ 获取对应的内部账号,部分接口参数需要 :return: 校园卡号对应的系统内部账号 """ if not self.inner_account: info = self._url2json(URL.card_info(), method='post', data={'json': True}) result = json.loads(info['Msg']) self.inner_account = result['query_card']['card'][0]['account'] return self.inner_account
def get_courses(self): """ 获取这学期的选课情况 :return: """ soup = self.get_soup(method='get', url=URL.zhengfang_courses(self.account)) trs = soup.select('#DBGrid > tr')[1:] courses = [] for tr in trs: tds = [node.text.strip() for node in tr.select('td')] name = tds[2] teacher = tds[5] all_time = tds[8].split(';') all_room = tds[9].split(';') for time, room in zip(all_time, all_room): if time and room: week_start, week_end = map(int, week_re.search(time).groups()) courser_index = list( map( int, courser_indexs_re.search(time).groups()[0].split( ','))) week = re.search('{(.*)}', time).groups()[0] if '双周' in week and week_start % 2 == 1: week_start += 1 if '单周' in week and week_start % 2 == 0: week_start += 1 courses.append({ 'day': chinese_rome[time[1]], 'name': name, 'week': week, 'week_start': week_start, 'week_end': week_end, 'class_start': courser_index[0], 'class_end': courser_index[-1], 'teacher': teacher, 'room': room, 'interval': 2 if '单周' in week or '双周' in week else 1, }) return courses
def wrapper(self, *args, **kwargs): if self.verify: # 利用状态量进行状态的判定 return func(self, *args, **kwargs) else: # 先判断有没有cookie文件, 再判断cookie是否有效 if 'WEB' in requests.utils.dict_from_cookiejar(self.cookies): # 判断教务系统是否登录成功,根据正方的一个地址无需参数的地址 r = self.get_soup(method="get", url=URL.zhengfang_logintest()) if 'Object moved to ' not in r.text: self.verify = True return func(self, *args, **kwargs) raise UnauthorizedError('未登录正方系统,请先登录')
def get_schedule(self, week, year=None, semester=None): """ 获取指定学期指定周的课表(不传入年份和学期则默认当前学期) :param year: 学年 格式为 "2017-2018" :param semester: 学期 数字1或2 :param week: 周次 数字 1-20 :return: 二维列表schedule,schedule[i][j]代表周i第j节课的课程。 为了方便,i或j为零0的单元均不使用。 列表的元素为None,代表没有课程,或描述课程信息的dict,dict例如 { 'classroom': '教4-202', 'name': '技术经济学', 'teacher': '储成祥' } """ schedule = [[None for col in range(13)] for row in range(8)] if year and semester: pass else: r = self.get(url=URL.zhengfang_class_schedule(self.account)) soup = BeautifulSoup(r.text.replace('<br>', '\n'), 'lxml') trs = soup.select("#Table1 > tr") for index, tr in enumerate(trs): tds = tr.select('td') for td in tds: if len(td.text) > 4: # 筛选出包含课程信息的单元格 for courser in td.text.split('\n\n'): info = courser.split() start_week, end_week = map( int, week_re.search(info[1]).groups()) courser_index = map( int, courser_indexs_re.search( info[1]).groups()[0].split(',')) is_odd_week_only = "单周" in info[1] is_even_week_only = "双周" in info[1] week_day = chinese_rome[info[1][1]] courser = { 'name': info[0], 'teacher': info[2], 'classroom': info[3], } if start_week <= week <= end_week: if (week % 2 == 0 and is_odd_week_only) or ( week % 2 == 1 and is_even_week_only): pass else: for courser_index in courser_index: schedule[week_day][ courser_index] = courser return schedule
def get_net_balance(self): """ 获取Dr.com的上网费用余额 :return: float 2.33 """ data = { 'jsondata': '{"query_net_info": {"aid": "%s", "account": "%s", "payacc": ""}}' # 无法使用format格式化 % (AIDS['net'], self._get_inner_account()), 'funname': 'synjones.onecard.query.net.info', 'json': True, } result = self._url2json(url=URL.card_common(), data=data, method='post') result = json.loads(result['Msg']) return float(re.search(r'余额(\d*\.\d*)元', result['query_net_info']['errmsg']).groups()[0])
def _get_build_ids(self, aid): """获取建筑的id""" data = { 'jsondata': '{"query_elec_building": {"aid": "%s", "account": "%s", "area": {"area": "", "areaname": ""}}}' % (aid, self._get_inner_account()), 'funname': 'synjones.onecard.query.elec.building', 'json': True, } result = self._url2json(data=data, url=URL.card_common(), method='post') result = json.loads(result['Msg']) # - -! building_id = {} for build in result['query_elec_building']['buildingtab']: building_id[build['building']] = build['buildingid'] return building_id
def wrapper(self, *args, **kwargs): if self.verify: # 利用状态量进行状态的判定 return func(self, *args, **kwargs) else: # 先判断有没有cookie文件, 再判断cookie是否有效 if 'WEB' in requests.utils.dict_from_cookiejar(self.cookies): # 判断教务系统是否登录成功,根据正方的一个地址无需参数的地址 r = self._url2soup(method="get", url=URL.jwxt_logintest()) if 'Object moved to ' not in r.text: self.verify = True return func(self, *args, **kwargs) while True: # 运行到此处代表cookies无效 data = self.login(self.account, input('输入密码:')) if data.get('r') == 0: self.verify = True return func(self, *args, **kwargs) else: print(data.get('msg'))
def wrapper(self, *args, **kwargs): success = False # 先判断有没有cookie文件, 再判断cookie是否有效 if 'WEB' in requests.utils.dict_from_cookiejar(self.cookies): # 判断教务系统是否登录成功,根据正方的一个地址无需参数的地址 r = self._url2soup(method="get", url=URL.jwxt_logintest()) if 'Object moved to ' not in r.text: success = True while not success: account = input("请输入教务系统账号:") self.account = account password = input("请输入账号密码:") data = self.login(account, password) if data.get("r") == 0: success = True else: print(data.get("msg")) else: return func(self, *args, **kwargs)
def get_class_schedule(self, week, year=None, semester=None): """ 获取指定学期指定周的课表(不传入年份和学期则默认当前学期) :param year: 学年 格式为 "2017-2018" :param semester: 学期 数字1或2 :param week: 周次 数字 1-20 :return: """ if year and semester: pass else: r = self.get(url=URL.jwxt_class_schedule(self.account)) soup = BeautifulSoup(r.text.replace('<br>', '\n'), 'lxml') trs = soup.select("#Table1 > tr") schedule = {} for tr in trs: tds = tr.select('td') for td in tds: print(td.text.split())
def get_balance(self): """ 查询余额 :return: dict { 'balance': 10.01, # 到账余额 'unsettle_balance': 0.01 # 过渡余额 'total': 10.02 # 总余额 } """ info = self._url2json(URL.card_info(), method='post', data={'json': True}) result = json.loads(info['Msg']) balance = int(result['query_card']['card'][0]['db_balance']) / 100 # 到账余额 unsettle_balance = int(result['query_card']['card'][0]['unsettle_amount']) / 100 # 过渡余额 return { 'balance': balance, # 到账余额 'unsettle_balance': unsettle_balance, # 过渡余额 'total': balance + unsettle_balance, # 总余额 }
def get_grade(self): """ 获取等级考试成绩信息 :return: list [ {'date': '20151219', # 考试日期 'name': '全国大学英语四级考试', # 考试名称 'number': '320082152113313', # 准考证号 'score': '547', # 成绩 'semester': '1', # 学期 'year': '2015-2016' # 学年 }, ] """ soup = self.get_soup(method='get', url=URL.zhengfang_grade(self.account)) results = [] for tr in soup.select("#DataGrid1 > tr")[1:]: names = ['year', 'semester', 'name', 'number', 'date', 'score'] result = {} for name, td in zip(names, tr.select('td')[:6]): result[name] = td.text results.append(result) return results
:param data_type: 提交的数据格式(可能是表单类型,也可能是json格式的字符串) :param kwargs: requests支持的参数,比如可以设置代理参数 :return: BeautifulSoup对象 """ # r = getattr(self, method)(url, json=json, data=data, params=params, **kwargs) # if r.ok: # soup = BeautifulSoup(r.text, 'lxml') # return soup # else: # raise ConnectionError("检查网络连接") try: # 出现网络连接问题,直接在该处抛出错误 r = getattr(self, method)(url, json=json, data=data, params=params, **kwargs) except ConnectionError: raise ConnectionError("请检查网络连接") else: if r.ok: # 状态码小于400为True soup = BeautifulSoup(r.text, 'lxml') return soup else: # 处理其他状态码 raise Exception('请确保能够正常访问当前页面: {}'.format(url)) if __name__ == "__main__": test_model = Model() test_model._get_captcha(URL.aolan_captcha())
手动获取奥兰系统验证码的训练集 """ import hashlib import os import time from io import BytesIO import requests from PIL import Image from njupt.settings import BASE_DIR from njupt.urls import URL from njupt.utils.aolan.aolan_captcha import letters for i in range(100): r = requests.get(URL.aolan_captcha()) im = Image.open(BytesIO(r.content)) # 灰度处理 imgry = im.convert('L') # 二值处理 threshold = 220 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) out = imgry.point(table, '1') out.show() numbers = input("输入数值")
def _get_captcha(self, url=URL.jwxt_captcha()): r = self.get(url) im = Image.open(BytesIO(r.content)) im.show() captcha = input("输入验证码:") return captcha
def crack(self): result = [] v = VectorTools() # 装载训练数据集 with open(os.path.join(current_dir, 'imageset.dat'), 'rb+') as f: imageset = pickle.load(f) for letter in self.handle_split_image(): guess = [] for image in imageset: for x, y in image.items(): if len(y) != 0: guess.append((v.relation(y[0], self.buildvector(letter)), x)) guess.sort(reverse=True) neighbors = guess[:10] # 距离最近的十个向量 class_votes = {} # 投票 for neighbor in neighbors: class_votes.setdefault(neighbor[-1], 0) class_votes[neighbor[-1]] += 1 sorted_votes = sorted(class_votes.items(), key=lambda x: x[1], reverse=True) result.append(sorted_votes[0][0]) return ''.join(result) def __str__(self): return self.crack() if __name__ == "__main__": r = requests.get(URL.zhengfang_captcha()) im = Image.open(BytesIO(r.content)) print(ZhengfangCaptcha(im))