├── .gitignore ├── README.md ├── evaluate.py ├── evaluation.py └── requriements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | venv/ 3 | __pycache__/ 4 | 5 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 北航教务一键评教 2 | ======= 3 | 4 | 北航有个奇怪要求,不评教不能查看成绩。但是,这么多教师,每个教师还有好多个选项,全部评价完需要10来分钟。 5 | 故写一段Python脚本,完成该功能。 6 | 7 | ## 安装依赖及运行 8 | 9 | 以windows10 PowerShell为例: 10 | 11 | ```powershell 12 | pip install -r requirements.txt 13 | python evaluate.py 14 | ``` 15 | 16 | ## 功能简介 17 | 18 | 1. 自动选择最佳评价(由于至少需要有一个选项与其他不同,故第一项选择为良,其他全部选择优秀) 19 | 20 | 2. 统一认证自动登陆(提供学号、密码,一键傻瓜式操作) 21 | 22 | 23 | ## 其他问题 24 | 25 | 由于本脚本仅在本机测试(windows 10)通过,无法保证其他环境下能正常使用。如有任何问题,欢迎issue中交流。 26 | -------------------------------------------------------------------------------- /evaluate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | from bs4 import BeautifulSoup 4 | from collections import defaultdict 5 | from urllib.parse import quote 6 | from getpass import getpass 7 | 8 | session = requests.Session() 9 | page_xnxq = '' 10 | 11 | jiaowu_url = 'http://jwxt.buaa.edu.cn:8080/ieas2.1/' 12 | # use '/login' instead of '/login/' to avoid redirects 13 | login_url = 'https://sso.buaa.edu.cn/login?service=' \ 14 | + quote(jiaowu_url, 'utf-8') + 'welcome' 15 | 16 | 17 | def get_login_token() -> str: 18 | r = session.get(login_url) 19 | assert (r.status_code == 200) 20 | soup = BeautifulSoup(r.content, 'html.parser') 21 | lt = soup.find('input', {'name': 'execution'})['value'] 22 | return lt 23 | 24 | 25 | def login(username: str, password: str) -> bool: 26 | formdata = { 27 | 'username': username, 28 | 'password': password, 29 | 'execution': get_login_token(), 30 | 'type': 'username_password', 31 | '_eventId': 'submit', 32 | 'submit': '登陆' 33 | } 34 | r2 = session.post(login_url, data=formdata, allow_redirects=True) 35 | soup = BeautifulSoup(r2.text, "html.parser") 36 | return not soup.find_all('div', class_='error_txt') 37 | 38 | 39 | def assess_item(teacher: "beautifulSoup object"): 40 | print('evaluate teacher %s...' % teacher.string) 41 | course_id = list(teacher.parents)[2]['rwh_id'] 42 | teacher_info = teacher['onclick'].split("'") 43 | teacher_id = teacher_info[1] 44 | pjcs = teacher_info[3] 45 | form_data = { 46 | 'rwh': course_id, 47 | 'zgh': teacher_id, 48 | 'pjcs': pjcs, 49 | 'kcdm': '', 50 | 'pageXnxq': page_xnxq 51 | } 52 | r = session.post(jiaowu_url + "xspj/toAddPjjs", 53 | data=form_data) 54 | r.raise_for_status() 55 | s = BeautifulSoup(r.content, "html.parser") 56 | form = s.find('form', id='queryform') 57 | entries = form.find_all('input', type='hidden') 58 | form_data2 = defaultdict(list) 59 | for entry in entries: 60 | if entry.has_attr('name'): 61 | form_data2[entry['name']].append(entry['value']) 62 | entries = form.find_all('input', id='zbdm') 63 | for i, entry in enumerate(entries): 64 | option = entry.find_next_sibling('td').input 65 | if i == 0: 66 | option = option.find_next_sibling('input') 67 | form_data2[option['name']].append(option['value']) 68 | r2 = session.post(jiaowu_url + 'xspj/saveXspj', 69 | data=form_data2) 70 | r2.raise_for_status() 71 | print('评教成功!') 72 | 73 | 74 | def auto_evaluation(): 75 | global page_xnxq 76 | r = session.get(jiaowu_url + 'xspj/Fxpj_fy', 77 | allow_redirects=False) 78 | r.raise_for_status() 79 | soup = BeautifulSoup(r.content, 'html.parser') 80 | yellow_spans = soup.find_all('span', class_='yellow') 81 | teachers = [] 82 | page_xnxq = soup.find('select', id='xnxq').find('option', selected=True)['value'] 83 | for span in yellow_spans: 84 | teachers += span.find_all('a') 85 | for teacher in teachers: 86 | assess_item(teacher) 87 | print("所有评教已完成!") 88 | 89 | 90 | def auto_judge(): 91 | username = input("请输入统一认证登录账号:") 92 | password = getpass("请输入统一认证登录密码(不会显示):") 93 | if login(username, password): 94 | auto_evaluation() 95 | else: 96 | print("账号或密码错误(请确保连入校园网)") 97 | 98 | 99 | if __name__ == '__main__': 100 | auto_judge() 101 | -------------------------------------------------------------------------------- /evaluation.py: -------------------------------------------------------------------------------- 1 | # 新版评教系统自动评教 2 | # 使用selenium 3 | from selenium import webdriver 4 | from selenium.webdriver import ActionChains 5 | from selenium.webdriver.common.by import By # 按照什么方式查找,By.ID,By.CSS_SELECTOR 6 | from selenium.webdriver.common.keys import Keys # 键盘按键操作 7 | from selenium.webdriver.support import expected_conditions as EC 8 | from selenium.webdriver.support.wait import WebDriverWait # 等待页面加载某些元素 9 | import random 10 | from time import sleep 11 | from selenium.webdriver.chrome.options import Options 12 | # 13 | chrome_options = Options() 14 | # chrome_options.add_argument('--disable-gpu') # 谷歌文档提到需要加上这个属性来规避bug 15 | # chrome_options.add_argument('--hide-scrollbars') # 隐藏滚动条, 应对一些特殊页面 16 | # chrome_options.add_argument( 17 | # 'blink-settings=imagesEnabled=false') # 不加载图片, 提升速度 18 | # # 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败 19 | # chrome_options.add_argument('--headless') 20 | chrome_options.add_experimental_option("detach", True) # 使得浏览器不自动关闭 21 | 22 | user_info = { 23 | 'username': 'xxx', 24 | 'password': 'xxx' 25 | } 26 | 27 | 28 | browser = webdriver.Chrome(options=chrome_options) 29 | wait = WebDriverWait(browser, 5) 30 | 31 | login_url = 'https://sso.buaa.edu.cn/login?service=https%3A%2F%2Fspoc.buaa.edu.cn%2Fpjxt%2Fcas' 32 | 33 | browser.get(login_url) 34 | wait.until(EC.frame_to_be_available_and_switch_to_it( 35 | browser.find_element(By.ID, 'loginIframe'))) 36 | 37 | browser.find_element(By.ID, 'unPassword').send_keys(user_info['username']) 38 | browser.find_element(By.ID, 'pwPassword').send_keys(user_info['password']) 39 | browser.find_element(By.XPATH, 40 | '//*[@id="content-con"]/div[1]/div[7]/input').click() 41 | 42 | evaluation_url = 'https://spoc.buaa.edu.cn/pjxt/authentication/main' 43 | 44 | browser.get(evaluation_url) 45 | wait.until(EC.frame_to_be_available_and_switch_to_it( 46 | browser.find_element(By.ID, 'mains_index_iframe') 47 | )) 48 | 49 | 50 | browser.find_element(By.XPATH, 51 | '//*[@id="app"]/div[3]/div/div[2]/div[2]/div/ul/li/h3/a').click() 52 | browser.implicitly_wait(5) 53 | browser.switch_to.default_content() 54 | browser.find_element(By.ID, 'mains_2078_01_link').click() 55 | 56 | wait.until(EC.frame_to_be_available_and_switch_to_it( 57 | browser.find_element(By.ID, 'mains_2078_01_iframe') 58 | )) 59 | 60 | browser.find_element(By.XPATH, 61 | '//*[@id="app"]/div/div[2]/div[1]/div/div/div[1]/div[2]/table/tbody/tr/td[9]/div/div/button').click() 62 | browser.implicitly_wait(5) 63 | 64 | for outer_item in range(1, 5): 65 | try: 66 | element = browser.find_element( 67 | By.XPATH, f'//*[@id="app"]/div/div[2]/div/div/div/div[1]/div[2]/table/tbody/tr[{outer_item}]/td[6]/div/div/button[1]') 68 | if element.text == '去评价': 69 | element.click() 70 | elements = browser.find_elements( 71 | By.CSS_SELECTOR, '.ivu-btn-primary') 72 | while len(elements) > 0: 73 | item = elements.pop(0) 74 | item.click() 75 | browser.implicitly_wait(5) 76 | arr = [1, 1, 2, 2, 3, 3] 77 | for i in range(1, 7): 78 | index = random.randint(0, len(arr)-1) 79 | choice = arr.pop(index) 80 | browser.find_element( 81 | By.XPATH, f'//*[@id="app"]/div/div/div[2]/div/div[1]/div/div[2]/div/div[{i}]/div/div/div[3]/div[2]/div/label[{choice}]/span[1]/input').click() 82 | browser.find_element( 83 | By.XPATH, '//*[@id="app"]/div/div/div[3]/button[2]').click() 84 | wait.until(EC.presence_of_element_located(( 85 | By.CLASS_NAME, 'ivu-modal-content'))) 86 | browser.find_element( 87 | By.CSS_SELECTOR, 'body > div:nth-child(6) > div.ivu-modal-wrap > div > div > div > div > div.ivu-modal-confirm-footer > button.ivu-btn.ivu-btn-primary').click() 88 | wait.until(EC.presence_of_element_located(( 89 | By.XPATH, '//*[@id="app"]/div/div[2]/div[2]/button'))) 90 | sleep(1) 91 | elements = browser.find_elements( 92 | By.CSS_SELECTOR, '.ivu-btn-primary') 93 | browser.find_element( 94 | By.XPATH, '//*[@id="app"]/div/div[2]/div[2]/button').click() 95 | except Exception as e: 96 | print(e) 97 | finally: 98 | print(f'课程类型{outer_item}评教完成') 99 | 100 | 101 | print('评教完成') 102 | -------------------------------------------------------------------------------- /requriements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | distlib==0.3.4 3 | filelock==3.4.2 4 | pipenv==2022.1.8 5 | platformdirs==2.5.0 6 | six==1.16.0 7 | virtualenv==20.13.1 8 | virtualenv-clone==0.5.7 9 | requests==2.27.1 10 | beautifulsoup4==4.10.0 --------------------------------------------------------------------------------