# python 实现自动选课
主要利用 selenium 与 ddddocr 库实现了对南大选课系统已收藏课程的自动化选择, 频率约为每两秒一次。实际测试中在一天时间内抢到了三门通识与两次近代史。
驱动这个项目动力来自于南大选课系统的落后。在开放选课的前一分钟 mikeMao 点击了一下刷新按钮,然后当 mikeMao 看到选课界面时已经是十分钟以后了。他没有任何体育课,也没有抢到近代史(因为他上学期为了逃离黑榜退掉了近代史),并且只中了一门必中的通识课!如果只靠蹲,mikeMao 这学期的课表可能会爆炸,于是 mikeMao 吐槽能否开发一个自动选课脚本,他的室友 stonerXiao 花了两小时写了一个可以使用的抢课脚本,mikeMao 获得了源码。但是他觉得一些地方写的不是很好,于是第二天上课(两点的课,两点零一群里通知老师有事不来了),他就动手改了一遍,借鉴了 stonerXiao 的刷新思路,mikeMao 几乎重写了一份代码。并在接下来的一天内塞满了课表。
源码
from selenium import webdriver | |
from selenium.webdriver.common.by import By | |
from selenium.webdriver.support.ui import WebDriverWait | |
from selenium.webdriver.edge.options import Options | |
import time | |
import ddddocr | |
import traceback | |
# 使用须知 需要安装:selenium 与 ddddorc 库 edge 浏览器及对应版本的驱动程序 并且参照注释对下面源码进行改动 将想选的课收藏 | |
# 可能不明原因退出或报错 可能是收藏被吞了(重新收藏一下) 或者网络波动(重启程序即可) | |
username = "" # 添加用户名 | |
password = "" # 添加密码 | |
url = "https://xk.nju.edu.cn/xsxkapp/sys/xsxkapp/*default/index.do" | |
login_button = "/html/body/div[1]/article/section/div[4]/div[1]/button" | |
start = "/html/body/div[1]/article/section/div[4]/div[2]/button" | |
favorite = "/html/body/div[1]/header/div[2]/ul/li[8]/a" | |
special = "/html/body/div[1]/header/div[2]/ul/li[1]/a" # for refresh | |
select = "/html/body/div[1]/article/div[2]/div[2]/table/tbody/tr[{}]/td[8]/a[2]" | |
confirm = "/html/body/div[3]/div[2]/div[2]/div[1]" | |
confirm_ = "/html/body/div[3]/div[2]/div[2]/div" | |
course_list = [] | |
# 识别验证码 | |
def Identify_verifi_code(driver): | |
img = driver.find_element(By.XPATH, '//*[@id="vcodeImg"]') | |
img.screenshot('vcode.png') | |
ocr = ddddocr.DdddOcr() | |
with open('vcode.png', 'rb') as f: | |
img_bytes = f.read() | |
res = ocr.classification(img_bytes) | |
return res | |
def login(driver): | |
# driver.maximize_window () # 将窗口最大化 | |
verifi_code = Identify_verifi_code(driver) # 识别验证码 | |
# 找到登录框 输入账号密码 | |
driver.find_element(By.ID, 'loginName').send_keys(username) # 输入用户名 | |
driver.find_element(By.ID, 'loginPwd').send_keys(password) # 输入密码 | |
driver.find_element(By.ID, 'verifyCode').send_keys(verifi_code) # 输入验证码 | |
wait = WebDriverWait(driver, 10) # 10 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, login_button)) | |
driver.find_element(By.XPATH, login_button).click() # 点击登录 | |
try: | |
while True: | |
verifi_code = Identify_verifi_code(driver) # 识别验证码 | |
driver.find_element(By.ID, 'verifyCode').clear() | |
driver.find_element(By.ID, 'verifyCode').send_keys(verifi_code) # 输入验证码 | |
time.sleep(1) | |
wait = WebDriverWait(driver, 10) # 10 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, login_button)) | |
driver.find_element(By.XPATH, login_button).click() # 点击登录 | |
except: | |
{} | |
wait = WebDriverWait(driver, 20) # 20 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, start)) | |
driver.find_element(By.XPATH, start).click() | |
def is_Login(driver): | |
try: | |
driver.find_element(By.ID, 'loginName') | |
return False | |
except: | |
return True | |
def start_select(driver): | |
if is_Login(driver) == False: | |
return False | |
time.sleep(1) | |
wait = WebDriverWait(driver, 20) # 20 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, favorite)) | |
driver.find_element(By.XPATH, favorite).click() | |
time.sleep(1) | |
course_board = driver.find_element(By.CLASS_NAME, 'course-body') | |
tr_list = course_board.find_elements(By.CLASS_NAME, 'course-tr ') | |
print('已收藏课程:') | |
for tr in tr_list: | |
wait = WebDriverWait(tr, 5) | |
wait.until(lambda tr: tr.find_element(By.XPATH, './td[2]')) | |
course_name = tr.find_element(By.XPATH, './td[2]').text | |
print(course_name, end="\n") | |
count = 0 | |
while True: | |
count = count + 1 | |
if count % 100 == 0: | |
print('已尝试次数:' + count, end=" ") | |
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))) | |
time.sleep(1) | |
course_board = driver.find_element(By.CLASS_NAME, 'course-body') | |
tr_list = course_board.find_elements(By.CLASS_NAME, 'course-tr ') | |
for tr in tr_list: | |
wait = WebDriverWait(tr, 5) | |
wait.until(lambda tr: tr.find_element(By.XPATH, './td[8]/a[2]')) | |
course_name = tr.find_element(By.XPATH, './td[2]').text | |
select_button = tr.find_element(By.XPATH, './td[8]/a[2]') | |
select_button.click() | |
try: | |
driver.find_element(By.XPATH, confirm).click() | |
print("选课成功!") | |
print("已成功选中{}".format(course_name)) | |
course_list.append(course_name) | |
print("确认中") | |
wait = WebDriverWait(driver, 10) # 20 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, confirm_)) | |
driver.find_element(By.XPATH, confirm_).click() | |
print("确认成功!") | |
except: | |
{} | |
wait = WebDriverWait(driver, 3) # 10 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, special)) | |
driver.find_element(By.XPATH, special).click() | |
time.sleep(0.5) | |
wait = WebDriverWait(driver, 3) # 10 秒内每隔 500 毫秒扫描 1 次页面变化,当出现指定的元素后结束。 | |
wait.until(lambda driver: driver.find_element(By.XPATH, favorite)) | |
driver.find_element(By.XPATH, favorite).click() | |
def working(): | |
options = Options() | |
# 关闭沙盒启动 把下面两行注释掉 会出现浏览器界面 建议注释掉看看能不能运行 可以再取消注释 | |
#options.add_argument('--no-sandbox') | |
#options.add_argument("--headless") | |
driver = webdriver.Edge(executable_path="msedgedriver.exe", options=options) # executable_path 改成下载的 edge 驱动程序路径 | |
driver.get(url) | |
# 登录并查询 | |
try: | |
login(driver) | |
start_select(driver) | |
except: | |
traceback.print_exc() | |
finally: | |
print("已选到如下课程:") | |
print(course_list) | |
driver.quit() | |
if __name__ == '__main__': | |
working() |