├── .gitignore ├── COPYING ├── README.md ├── check_in.py ├── choose_cs_exp.py ├── choose_pe_class.py ├── credentials.sample.py ├── export_physics_experiment.py ├── export_timetable.py ├── get_course_data.py ├── get_electricity_balance.py ├── get_grades.py ├── get_network_balance_of_current_device.py ├── get_network_usage.py ├── get_rs_campus_recruitment.py ├── get_sport_measure_score.py ├── get_sports_punch_records.py ├── get_xdoj_outside.py └── get_xidian_news.py /.gitignore: -------------------------------------------------------------------------------- 1 | configurations.py 2 | credentials.py 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # PyInstaller 13 | # Usually these files are written by a python script from a template 14 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 15 | *.manifest 16 | *.spec 17 | 18 | # Installer logs 19 | pip-log.txt 20 | pip-delete-this-directory.txt 21 | 22 | # Unit test / coverage reports 23 | htmlcov/ 24 | .tox/ 25 | .coverage 26 | .coverage.* 27 | .cache 28 | nosetests.xml 29 | coverage.xml 30 | *.cover 31 | .hypothesis/ 32 | .pytest_cache/ 33 | 34 | # Translations 35 | *.mo 36 | *.pot 37 | 38 | # Django stuff: 39 | *.log 40 | local_settings.py 41 | db.sqlite3 42 | 43 | # Flask stuff: 44 | instance/ 45 | .webassets-cache 46 | 47 | # Scrapy stuff: 48 | .scrapy 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Jupyter Notebook 57 | .ipynb_checkpoints 58 | 59 | # pyenv 60 | .python-version 61 | 62 | # celery beat schedule file 63 | celerybeat-schedule 64 | 65 | # SageMath parsed files 66 | *.sage.py 67 | 68 | # Environments 69 | .env 70 | .venv 71 | env/ 72 | venv/ 73 | ENV/ 74 | env.bak/ 75 | venv.bak/ 76 | 77 | # Spyder project settings 78 | .spyderproject 79 | .spyproject 80 | 81 | # Rope project settings 82 | .ropeproject 83 | 84 | # mkdocs documentation 85 | /site 86 | 87 | # mypy 88 | .mypy_cache/ 89 | 90 | # ide 91 | .vscode/ 92 | .idea/ 93 | 94 | # res files 95 | .DS_Store 96 | 97 | test.py 98 | credentials.py 99 | configurations.py -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xidian-scripts 2 | 3 | [![License: LGPL v3+](https://img.shields.io/badge/License-LGPL%20v3+-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) 4 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 5 | 6 | ## 这是什么? 7 | 8 | 西安电子科技大学校园生活的一些实用小脚本 9 | 10 | ## 开始使用 11 | 12 | 请在运行脚本前设置一些环境变量。Linux下可以写进`~/.*shrc` 13 | 14 | |环境变量|用到这个环境变量的脚本|补充说明| 15 | |:-:|:-:|:-:| 16 | |IDS_USER/IDS_PASS|export_timetable
get_grades.py
get_borrowed_books
check_in
get_course_data|对应西电统一认证服务的用户名密码| 17 | |WX_USER/WX_PASS|get_borrowed_books
get_card_balance
query_card_bill|由于此服务与统一认证密码保持一致,若脚本找不到这两个环境变量,则会使用IDS_USER/IDS_PASS| 18 | |PAY_USER/PAY_PASS|get_network_usage|对应zfw.xidian.edu.cn用户名密码,此脚本由于需要识别验证码,需要安装tesseract才能正常运行,且登陆速度可能较慢| 19 | |ENERGY_USER/ENERGY_PASS|get_electricity_balance|对应宿舍电费账户| 20 | |RS_USER/RS_PASS|get_rs_campus_recruitment|睿思校外站| 21 | |SPORTS_USER/SPORTS_PASS|get_sports_punch_records|对应体适能用户名和密码| 22 | |EXP_CS_PASSWORD|choose_cs_exp|计科院系统实验教学中心密码| 23 | 24 | --- 25 | 26 | 如果不想设置环境变量或者想一次性配好所有变量,可以通过编辑`credentials.py`完成: 27 | 28 | 1. 首先复制`credentials.sample.py`为`credentials.py` 29 | 2. 开始编辑`credentials.py`即可 30 | 31 | 注:`credentials.py`需要和你使用的脚本在同一个路径下 32 | 33 | ### For Example 34 | 35 | 在命令行直接运行: 36 | `IDS_USER=学号 IDS_PASS=密码 python3 get_grades.py` 37 | 38 | 或者在`~/.bashrc`(假如你使用的是bash)中加入: 39 | ```sh 40 | export IDS_USER=学号 41 | export IDS_PASS=密码 42 | ``` 43 | 重启终端或执行`source ~/.bashrc`后执行: 44 | `python3 get_grades.py` 45 | 46 | ### Manifest 47 | 48 | * get_borrowed_books: 看看你借过哪些书 49 | * get_card_balance: 查询一卡通余额 50 | * get_electricity_balance*: 查询电量余额 51 | * get_grades: 看看你考了多少分 52 | * get_network_usage: 看看你的 10G 流量还有多少 53 | * get_xdoj_outside: 把你在 `acm.xidian.edu.cn` 上交过的代码都扒拉下来 54 | * export_timetable: 把当前学期课表保存为`.ics`格式,以便导入到日历软件中。 55 | * export_physics_experiment.py*: 将当前学期的物理实验保存为`.ics`格式,以便导入到日历软件中。 56 | * query_card_bill: 查询一卡通在指定时间段(30天内)的消费记录 57 | * get_rs_campus_recruitment: 获取睿思论坛上的校园招聘信息 58 | * check_in: 2020 晨午晚检 59 | * get_sports_punch_records: 查询体育打卡次数 60 | * choose_pe_class: 体育课选课 61 | * choose_cs_exp: 计科院实验选课 62 | * get_course_data:获取课程数据如签到次数,签到率等,保存为csv文件 63 | * get_network_balance_of_current_device*: 获取当前设备的校园网流量使用情况 64 | 65 | ### 备注 66 | 67 | 1. 标*号的脚本只能在西电内网使用 68 | 1. 设置好上面这些环境变量,就可以直接执行脚本了。你可以只保留或只下载自己所需要的脚本。 69 | 1. 如果希望将运行结果推送至类似[Server酱](https://sc.ftqq.com)这样的平台,可以参考下面的命令(以get_rs_campus_recruitment为例) 70 | 71 | ```bash 72 | echo "text=HR_news&desp=`python3 get_rs_campus_recruitment.py --markdown --urlencode`" | \ 73 | curl -d @- https://sc.ftqq.com/{你的SEKEY}.send 74 | ``` 75 | 76 | ## 参与贡献 77 | 78 | ### As a programmer 79 | 80 | 直接 PR 即可,同时我们欢迎其他自由的语言实现。 81 | 本仓库的目的更多是鼓励大家尝试在 GitHub 上参与程序的编写,请大胆提 Pull Request,不必担心。 82 | 仓库的维护者会尽最大可能帮助你熟悉 git workflow。 83 | 84 | ### As a reviewer 85 | 86 | 点击右上角的 watch,关注并检验每一次 PR 并对结果作出回复。 87 | 如何检验?使用 GNU diffutils 或你喜欢的工具应用 PR 邮件里的Patch Links。 88 | 89 | ### 要求 90 | 91 | 至少要保证能在 GNU/Linux 或者其他自由的操作系统下能运行。 92 | 只能在专有的操作系统上运行的不会允许(或者即使程序本身是自由的,但是依赖专有库的也不会允许)。 93 | 94 | ## 友情链接 95 | 96 | [Traintime PDA / XDYou]([https://www.coolapk.com/apk/249065](https://github.com/BenderBlog/traintime_pda)) by @BenderBlog 97 | [电表](https://www.coolapk.com/apk/249065) by @Robotxm 98 | [oh-my-xdu](https://github.com/zkonge/oh-my-xdu) by @zkonge 99 | -------------------------------------------------------------------------------- /check_in.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import pytz 4 | import datetime 5 | import requests 6 | 7 | try: 8 | import credentials 9 | USERNAME = credentials.IDS_USERNAME 10 | PASSWORD = credentials.IDS_PASSWORD 11 | except ImportError: 12 | USERNAME, PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'IDS_PASS')] 13 | 14 | 15 | def commit_data(username, password): 16 | sess = requests.session() 17 | sess.post( 18 | 'https://xxcapp.xidian.edu.cn/uc/wap/login/check', data={ 19 | 'username': username, 20 | 'password': password 21 | }) 22 | return sess.post( 23 | 'https://xxcapp.xidian.edu.cn/xisuncov/wap/open-report/save', data={ 24 | 'sfzx': '1', 'tw': '1', 25 | 'area': '陕西省 西安市 长安区', 26 | 'city': '西安市', 'province': '陕西省', 27 | 'address': '陕西省西安市长安区兴隆街道梧桐大道西安电子科技大学长安校区', 28 | 'geo_api_info': '{"type":"complete","position":{"Q":34.129092068143,"R":108.83138888888902,"lng":108.831389,"lat":34.129092},"location_type":"html5","message":"Get geolocation success.Convert Success.Get address success.","accuracy":65,"isConverted":true,"status":1,"addressComponent":{"citycode":"029","adcode":"610116","businessAreas":[],"neighborhoodType":"","neighborhood":"","building":"","buildingType":"","street":"雷甘路","streetNumber":"266#","country":"中国","province":"陕西省","city":"西安市","district":"长安区","township":"兴隆街道"},"formattedAddress":"陕西省西安市长安区兴隆街道梧桐大道西安电子科技大学长安校区","roads":[],"crosses":[],"pois":[],"info":"SUCCESS"}', 29 | 'sfcyglq': '0', 'sfyzz': '0', 'qtqk': '', 'ymtys': '0' 30 | } 31 | ).json()['m'] 32 | 33 | 34 | def get_hour_message(): 35 | h = datetime.datetime.fromtimestamp( 36 | int(time.time()), pytz.timezone('Asia/Shanghai')).hour 37 | return "睡晨午晚"[h // 6] 38 | 39 | 40 | def main_handler(event, context): 41 | message = commit_data(USERNAME, PASSWORD) 42 | print(f'[{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")}] {message}') 43 | message = get_hour_message() + '检-' + message 44 | 45 | if __name__ == "__main__": 46 | main_handler(None, None) 47 | -------------------------------------------------------------------------------- /choose_cs_exp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | # @author: https://github.com/akynazh 19 | # @date: 2023-03-09 20 | 21 | import asyncio 22 | import aiohttp 23 | import time 24 | import os 25 | from prettytable import PrettyTable 26 | from bs4 import BeautifulSoup 27 | 28 | try: 29 | import credentials 30 | USERNAME = credentials.IDS_USERNAME 31 | PASSWORD = credentials.EXP_CS_PASSWORD 32 | except ImportError: 33 | USERNAME, PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'EXP_PASSWORD')] 34 | if not USERNAME or not PASSWORD: 35 | print('请设置环境变量 IDS_USER 和 EXP_PASSWORD') 36 | exit(1) 37 | 38 | BASE_URL = 'http://222.25.176.4' 39 | MAX_COROUINES = 5 40 | TIMEOUT = 3 41 | CLASS_NO = '2003%BC%B6' 42 | 43 | 44 | def DUMMY(): 45 | return time.time() * 1000 46 | 47 | 48 | async def login(session: aiohttp.ClientSession): 49 | '''尝试登录 50 | 51 | :param aiohttp.ClientSession session 52 | :return bool: 登录成功与否 53 | ''' 54 | resp = await session.post( 55 | url=f'{BASE_URL}/index.php', 56 | data={ 57 | 'username': USERNAME, 58 | 'userpwd': PASSWORD, 59 | 'user': 'student', 60 | }, 61 | ) 62 | if resp.status != 200: 63 | return 64 | session.cookie_jar.update_cookies(resp.cookies) 65 | resp = await session.get(url=f'{BASE_URL}/student/default.php') 66 | if resp.status != 200: 67 | return 68 | resp_text = await resp.text() 69 | if resp_text.find(USERNAME) != -1: 70 | return True 71 | 72 | 73 | async def get_exp_info(session: aiohttp.ClientSession) -> dict: 74 | '''尝试获取实验信息:实验名称 => 实验对应编号 => 教室门牌号列表 75 | 76 | :param aiohttp.ClientSession session 77 | :return dict: 实验信息 78 | ''' 79 | exp_info = [] 80 | 81 | async def get_crnos_by_exp(name: str, cid: str): 82 | '''根据实验获取对应教室 83 | 84 | :param str name: 实验名称 85 | :param str cid: 实验编号 86 | ''' 87 | if cid == '-1': 88 | return 89 | resp = await session.get( 90 | url= 91 | f'{BASE_URL}/student/ajax-showClassroom.php?dummy={DUMMY()}&cid={cid}&classno={CLASS_NO}', 92 | ) 93 | if resp.status == 200: 94 | resp_text = await resp.text() 95 | crnos = resp_text.split('#') 96 | exp_info.append({ 97 | 'cid': cid.strip(), 98 | 'name': name.strip(), 99 | 'crnos': crnos 100 | }) 101 | else: 102 | exp_info.append({'cid': cid, 'name': name, 'crnos': []}) 103 | 104 | resp = await session.get( 105 | url= 106 | f'{BASE_URL}/student/showCourses.php?dummy={DUMMY()}&classno={CLASS_NO}', 107 | ) 108 | if resp.status != 200: 109 | return 110 | try: 111 | soup = BeautifulSoup(await resp.text(), 'lxml') 112 | options = soup.find(id='paikeFormId').find_all('option') 113 | tasks = [ 114 | asyncio.ensure_future( 115 | get_crnos_by_exp(name=op.text, cid=op['value'])) 116 | for op in options 117 | ] 118 | for task in asyncio.as_completed(tasks): 119 | await task 120 | return exp_info 121 | except Exception as e: 122 | print(e) 123 | return 124 | 125 | 126 | async def submit_exp_choice(session: aiohttp.ClientSession, crno: str, 127 | cid: str): 128 | '''尝试选实验 129 | 130 | :param aiohttp.ClientSession session 131 | :param str crno: 实验教室 132 | :param str cid: 实验编号 133 | :return bool: 是否成功(或需要修改参数) 134 | ''' 135 | try: 136 | url = f'{BASE_URL}/student/ajax-submitXuanke.php?dummy={DUMMY()}&user={USERNAME}&crno={crno}&cid={cid}&classno={CLASS_NO}' 137 | # a sample: http://222.25.176.4/student/ajax-submitXuanke.php?dummy=1678160826939&user=20009100359&crno=E-II312&cid=115&classno=2003%BC%B6 138 | print(f'开始提交选择,请求地址:{url}') 139 | resp = await session.get(url=url) 140 | if resp.status == 200: 141 | code = await resp.text() 142 | code = code.strip() 143 | if code == '1': 144 | print('选课成功, 你可以在"查看已选课程"查看该课程信息') 145 | return True 146 | elif code == '2': 147 | print('你已经选过此课程了') 148 | return True 149 | elif code == '3': 150 | print('已经被选满') 151 | return True 152 | elif code == '4': 153 | print('提交过程出现错误') 154 | elif code == '5': 155 | print('在选择第 1 课次前,不能选择后面的课次') 156 | return True 157 | elif code == '6': 158 | print('你怎么可能点了不是第 1 次课的课程呢') 159 | return True 160 | elif code == '-1': 161 | print('该课程安排已经过了选课日期') 162 | return True 163 | else: 164 | print('出现错误,选课失败') 165 | else: 166 | print('出现错误,选课失败') 167 | except asyncio.CancelledError: 168 | pass 169 | 170 | 171 | async def get_submit_res(session): 172 | '''获取实验选择结果 173 | ''' 174 | table = PrettyTable() 175 | url = f'{BASE_URL}/student/showMyCourses.php' 176 | print('尝试获取实验选择结果......') 177 | resp = await session.get(url) 178 | if resp.status == 200: 179 | try: 180 | soup = BeautifulSoup(await resp.text(), 'lxml') 181 | rows = soup.find('table').find_all('tr') 182 | for i, row in enumerate(rows): 183 | if i == 0: 184 | cols = row.find_all('th') 185 | table.field_names = [col.text.strip() for col in cols] 186 | else: 187 | cols = row.find_all('td') 188 | table.add_row([col.text.strip() for col in cols]) 189 | print(table) 190 | except Exception as e: 191 | print(f'获取失败:{e}') 192 | else: 193 | print('获取失败,请重试') 194 | 195 | 196 | async def main(): 197 | async with aiohttp.ClientSession() as session: 198 | print('尝试登录......') 199 | while True: 200 | if await login(session): 201 | print('登录成功!') 202 | break 203 | print('登录失败,请检查网络或用户名和密码') 204 | time.sleep(0.5) 205 | print(f'开启 {MAX_COROUINES} 个协程,尝试获取实验信息......') 206 | while True: 207 | info_flag = False 208 | tasks = [ 209 | asyncio.ensure_future(get_exp_info(session)) 210 | for i in range(MAX_COROUINES) 211 | ] 212 | for task in asyncio.as_completed(tasks): 213 | exp_info = await task 214 | if exp_info: 215 | info_flag = True 216 | break 217 | if info_flag: 218 | break 219 | print('成功获取实验信息:') 220 | for exp in exp_info: 221 | print(f'实验名称:{exp["name"]},实验编号:{exp["cid"]},实验教室:{exp["crnos"]}') 222 | # choose 223 | cid = input('请输入实验编号 >>> ') 224 | crno = input('请输入实验教室 >>> ') 225 | print(f'开启 {MAX_COROUINES} 个协程,尝试提交选择......') 226 | # submit choice 227 | while True: 228 | submit_flag = False 229 | tasks = [ 230 | asyncio.create_task( 231 | submit_exp_choice(session=session, crno=crno, cid=cid)) 232 | for _ in range(MAX_COROUINES) 233 | ] 234 | done, pending = await asyncio.wait( 235 | tasks, return_when=asyncio.FIRST_COMPLETED) 236 | for task in done: 237 | if task.result(): 238 | submit_flag = True 239 | for task in pending: 240 | task.cancel() 241 | if submit_flag: 242 | break 243 | await get_submit_res(session) 244 | 245 | 246 | if __name__ == '__main__': 247 | asyncio.run(main()) -------------------------------------------------------------------------------- /choose_pe_class.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.IDS_USERNAME 32 | PASSWORD = credentials.IDS_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'IDS_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 IDS_USER 和 IDS_PASS') 37 | exit(1) 38 | 39 | import time 40 | from libxduauth import IDSSession 41 | 42 | 43 | def process(): 44 | whatever = IDSSession("http://tybjxgl.xidian.edu.cn", USERNAME, PASSWORD) 45 | classes = whatever.post( 46 | "http://tybjxgl.xidian.edu.cn/admin/" 47 | "chooseCurriculum/showTeachingCurriculum" 48 | ).json()["data"] 49 | for i in classes: 50 | print( 51 | f'{str(i["id"])} {i["sysUserName"]} ' 52 | f'{i["teachingCurriculumName"]} {i["teachingSchoolTimeName"]}' 53 | ) 54 | ohyeah = input("输入你想上课程的id: ") 55 | choice = whatever.post( 56 | "http://tybjxgl.xidian.edu.cn/admin//" 57 | "stuTeacherCurriculum/chooseTeachingCurriculum?" 58 | f"teaCurriculumid={ohyeah}&_={int(round(time.time() * 1000))}" 59 | ) 60 | print(choice.content.decode()) 61 | 62 | 63 | if __name__ == "__main__": 64 | process() 65 | -------------------------------------------------------------------------------- /credentials.sample.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | STUDENT_ID = '' 19 | # 你的学号 20 | 21 | IDS_USERNAME = STUDENT_ID 22 | # ids.xidian.edu.cn的用户名,一般来说是学号 23 | 24 | IDS_PASSWORD = '' 25 | # ids.xidian.edu.cn的密码,一般是身份证后六位 26 | 27 | WX_USERNAME = STUDENT_ID 28 | # wx.xidian.edu.cn/wx_xdu的用户名 29 | 30 | WX_PASSWORD = IDS_PASSWORD 31 | # wx.xidian.edu.cn/wx_xdu的密码,和ids同步 32 | # 在ids上改密码这边的也会变,所以不要单独改 33 | 34 | XDOJ_USERNAME = STUDENT_ID 35 | # 202.117.120.31/xdoj的用户名 36 | 37 | XDOJ_PASSWORD = '' 38 | # 202.117.120.31/xdoj的密码 39 | 40 | PAY_USERNAME = STUDENT_ID 41 | # pay.xidian.edu.cn 42 | 43 | PAY_PASSWORD = '' 44 | 45 | PHYSICS_USERNAME = STUDENT_ID 46 | # Username for physics experiment system(wlsy.xidian.edu.cn). Normally it is your student id. 47 | 48 | PHYSICS_PASSWORD = '' 49 | # Password for physics experiment system 50 | 51 | ELECTRICITY_USERNAME = '' 52 | # Username for electricty system(http://10.168.55.50:8088/). 53 | # 查询工具: https://github.com/Hoooxz/xd-charge 54 | 55 | ELECTRICITY_PASSWORD = '123456' 56 | # Password for electricty system. Default value is 123456 57 | 58 | SPORTS_USERNAME = IDS_USERNAME 59 | # Username for sports system. Normally it is your student id. 60 | 61 | SPORTS_PASSWORD = '' 62 | # Password for sports system. 63 | 64 | RS_USERNAME = '' 65 | # Username for rs 66 | 67 | RS_PASSWORD = '' 68 | # Password for rs 69 | 70 | EXP_CS_PASSWORD = '' 71 | # Password for 222.25.176.4 -------------------------------------------------------------------------------- /export_physics_experiment.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth', 'icalendar')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth', 'icalendar' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.PHYSICS_USERNAME 32 | PASSWORD = credentials.PHYSICS_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('PHYSICS_USER', 'PHYSICS_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 PHYSICS_USER 和 PHYSICS_PASS') 37 | exit(1) 38 | 39 | from icalendar import Calendar, Event 40 | from datetime import datetime, timedelta 41 | import requests 42 | import re 43 | 44 | expList = [] 45 | 46 | 47 | def get_experiments(): 48 | expList = [] 49 | ses = requests.session() 50 | ses.post('http://wlsy.xidian.edu.cn/PhyEws/default.aspx', 51 | data={ 52 | '__EVENTTARGET': '', 53 | '__EVENTARGUMENT': '', 54 | '__VIEWSTATE': '/wEPDwUKMTEzNzM0MjM0OWQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFD2xvZ2luMSRidG5Mb2dpbutGpJNAAaBhxseXkh1n/woLBppW', 55 | '__VIEWSTATEGENERATOR': 'EE008CD9', 56 | '__EVENTVALIDATION': '/wEWBwLsvJu+AgKckJOGDgKD8YXRCQLJ5dDDBAKVx8n1CQKytMi0AQKcg465CqDdcB40IuBzviNuzXl4xNRdD759', 57 | 'login1$StuLoginID': USERNAME, 58 | 'login1$StuPassword': PASSWORD, 59 | 'login1$UserRole': 'Student', 60 | 'login1$btnLogin.x': '0', 61 | 'login1$btnLogin.y': '0' 62 | }) 63 | html = ses.get('http://wlsy.xidian.edu.cn/PhyEws/student/select.aspx') 64 | 65 | pattern = re.compile( 66 | r'((?!《物理实验》)(?!下载).*?)([0-9]学时)' 67 | r'[0-9]{1,2}' 68 | r'星期(.*?)((([01][0-9]|2[0-3]):[0-5][0-9])\-(([01][0-9]|2[0-3]):[0-5][0-9]))' 69 | r'([0-9]{1,2}/[0-9]{1,2}/[0-9]{4})' 70 | r'([A-F]([0-999]{3,3}))') 71 | result = pattern.findall(html.text) 72 | for exp in result: 73 | expName = exp[1] 74 | expStart = exp[4] 75 | expEnd = exp[6] 76 | expDate = exp[8] 77 | expClassroom = exp[9] 78 | expList.append([expName, expStart, expEnd, expDate, expClassroom]) 79 | return expList 80 | 81 | 82 | source = input("请确保当前处于校园网或翼讯网络环境下,回车继续...") 83 | expList = get_experiments() 84 | cal = Calendar() 85 | for exp in expList: 86 | e = Event() 87 | e.add("description", exp[0] + ' @ ' + exp[4]) 88 | e.add('summary', exp[0] + ' @ ' + exp[4]) 89 | start = datetime.strptime(exp[3] + ' ' + exp[1], '%m/%d/%Y %H:%M') 90 | e.add('dtstart', start) 91 | end = datetime.strptime(exp[3] + ' ' + exp[2], '%m/%d/%Y %H:%M') 92 | e.add('dtend', end) 93 | e.add('location', exp[4]) 94 | cal.add_component(e) 95 | 96 | f = open(USERNAME + '_physicsExperiment.ics', 'wb') 97 | f.write(cal.to_ical()) 98 | f.close() 99 | print("物理实验日历文件已保存到 " + USERNAME + '_physicsExperiment.ics') 100 | -------------------------------------------------------------------------------- /export_timetable.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth', 'icalendar')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth', 'icalendar' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.IDS_USERNAME 32 | PASSWORD = credentials.IDS_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'IDS_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 IDS_USER 和 IDS_PASS') 37 | exit(1) 38 | 39 | from icalendar import Calendar, Event 40 | from datetime import datetime, timedelta 41 | from libxduauth import EhallSession 42 | 43 | TIME_SCHED = [ 44 | ("8:30", "10:05"), 45 | ("10:25", "12:00"), 46 | ("14:00", "15:35"), 47 | ("15:55", "17:30"), 48 | ("19:00", "20:35") 49 | ] 50 | 51 | ses = EhallSession(USERNAME, PASSWORD) 52 | ses.use_app(4770397878132218) 53 | 54 | 55 | semesterCode = ses.post( 56 | 'http://ehall.xidian.edu.cn/jwapp/sys/wdkb/modules/jshkcb/dqxnxq.do', 57 | headers={ 58 | 'Accept': 'application/json, text/javascript, */*; q=0.01' 59 | } 60 | ).json()['datas']['dqxnxq']['rows'][0]['DM'] 61 | termStartDay = datetime.strptime(ses.post( 62 | 'http://ehall.xidian.edu.cn/jwapp/sys/wdkb/modules/jshkcb/cxjcs.do', 63 | headers={ 64 | 'Accept': 'application/json, text/javascript, */*; q=0.01' 65 | }, 66 | data={ 67 | 'XN': semesterCode.split('-')[0] + '-' + semesterCode.split('-')[1], 68 | 'XQ': semesterCode.split('-')[2] 69 | } 70 | ).json()['datas']['cxjcs']['rows'][0]["XQKSRQ"].split(' ')[0], '%Y-%m-%d') 71 | qResult = ses.post( 72 | 'http://ehall.xidian.edu.cn/jwapp/sys/wdkb/modules/xskcb/xskcb.do', 73 | headers={ # 学生课程表 74 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 75 | 'Accept': 'application/json, text/javascript, */*; q=0.01' 76 | }, data={ 77 | 'XNXQDM': semesterCode 78 | } 79 | ).json() 80 | qResult = qResult['datas']['xskcb'] # 学生课程表 81 | if qResult['extParams']['code'] != 1: 82 | raise Exception(qResult['extParams']['msg']) 83 | 84 | courseList = [] 85 | for i in qResult['rows']: 86 | while len(courseList) < len(i['SKZC']): 87 | courseList.append([[], [], [], [], [], [], []]) 88 | for j in range(len(i['SKZC'])): 89 | if i['SKZC'][j] == '1' and int(i['KSJC']) <= 10 and int(i['JSJC']) <= 10: 90 | courseList[j][int(i['SKXQ']) - 1].append({ 91 | 'name': i['KCM'], 92 | 'location': i['JASMC'], 93 | 'sectionSpan': (int(i['KSJC']), int(i['JSJC'])) 94 | }) 95 | 96 | cal = Calendar() 97 | for week_cnt in range(len(courseList)): 98 | for day_cnt in range(len(courseList[week_cnt])): 99 | for course in courseList[week_cnt][day_cnt]: 100 | e = Event() 101 | if course['sectionSpan'][0] > 10: 102 | continue 103 | elif course['location'] == None: 104 | course['location'] = '待定' 105 | e.add( 106 | "description", 107 | '课程名称:' + course['name'] + 108 | ';上课地点:' + course['location'] 109 | ) 110 | e.add('summary', course['name'] + '@' + course['location']) 111 | 112 | date = termStartDay + \ 113 | timedelta(days=week_cnt * 7 + day_cnt) # 从第一 周的第一天起 114 | (beginTime, endTime) = TIME_SCHED[int(course['sectionSpan'][1] / 2 - 1)] 115 | (beginTime, endTime) = (beginTime.split(':'), endTime.split(':')) 116 | 117 | e.add( 118 | "dtstart", 119 | date.replace( 120 | hour=int(beginTime[0]), 121 | minute=int(beginTime[1]) 122 | ) 123 | ) 124 | e.add( 125 | "dtend", 126 | date.replace( 127 | hour=int(endTime[0]), 128 | minute=int(endTime[1]) 129 | ) 130 | ) 131 | cal.add_component(e) 132 | f = open(USERNAME + '_' + semesterCode + ".ics", 'wb') 133 | f.write(cal.to_ical()) 134 | f.close() 135 | print("日历文件已保存到 " + USERNAME + '_' + semesterCode + ".ics") 136 | -------------------------------------------------------------------------------- /get_course_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | from typing import List 4 | 5 | from bs4 import BeautifulSoup 6 | from libxduauth import IDSSession 7 | 8 | try: 9 | from credentials import IDS_PASSWORD, IDS_USERNAME 10 | except ImportError: 11 | IDS_USERNAME, IDS_PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'IDS_PASS')] 12 | if not IDS_USERNAME or not IDS_PASSWORD: 13 | print('请设置环境变量 IDS_USER 和 IDS_PASS') 14 | exit(1) 15 | 16 | 17 | LOGIN_URL: str = "https://xdspoc.fanya.chaoxing.com/sso/xdspoc" 18 | COURSE_DATA_URL: str = "http://fycourse.fanya.chaoxing.com/courselist/studyCourseDatashow" 19 | 20 | session = IDSSession(LOGIN_URL, IDS_USERNAME, IDS_PASSWORD) 21 | 22 | 23 | def get_course_info(semesternum: int) -> List[List[str]]: 24 | html = session.get(COURSE_DATA_URL, params={ 25 | 'semesternum': semesternum, 26 | }).text 27 | soup = BeautifulSoup(html, 'lxml') 28 | table = soup.find('table') 29 | headers = [ 30 | header.get_text(strip=True) for header in 31 | table.find('thead').find('tr').find_all('th') 32 | ] 33 | data = [headers] 34 | for row in table.find('tbody').find_all('tr'): 35 | row_data = [cell.get_text(strip=True) for cell in row.find_all('td')] 36 | data.append(row_data) 37 | 38 | return data 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = argparse.ArgumentParser() 43 | parser.add_argument("semesternum", nargs='?', type=int, default=0) 44 | args = parser.parse_args() 45 | data = get_course_info(args.semesternum) 46 | result = [', '.join(sublist) for sublist in data] 47 | result = '\n'.join(result) 48 | with open(f'course_data_{args.semesternum}.csv', 'w') as f: 49 | f.write(result) 50 | print(f"已将课程数据保存至course_data_{args.semesternum}.csv") 51 | -------------------------------------------------------------------------------- /get_electricity_balance.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.ELECTRICITY_USERNAME 32 | PASSWORD = credentials.ELECTRICITY_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('ENERGY_USER', 'ENERGY_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 ENERGY_USER 和 ENERGY_PASS') 37 | exit(1) 38 | 39 | 40 | import re 41 | from libxduauth import EnergySession 42 | 43 | ses = EnergySession(USERNAME, PASSWORD) 44 | 45 | balance_page = ses.get( 46 | 'http://10.168.55.50:8088/searchWap/webFrm/met.aspx' 47 | ).text 48 | pattern_name = re.compile('表名称:(.*?) ', re.S) 49 | name = re.findall(pattern_name, balance_page) 50 | pattern_balance = re.compile('剩余量:(.*?) ', re.S) 51 | balance = re.findall(pattern_balance, balance_page) 52 | print("电费账号:", USERNAME) 53 | for n, b in zip(name, balance): 54 | print(" 表名称:", n, "剩余量:", float(b)) 55 | -------------------------------------------------------------------------------- /get_grades.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.IDS_USERNAME 32 | PASSWORD = credentials.IDS_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('IDS_USER', 'IDS_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 IDS_USER 和 IDS_PASS') 37 | exit(1) 38 | 39 | import json 40 | import sys 41 | from libxduauth import EhallSession 42 | 43 | ses = EhallSession(USERNAME, PASSWORD) 44 | ses.use_app(4768574631264620) 45 | 46 | querySetting = [ 47 | { # 是否有效 48 | 'name': 'SFYX', 49 | 'value': '1', 50 | 'linkOpt': 'and', 51 | 'builder': 'm_value_equal' 52 | } 53 | ] 54 | if '--all-terms' not in sys.argv[1:]: 55 | res = ses.post( 56 | # 查询当前学年学期和上一个学年学期 57 | 'http://ehall.xidian.edu.cn/jwapp/sys/cjcx/modules/cjcx/cxdqxnxqhsygxnxq.do', 58 | ).json()['datas']['cxdqxnxqhsygxnxq']['rows'] 59 | querySetting.append({ # 学期 60 | 'name': 'XNXQDM', 61 | 'value': res[-1]['XNXQDM'], 62 | 'linkOpt': 'and', 63 | 'builder': 'equal' 64 | }) 65 | 66 | total = [0, 0] 67 | course_ignore = ['军事', '形势与政策', '创业基础', '新生', '写作与沟通', '学科导论', '心理', '物理实验'] 68 | types_ignore = ['公共任选课', '集中实践环节', '拓展提高', '通识教育核心课', '专业选修课'] 69 | unpassed_course = {} 70 | 71 | for i in ses.post( 72 | 'http://ehall.xidian.edu.cn/jwapp/sys/cjcx/modules/cjcx/xscjcx.do', 73 | data={ 74 | 'querySetting': json.dumps(querySetting), 75 | '*order': '+XNXQDM,KCH,KXH', 76 | # 请参考 https://github.com/xdlinux/xidian-scripts/wiki/EMAP#高级查询的格式 77 | 'pageSize': 1000, 78 | 'pageNumber': 1 79 | } 80 | ).json()['datas']['xscjcx']['rows']: 81 | 82 | flag = 0 83 | 84 | for lx in types_ignore: 85 | if i["KCLBDM_DISPLAY"].find(lx) != -1: 86 | flag = 1 87 | break 88 | 89 | for kw in course_ignore: 90 | if i["XSKCM"].find(kw) != -1: 91 | flag = 1 92 | break 93 | 94 | if flag == 1: 95 | i["XSKCM"] = '*' + i["XSKCM"] 96 | else: 97 | if i["SFJG"] == '1': # 及格 98 | if i["CXCKDM"] == '01': # 初修 99 | total[0] += i["XF"] * i["ZCJ"] 100 | total[1] += i["XF"] 101 | else: 102 | total[0] += i["XF"] * 60.0 103 | total[1] += i["XF"] 104 | 105 | if i["SFJG"] == '0' and i["KCXZDM_DISPLAY"] == "必修": 106 | unpassed_course[i["KCH"]] = i["XF"] 107 | elif i["SFJG"] == '1' and i["KCH"] in unpassed_course.keys(): 108 | del unpassed_course[i["KCH"]] 109 | 110 | roman_nums = str.maketrans({ 111 | 'Ⅰ': 'I', 'Ⅱ': 'II', 'Ⅲ': 'III', 'Ⅳ': 'IV', 'Ⅴ': 'V', 'Ⅵ': 'VI', 'Ⅶ': 'VII', 'Ⅷ': 'VIII', 112 | }) # 一些终端无法正确打印罗马数字 113 | i["XSKCM"] = i["XSKCM"].translate(roman_nums) 114 | print( 115 | f'{i["XNXQDM"]} [{i["KCH"]}]{i["XSKCM"]} ' 116 | f'{i["KCXZDM_DISPLAY"]} ' 117 | f'{i["ZCJ"] if i["ZCJ"] else "还没出成绩"} ' 118 | f'{i["KCLBDM_DISPLAY"]}' 119 | ) 120 | 121 | print(f'未获得的学分有:{sum(unpassed_course.values())}') 122 | if '--all-terms' in sys.argv[1:]: 123 | print(f'加权平均成绩:{total[0] / total[1]:.2f}') 124 | print('注:标记有*的课程以及未通过科目不计入均分') 125 | -------------------------------------------------------------------------------- /get_network_balance_of_current_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | # intro: this script only works at school network environment 19 | # and this is a zero-config script 20 | 21 | import requests 22 | import json 23 | from datetime import datetime 24 | import math 25 | 26 | def format_bytes(bytes, decimals=2): 27 | if bytes <= 0: 28 | return "0 B" 29 | suffixes = ["B", "KB", "MB", "GB", "TB"] 30 | i = int(math.log(bytes, 1000)) 31 | return f"{bytes / math.pow(1000, i):.{decimals}f} {suffixes[i]}" 32 | 33 | def get_network_info(): 34 | try: 35 | response = requests.get( 36 | 'https://w.xidian.edu.cn/cgi-bin/rad_user_info', 37 | params={ 38 | 'callback': 'jsonp', 39 | '_': str(int(datetime.now().timestamp() * 1000)), 40 | }, 41 | headers={'Accept': 'text/plain'} 42 | ) 43 | response.raise_for_status() 44 | jsonString = response.text[6:-1] 45 | 46 | return json.loads(jsonString) 47 | 48 | except Exception as e: 49 | print(f'Error fetching network info: {e}') 50 | isLoading = False 51 | return None, None, isLoading 52 | 53 | if __name__ == '__main__': 54 | res = get_network_info() 55 | print(f"Network Info: {res['products_name']}", ) 56 | print(f"Usage: {format_bytes(res['sum_bytes'])}", ) 57 | -------------------------------------------------------------------------------- /get_network_usage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth', 'BeautifulSoup4')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth', 'BeautifulSoup4' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.PAY_USERNAME 32 | PASSWORD = credentials.PAY_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('PAY_USER', 'PAY_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 PAY_USER 和 PAY_PASS') 37 | exit(1) 38 | 39 | import re 40 | from libxduauth import ZFWSession 41 | import bs4 42 | 43 | 44 | def get_info(ses): 45 | info_url = ses.BASE + '/home' 46 | ip_list = [] 47 | used = "" 48 | rest = "" 49 | charged = "" 50 | filt = re.compile(r'>(.*)<') 51 | soup = bs4.BeautifulSoup(ses.get(info_url).text, 'lxml') 52 | tr_list = soup.find_all('tr') 53 | for tr in tr_list: 54 | td_list = bs4.BeautifulSoup(str(tr), 'lxml').find_all('td') 55 | if len(td_list) == 0: 56 | continue 57 | elif len(td_list) == 4: 58 | ip = filt.search(str(td_list[0])).group(1) 59 | online_time = filt.search(str(td_list[1])).group(1) 60 | used_t = filt.search(str(td_list[2])).group(1) 61 | if used_t == '': 62 | continue 63 | ip_list.append((ip, online_time, used_t)) 64 | elif len(td_list) == 6: 65 | used = filt.search(str(td_list[1])).group(1) 66 | rest = filt.search(str(td_list[2])).group(1) 67 | charged = filt.search(str(td_list[3])).group(1) 68 | return ip_list, used, rest, charged 69 | 70 | 71 | if __name__ == '__main__': 72 | ses = ZFWSession(USERNAME, PASSWORD) 73 | ip_list, used, rest, charged = get_info(ses) 74 | for ip_info in ip_list: 75 | print(ip_info) 76 | print("此月已使用流量 %s , 剩余 %s , 充值剩余 %s" % (used, rest, charged)) 77 | -------------------------------------------------------------------------------- /get_rs_campus_recruitment.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | # python3,需要requests和BeautifulSoup4 19 | import time 20 | import requests 21 | from bs4 import BeautifulSoup 22 | 23 | from libxduauth import RSBBSSession 24 | try: 25 | import credentials 26 | USERNAME, PASSWORD = credentials.RS_USERNAME, credentials.RS_PASSWORD 27 | except ImportError: 28 | import os 29 | USERNAME = os.getenv('RS_USER') 30 | PASSWORD = os.getenv('RS_PASS') 31 | 32 | ses = RSBBSSession(USERNAME, PASSWORD) 33 | 34 | 35 | def get_hr_posts(): 36 | # 外网睿思rsbbs 37 | response = ses.get('http://rsbbs.xidian.edu.cn/forum.php', params={ 38 | 'mod': 'forumdisplay', 'fid': '554', 'filter': 'typeid', 'typeid': '43' 39 | }) 40 | html = BeautifulSoup(response.text, 'html.parser') 41 | info_all = {} 42 | hr_index = 1 43 | for link in html.find_all('li'): 44 | link = link.find('a') 45 | info_link = link.get('href') 46 | # del 就业招聘版版规 47 | if "921702" in info_link: 48 | continue 49 | info_link = "http://rsbbs.xidian.edu.cn/" + info_link 50 | info_text = link.get_text(strip=True) 51 | info_all[info_text] = info_link 52 | hr_index += 1 53 | return hr_index, info_all 54 | 55 | import sys 56 | options = sys.argv[1:] 57 | if __name__ == '__main__': 58 | template = '{k}:\t{v} \n' 59 | if '--markdown' in options: 60 | template = '[{k}]({v}) \n' 61 | first_hr, info_all = get_hr_posts() 62 | first_hr_new = first_hr 63 | desp_data = '' 64 | for k, v in info_all.items(): 65 | desp_data += template.format(k=k, v=v) 66 | 67 | if '--urlencode' in options: 68 | from urllib import parse 69 | desp_data = parse.quote(desp_data) 70 | print(desp_data) 71 | -------------------------------------------------------------------------------- /get_sport_measure_score.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | # 18 | 19 | import pkg_resources 20 | import subprocess 21 | import sys 22 | import os 23 | try: 24 | pkg_resources.require(('libxduauth')) 25 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 26 | subprocess.check_call([ 27 | sys.executable, '-m', 'pip', 'install', 'libxduauth' 28 | ]) 29 | 30 | try: 31 | import credentials 32 | USERNAME = credentials.SPORTS_USERNAME 33 | PASSWORD = credentials.SPORTS_PASSWORD 34 | except ImportError: 35 | USERNAME, PASSWORD = [os.getenv(i) for i in ('SPORTS_USER', 'SPORTS_PASS')] 36 | if not USERNAME or not PASSWORD: 37 | print('请设置环境变量 SPORTS_USER 和 SPORTS_PASS') 38 | exit(1) 39 | 40 | from libxduauth import SportsSession 41 | # For a nice output. 42 | from prettytable import PrettyTable 43 | 44 | 45 | def get_sport_measure_detail(session, measure_score_id): 46 | form = PrettyTable() 47 | form.field_names = ["项目", "结果", "单位", "分数"] 48 | response = session.post(session.BASE_URL + 'measure/getStuScoreDetail', 49 | data={ 50 | "meaScoreId": measure_score_id 51 | }).json() 52 | for each in response['data']: 53 | if 'actualScore' in each: 54 | form.add_row([each["examName"], each["actualScore"], 55 | each["examunit"], each["score"]]) 56 | else: 57 | form.add_row([each["examName"], "未录入", each["examunit"], "未录入"]) 58 | form.align = "l" 59 | print(form) 60 | 61 | 62 | def get_sport_measure_general(session): 63 | response = session.post(session.BASE_URL + 'measure/getStuTotalScore', 64 | data={ 65 | "userId": session.user_id 66 | }).json() 67 | for i in response['data']: 68 | if 'meaScoreId' in i: 69 | print("{} 年度的成绩是 {} ,等级是{}。详情如下:".format( 70 | i['year'], i['totalScore'], i['rank'])) 71 | get_sport_measure_detail(session, i["meaScoreId"]) 72 | else: 73 | print("目前四次体测成绩总分是 {}".format(i['totalScore'])) 74 | 75 | 76 | if __name__ == '__main__': 77 | ses = SportsSession(USERNAME, PASSWORD) 78 | get_sport_measure_general(ses) 79 | -------------------------------------------------------------------------------- /get_sports_punch_records.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | import pkg_resources 19 | import subprocess 20 | import sys 21 | import os 22 | try: 23 | pkg_resources.require(('libxduauth')) 24 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 25 | subprocess.check_call([ 26 | sys.executable, '-m', 'pip', 'install', 'libxduauth' 27 | ]) 28 | 29 | try: 30 | import credentials 31 | USERNAME = credentials.SPORTS_USERNAME 32 | PASSWORD = credentials.SPORTS_PASSWORD 33 | except ImportError: 34 | USERNAME, PASSWORD = [os.getenv(i) for i in ('SPORTS_USER', 'SPORTS_PASS')] 35 | if not USERNAME or not PASSWORD: 36 | print('请设置环境变量 SPORTS_USER 和 SPORTS_PASS') 37 | exit(1) 38 | 39 | 40 | from libxduauth import SportsSession 41 | 42 | def get_current_term(session): 43 | response = session.post(session.BASE_URL + 'stuTermPunchRecord/findList', 44 | data={ 45 | 'userId': session.user_id 46 | }).json() 47 | return (response['data'][0]['sysTermId'], response['data'][0]['sysTerm']) 48 | 49 | 50 | def get_valid_punch_records(session, term_id): 51 | response = session.post(session.BASE_URL + 'stuPunchRecord/findPagerOk', 52 | data={ 53 | 'userNum': USERNAME, 54 | 'sysTermId': term_id, 55 | 'pageSize': 999, 56 | 'pageIndex': 1 57 | }).json() 58 | return len(response['data']) 59 | 60 | 61 | def get_all_punch_records(session, term_id): 62 | response = session.post(session.BASE_URL + 'stuPunchRecord/findPager', 63 | data={ 64 | 'userNum': USERNAME, 65 | 'sysTermId': term_id, 66 | 'pageSize': 999, 67 | 'pageIndex': 1 68 | }).json() 69 | return len(response['data']) 70 | 71 | 72 | if __name__ == '__main__': 73 | ses = SportsSession(USERNAME, PASSWORD) 74 | (term_id, term_name) = get_current_term(ses) 75 | print('当前学期: ' + term_name) 76 | valid_count = get_valid_punch_records(ses, term_id) 77 | print('有效打卡次数: ' + str(valid_count)) 78 | all_count = get_all_punch_records(ses, term_id) 79 | print('总打卡次数: ' + str(all_count)) 80 | -------------------------------------------------------------------------------- /get_xdoj_outside.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | 18 | # contributed by speroxu 19 | import pkg_resources 20 | import subprocess 21 | import sys 22 | import os 23 | try: 24 | pkg_resources.require(('requests', 'Pillow')) 25 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 26 | subprocess.check_call([ 27 | sys.executable, '-m', 'pip', 'install', 'requests', 'Pillow' 28 | ]) 29 | 30 | try: 31 | import credentials 32 | USERNAME = credentials.XDOJ_USERNAME 33 | PASSWORD = credentials.XDOJ_PASSWORD 34 | except ImportError: 35 | USERNAME, PASSWORD = [os.getenv(i) for i in ('XDOJ_USER', 'XDOJ_PASS')] 36 | if not USERNAME or not PASSWORD: 37 | print('请设置环境变量 XDOJ_USER 和 XDOJ_PASS') 38 | exit(1) 39 | 40 | import re 41 | import requests 42 | from PIL import Image 43 | from io import BytesIO 44 | 45 | base = 'http://acm.xidian.edu.cn/' 46 | dir_name = USERNAME + '_xdacm' 47 | os.makedirs(dir_name, exist_ok=True) 48 | 49 | 50 | def login(sess): 51 | try: 52 | login_url = base + 'loginpage.php' 53 | sess.get(login_url) 54 | vcode_resp = sess.get(base + 'vcode.php') 55 | img = Image.open(BytesIO(vcode_resp.content)) 56 | img.show() 57 | vcode = input('验证码:') 58 | if vcode == '': 59 | raise PermissionError 60 | if 'Verify Code Wrong!' in sess.post(base + 'login.php', data={ 61 | 'user_id': USERNAME, 62 | 'password': PASSWORD, 63 | 'vcode': vcode, 64 | 'submit': 'Submit' 65 | }).text: 66 | raise PermissionError 67 | return sess 68 | except PermissionError: 69 | return login(sess) 70 | 71 | 72 | def get_status(sess, top=''): 73 | url = base + 'status.php?user_id=' + USERNAME + top 74 | page = sess.get(url).text 75 | 76 | status_reg = r"(.*?)<\/span>.*?submitpage.php\?id=([0-9]+)&sid=([0-9]+)" 77 | params = re.findall(status_reg, page) 78 | print(params) 79 | 80 | for param in params: 81 | get_code(sess, 'submitpage.php?id=%s&sid=%s' % (param[1:]), *param) 82 | 83 | np_reg = r'&top=([0-9]+)&prevtop=([0-9]+)>Next\ Page' 84 | np = re.search(np_reg, page) 85 | if np.group(1) != np.group(2): 86 | get_status(sess, np.expand('&top=\\1&prevtop=\\2')) 87 | 88 | 89 | def get_code(sess, url, status, problem_id, sid): 90 | # print(url, status, id, sid) 91 | code_page = sess.get(base + url).text 92 | code_re_res = re.search( 93 | r'', code_page, re.S) 94 | code_type = code_re_res.group(1).strip().lower() 95 | code_type = 'cpp' if code_type == 'c++' else code_type 96 | origin_code = re.search( 97 | r'name="source">(.*?)', code_page, re.S).group(1) 98 | 99 | os.makedirs(dir_name + '/' + problem_id, exist_ok=True) 100 | filename = dir_name + '/' + problem_id + '/' + sid + '.' + code_type 101 | print(filename) 102 | with open(filename, 'w') as fp: 103 | fp.write(origin_code) 104 | 105 | 106 | if __name__ == '__main__': 107 | oj_sess = login(requests.Session()) 108 | get_status(oj_sess) 109 | -------------------------------------------------------------------------------- /get_xidian_news.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 by the XiDian Open Source Community. 2 | # 3 | # This file is part of xidian-scripts. 4 | # 5 | # xidian-scripts is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by the 7 | # Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. 9 | # 10 | # xidian-scripts is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with xidian-scripts. If not, see . 17 | import pkg_resources 18 | import subprocess 19 | import sys 20 | try: 21 | pkg_resources.require(('requests', 'pyquery')) 22 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): 23 | subprocess.check_call([ 24 | sys.executable, '-m', 'pip', 'install', 'requests', 'pyquery' 25 | ]) 26 | 27 | import requests as rq 28 | from pyquery import PyQuery as pq 29 | 30 | headers = { 31 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'} 32 | root_url = 'https://news.xidian.edu.cn/' 33 | news_dic = [] 34 | 35 | 36 | def req(url): 37 | #获取页面函数 38 | response = rq.get(url, headers=headers) 39 | response.encoding = 'utf-8' 40 | html = response.text 41 | doc = pq(html) 42 | return doc 43 | 44 | 45 | def extract(doc): 46 | ''' 47 | 提取首页url函数 48 | doc1是banner头条新闻部分,banner下面有三栏, 49 | doc2是第一栏工作动态 50 | doc3是第二栏是全网新闻 51 | 第三栏是热点新闻,调用API方法所以另写函数 52 | 其他页面没啥意思,如果想写的话可以自己写,parse可以通用 53 | ''' 54 | 55 | urls = [] 56 | 57 | doc1 = doc('.content1_left') 58 | doc1('.content1_left_top tbody tr:first-child').remove() 59 | doc1('.content1_left_bottom_top').remove() 60 | for url in doc1('a').items(): 61 | urls.append(url.attr.href) 62 | 63 | doc2 = doc('.gzdt_bottom ul') 64 | for url in doc2('li a:last-child').items(): 65 | urls.append(url.attr.href) 66 | 67 | doc3 = doc('.mtxd_bottom') 68 | for url in doc3('a').items(): 69 | if(url.attr.href[0] == 'i'): 70 | urls.append(url.attr.href) 71 | 72 | dic4 = get_hot_news() 73 | for dic in dic4: 74 | urls.append(dic['linkurl']) 75 | 76 | return urls 77 | 78 | 79 | def parse(url): 80 | #子页面处理函数 81 | doc = req(root_url + url) 82 | doc('#wz_zw img').remove() 83 | doc('#wz_zw span').remove() 84 | 85 | tag = doc('.yaowen-a').text() 86 | title = doc('.neirong-bt').text() 87 | date = doc('#date').text()[5:21] # 发布时间:2020-12-01 08:52:41 自行调整切片 88 | source = doc('#from').text() 89 | author = doc('.editor').text() # 责任编辑:XXX 自行调整切片 90 | content = doc('#wz_zw p').text() # 如果需要换行符请手写re,默认段与段直接以空格间隔 91 | 92 | #首个图片的链接 93 | if doc('.img_vsb_content').attr.src: 94 | picurl = root_url[0:-1] + doc('.img_vsb_content').attr.src 95 | else: 96 | picurl = '' 97 | 98 | news_dic.append(dict(zip(["tag", "title", "date", "author", "content", "picurl"], 99 | [tag, title, date, author, content, picurl]))) 100 | 101 | 102 | def get_hot_news(): 103 | #因为这个热点新闻是调用API获取的,所以另写函数 104 | data = { 105 | 'owner': '1271716923', 106 | 'treeid': '1001', 107 | 'viewid': '189460', 108 | 'mode': '10', 109 | 'locale': 'zh_CN', 110 | 'pageUrl': '%2Findex.htm', 111 | 'uniqueId': 'u38', 112 | 'actionmethod': 'getnewslist' 113 | } 114 | 115 | json_raw = rq.post( 116 | 'https://news.xidian.edu.cn/system/resource/js/news/hotdynpullnews.jsp', data=data) 117 | 118 | return eval(json_raw.text) 119 | 120 | 121 | if __name__ == '__main__': 122 | doc = req(root_url) 123 | urls = extract(doc) 124 | #爱护学校服务器,测试请取urls切片 125 | for url in urls[25:30]: 126 | parse(url) 127 | print(news_dic) 128 | 129 | 130 | ''' 131 | 输出格式示例 132 | [{ 133 | 'tag': '西电要闻', 134 | 'title': '西电举办第五届“三好三有”研究生导学团队评审会', 135 | 'date': '2020-11-30 09:38', 136 | 'author': ' 责任编辑:冯毓璇', 137 | 'content': '西电新闻网讯(通讯员 霍学浩 高宇星)11月27日下午,西安电子科技大学第五届“三好三有”研究 生导学团队评审会在北校区大礼堂举行...', 138 | 'picurl': 'https://news.xidian.edu.cn/__local/F/9A/57/DD2D65A251C04AE5C33ADA469B3_E66B88F8_4CA34.jpg' 139 | }, { 140 | 'tag': '西电要闻', 141 | 'title': '师德标兵|秦枫:知行合一的“大先生”', 142 | 'date': '2020-12-01 10:26', 143 | 'author': '责任编辑:冯毓璇', 144 | 'content': ' ■学生记者 彭怡乐 宫懿伦 赵晨晋 顾启宇 自1992年任教以来,秦枫已在三尺讲台上辛勤耕耘了28年,她精进自身、知行合一、严谨治学...', 145 | 'picurl': 'https://news.xidian.edu.cn/__local/E/EC/25/D514D9A10754ADA29CCDB064439_93C52D97_7C020.jpg' 146 | }] 147 | ''' 148 | --------------------------------------------------------------------------------