在冬天进行羽毛球运动是一个很好的选择,它能帮助你保持身体活力,增强心肺功能,并促进血液循环。但是室友和师弟师妹反应,学校的羽毛球场地有限,手速慢的根本预约不到场地。
中午12:00准时开放预约,1秒钟不到,就只剩下08:00-09:00和12:00-13:00的不好的时间段,如果想要预约到晚上19:00-21:00的黄金时间段就需要用脚本了。
一、总代码
import sys
import json
from PySide6.QtWidgets import (QApplication, QWidget, QFormLayout, QLineEdit,
QComboBox, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QLabel)
from PySide6.QtCore import QThread, Signal, QMutex
import requests
from datetime import datetime, timedelta
import queue
import threading
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Value
import aiohttp
import asyncio
import nest_asyncio
# 允许在Jupyter等环境中运行异步代码
nest_asyncio.apply()
class LogWorker(QThread):
log_signal = Signal(str)
def __init__(self):
super().__init__()
self.log_queue = queue.Queue()
self._stop = False
self.mutex = QMutex()
def run(self):
while not self._stop:
try:
message = self.log_queue.get(timeout=0.1)
self.log_signal.emit(message)
except queue.Empty:
continue
def add_log(self, message):
self.mutex.lock()
self.log_queue.put(message)
self.mutex.unlock()
def stop(self):
self._stop = True
class AsyncBookingThread(threading.Thread):
def __init__(self, config, log_worker, request_count, stop_event):
super().__init__()
self.config = config
self.log_worker = log_worker
self.request_count = request_count
self.stop_event = stop_event
self.success = False
self.loop = None
async def make_request(self, session):
url = f"https://ndyy.ncu.edu.cn/api/badminton/saveReservationInformation?\
role=ROLE_STUDENT&\
date={self.config['date']}&\
startTime={self.config['time']}&\
areaName=%E7%BE%BD%E6%AF%9B%E7%90%83{self.config['court']}%E5%8F%B7%E5%9C%BA%E5%9C%B0&\
areaNickname=hall{self.config['court']}"
headers = {
"token": self.config['token'].strip(),
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
try:
async with session.get(url, headers=headers, timeout=1) as response:
data = await response.json()
with self.request_count.get_lock():
self.request_count.value += 1
count = self.request_count.value
if data.get('msg', '') == '':
self.log_worker.add_log(f"✅ 预约成功!(共尝试{count}次)")
self.success = True
self.stop_event.set()
return True
if count % 500 == 0: # 改为每500次更新一次日志
error_msg = data.get('msg', '未知错误')
self.log_worker.add_log(f"⏳ 已尝试{count}次: {error_msg}")
return False
except Exception as e:
return False
async def run_async(self):
async with aiohttp.ClientSession() as session:
while not self.stop_event.is_set():
success = await self.make_request(session)
if success:
break
def run(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
self.loop.run_until_complete(self.run_async())
self.loop.close()
class BadmintonWorker(QThread):
def __init__(self, config, log_worker):
super().__init__()
self.config = config
self.log_worker = log_worker
self._stop = False
self.request_count = Value('i', 0)
self.stop_event = threading.Event()
self.threads = []
def run(self):
try:
# 创建多个线程并发请求
num_threads = 10 # 可以根据需要调整线程数量
for _ in range(num_threads):
thread = AsyncBookingThread(self.config, self.log_worker, self.request_count, self.stop_event)
thread.start()
self.threads.append(thread)
# 等待所有线程完成
for thread in self.threads:
thread.join()
except Exception as e:
if not self._stop:
self.log_worker.add_log(f"❌ 发生错误: {str(e)} (共尝试{self.request_count.value}次)")
finally:
self._stop = False
def stop(self):
self._stop = True
self.stop_event.set()
for thread in self.threads:
thread.join()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.worker = None
self.log_worker = LogWorker()
self.log_worker.log_signal.connect(self.update_log)
self.log_worker.start()
def initUI(self):
self.setWindowTitle("NCU场馆预约")
self.setFixedSize(400, 400)
layout = QFormLayout()
# 日期选择
date_layout = QHBoxLayout()
# 年份选择(2025-2030)
self.year = QComboBox()
for year in range(2025, 2030):
self.year.addItem(str(year))
self.year.setCurrentText(str(datetime.now().year)) # 默认选择当前年份
# 月份选择
self.month = QComboBox()
self.month.addItems([f"{i:02d}" for i in range(1, 13)])
self.month.setCurrentText(datetime.now().strftime("%m"))
# 日期选择
self.day = QComboBox()
self.day.addItems([f"{i:02d}" for i in range(1, 32)]) # 固定1-31日
self.day.setCurrentText(datetime.now().strftime("%d")) # 默认选择当前日期
# 连接月份变化信号
self.month.currentTextChanged.connect(self.update_days)
date_layout.addWidget(self.year)
date_layout.addWidget(QLabel("年"))
date_layout.addWidget(self.month)
date_layout.addWidget(QLabel("月"))
date_layout.addWidget(self.day)
date_layout.addWidget(QLabel("日"))
# 其他输入字段
self.time = QComboBox()
self.time.addItems(["08:00-09:00", "09:00-10:00", "10:00-11:00", "11:00-12:00","12:00-13:00", "13:00-14:00", "14:00-15:00", "15:00-16:00", "16:00-17:00", "17:00-18:00", "18:00-19:00", "19:00-20:00", "20:00-21:00", "21:00-22:00"])
self.court = QComboBox()
self.court.addItems([str(i) for i in range(1, 13)])
self.token = QLineEdit()
self.token.setStyleSheet("QLineEdit { border: none; }") # 去掉下划线
# 日志显示
self.log = QTextEdit()
self.log.setReadOnly(True)
self.log.setStyleSheet("QTextEdit { border: none; }") # 去掉下划线
# 提交按钮
self.btn = QPushButton("开始预约")
self.btn.clicked.connect(self.start_booking)
# 停止按钮
self.stop_btn = QPushButton("停止预约")
self.stop_btn.clicked.connect(self.stop_booking)
self.stop_btn.setEnabled(False)
# 添加组件
layout.addRow("预约日期", date_layout)
layout.addRow("时间段", self.time)
layout.addRow("场地号", self.court)
layout.addRow("Token", self.token)
layout.addRow(self.log)
layout.addRow(self.btn)
layout.addRow(self.stop_btn)
self.setLayout(layout)
def update_days(self):
"""更新日期选项"""
self.day.clear()
year = int(self.year.currentText())
month = int(self.month.currentText())
# 获取当月天数
if month == 12:
next_month = 1
next_year = year + 1
else:
next_month = month + 1
next_year = year
first_day = datetime(year, month, 1)
last_day = datetime(next_year, next_month, 1) - timedelta(days=1)
days_in_month = last_day.day
# 添加日期选项
self.day.addItems([f"{i:02d}" for i in range(1, days_in_month + 1)])
# 设置当前日期
current_day = datetime.now().day
if month == datetime.now().month and year == datetime.now().year:
self.day.setCurrentText(f"{current_day:02d}")
def start_booking(self):
if self.worker and self.worker.isRunning():
QMessageBox.warning(self, "警告", "已有预约在进行中!")
return
config = {
"date": f"{self.year.currentText()}-{self.month.currentText()}-{self.day.currentText()}",
"time": self.time.currentText(),
"court": self.court.currentText(),
"token": self.token.text()
}
if not all(config.values()):
QMessageBox.critical(self, "错误", "所有字段必须填写!")
return
self.worker = BadmintonWorker(config, self.log_worker)
self.worker.finished.connect(self.on_worker_finished) # 添加完成信号连接
self.worker.start()
self.btn.setEnabled(False)
self.stop_btn.setEnabled(True)
def stop_booking(self):
if self.worker:
self.worker.stop()
self.worker.wait()
self.worker = None
self.log_worker.add_log("🛑 已停止预约") # 使用日志线程添加停止消息
self.btn.setEnabled(True)
self.stop_btn.setEnabled(False)
def update_log(self, message):
self.log.append(message)
def on_worker_finished(self):
self.worker = None
self.btn.setEnabled(True)
self.stop_btn.setEnabled(False)
def closeEvent(self, event):
if self.worker:
self.worker.stop()
self.worker.wait()
self.log_worker.stop()
self.log_worker.wait()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
二、APP 使用
通过百度网盘分享的文件:NCU场馆预约助手.exe
链接: https://pan.baidu.com/s/183uUrd7sLAy6YKJfEmupgg?pwd=en67 提取码: en67
由于里面包含Pysid6(这个库比较大)和requests库, 所以APP有48M的大小。
import sys
import json
from PySide6.QtWidgets import (QApplication, QWidget, QFormLayout, QLineEdit,
QComboBox, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QLabel)
from PySide6.QtCore import QThread, Signal, QMutex
import requests
from datetime import datetime, timedelta
import queue
import threading
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Value
import aiohttp
import asyncio
import nest_asyncio
双击,弹出这个界面:
填写信息就可以了:注意token需要自己获取 ,token差不多15分钟更新一次。
“token” 更新 ----F12键打开开发者界面
最后2分钟内去网站页面交钱就行。