如果不了解背景的,建议先去看看v1.0这个版本的文章。
声明:本文仅供学习交流,不针对学校及个人,无意破坏竞争公平性。
题外话:
接下来为什么直接就是v3.0呢,是因为v2.0(在v1.0的基础上增加了GUI)早就写出来了,只是没发文章,当时觉得连带着日志一块搞出来发文章得了,结果一下子多肝了四天时间。。
遇到太多问题了,太多bug了,只能一点点想办法,一点点解决。
介绍一下版本更迭:
v1.0:(实现自动维持token时效、提交预约请求和发送短信提醒)
v2.0:(新增GUI,方便操作)
v3.0:(新增日志功能,实时输出请求结果)
以下内容对照v1.0的文章阅读,体验更佳
先上效果图:
(为了展示方便,我将定时时间改得短了点,效果是相同的。)
那么,,,,
短信:
当然,这会健身房约不到,如果设置成正确的时间点约的话,就可以成功啦。
文章目录
1. 修改详情
1.1 请求函数
v1.0发送网络请求的两个函数做出一点点修改:
具体功能不变,给两个函数加了参数(方便前后端交互),分别对应新增了函数返回值(为了将其输出在GUI界面上)。
def keep_token(token):
# url部分遮挡了,自己改喔
url = "https://xxx.xxx.xxx.xxx/gym/?state=1#/pages/index"
# 字典格式,推荐使用,它会自动帮你按照k-v拼接url
myParams = {"token": token}
# 发送请求
res = requests.get(url=url, params=myParams)
# 如果响应码是200,也就是访问成功
if res.status_code == 200:
str1 = '成功:维持token时效... ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
#访问失败
else:
str1 = '失败:维持token时效... ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
send_message.send('token刷新异常,可能导致预约失败,请尽快查看。')
return str1
def oppoint(token,ptId):
# url部分遮挡了,自己改喔
url = "http://xxx.xxxx.xxx.xxx/ccms-gym/gym/queue/appointMulti"
#通过信息收集可知,dateId是日期id,1表示今天,2表示明天;ptId是时间段id,每个时间段对应一个id,打开网页F12就能看见。
myParams = {"token": token, "dateId": 2, "ptId": ptId,
"areaId": 3}
#发送请求
res = requests.get(url=url, params=myParams)
#请求成功
if res.status_code == 200:
chinese = re.findall('[\u4e00-\u9fa5]', res.text) # 汉字的范围为"\u4e00-\u9fa5"
tips = ''
for i in range(len(chinese)):
tips = tips + chinese[i]
str2 = tips + ' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
send_message.send(tips)
else:
str2 = '预约失败... ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
send_message.send('请求失败,可能导致预约失败,请尽快查看。')
return str2
1.2 多线程并行执行
具体代码在后面的my_window类里面有,主要是摒弃了schedule的定时,转而以sleep为基础自己写了个定时执行的代码,加了一个线程(实时输出日志),全部封装在了my_window类里面了。
1.3 发送短信
主体没变,只是把print()去掉了,因为我要完全实现后台运行+GUI上显示。
2. 本例中的GUI各组件详解
不得不说,单纯实现GUI太简单了,把它和后端代码联系起来运行也不难,这个tkinter库我完全没玩过,花了一个下午边看边写界面就整好了,最要命的还得是这个日志实时输出,写出来之后觉得我当时就是个傻x,但是当时钻了牛角尖是真的一点办法都没有,记录此文章也算给我长长记性。
以下代码注释非常详细,对代码本身的解释少一点,参考注释应该可以看懂
2.1 创建输入框
# 创建token输入框
def get_token(self):
# 创建l1标签
l1 = tkinter.Label(win, text='请输入token:', justify=tkinter.RIGHT, width=50)
l1.place(x=20, y=40, # 设置x,y坐标
width=100, height=30 # 设置长宽
)
# 定义输入框
global e1 #全局变量
e1 = tkinter.Entry(win)
e1.place(x=120, y=40, width=150, height=30) #place方法确定位置
创建出来就长这个样子,框里能输入内容,保存到e1,之后要获取e1的值,直接e1.get()就可以。
2.2 创建下拉选项框
#创建选择时段的下拉选项框
def get_time(self):
# 创建lable2标签
l2 = tkinter.Label(win, text='选择预约时段:', justify=tkinter.RIGHT, width=50)
l2.place(x=20, y=100, # 设置x,y坐标
width=100, height=30 # 设置长宽
)
#创建下拉框
value = tkinter.StringVar()
values = ['18:30-19:30', "19:30-20:30", "20:30-21:30"]
global combobox # 全局变量
combobox = ttk.Combobox(
master=win, # 父容器
height=10, # 高度,下拉显示的条目数量
width=20, # 宽度
state='readonly', # 设置状态 normal(可选可输入)、readonly(只可选)、 disabled
cursor='arrow', # 鼠标移动时样式 arrow, circle, cross, plus...
font=('', 10), # 字体
textvariable=value, # 通过StringVar设置可改变的值
values=values, # 设置下拉框的选项
)
combobox.place(x=120, y=100, width=150, height=30) # place方法确定位置
创建出来就长这个样子,可以选择给定的时段。
2.3 创建复选框
# 创建选择星期的复选框
def get_week(self):
# 创建lable3标签
l3 = tkinter.Label(win, text='选择预约星期:', justify=tkinter.RIGHT, width=50)
l3.place(x=20, y=160, # 设置x,y坐标
width=100, height=30 # 设置长宽
)
global checkVar1,checkVar2,checkVar3,checkVar4,checkVar5,checkVar6,checkVar7 #全局变量
# 创建复选框组件
# StringVar()是Tkinter下的对象,它支持将变量的值实时显示在屏幕上
checkVar1 = StringVar(value="0") # 默认复选框的值为“0”,显示出来即为不打√,如果为“1”,则默认打√
checkbutton1 = tkinter.Checkbutton(win, text='星期一', variable=checkVar1) # 创建复选框
checkbutton1.place(x=120, y=160, width=60, height=30) #place()方法定位
checkVar2 = StringVar(value="0")
checkbutton2 = tkinter.Checkbutton(win, text='星期二', variable=checkVar2)
checkbutton2.place(x=200, y=160, width=60, height=30)
checkVar3 = StringVar(value="0")
checkbutton3 = tkinter.Checkbutton(win, text='星期三', variable=checkVar3)
checkbutton3.place(x=120, y=190, width=60, height=30)
checkVar4 = StringVar(value="0")
checkbutton4 = tkinter.Checkbutton(win, text='星期四', variable=checkVar4)
checkbutton4.place(x=200, y=190, width=60, height=30)
checkVar5 = StringVar(value="0")
checkbutton5 = tkinter.Checkbutton(win, text='星期五', variable=checkVar5)
checkbutton5.place(x=120, y=220, width=60, height=30)
checkVar6 = StringVar(value="0")
checkbutton6 = tkinter.Checkbutton(win, text='星期六', variable=checkVar6)
checkbutton6.place(x=200, y=220, width=60, height=30)
checkVar7 = StringVar(value="0")
checkbutton7 = tkinter.Checkbutton(win, text='星期天', variable=checkVar7)
checkbutton7.place(x=120, y=250, width=60, height=30)
看着代码好多,那是因为一周有7天,复制粘贴改一下place的参数就ok了,这个还是很简单的。创建出来的效果如下:
2.4 用来输出日志的文本框
# 创建日志文本框
def creat_text(self):
# 创建lable1标签
l4 = tkinter.Label(win, text='日志:', justify=tkinter.RIGHT, width=50)
l4.place(x=280, y=15, # 设置x,y坐标
width=80, height=30 # 设置长宽
)
# 创建文本框
self.log = tkinter.Text(win)
self.log.place(x=300, y=40, width=330, height=280) # place()方法定位
长这样:
2.5 “提交”按钮
# 创建提交按钮
def creat_btn(self):
self.btn = Button(win, text='提交', command=self.start_thread)
self.btn.place(x=230, y=300, width=50, height=20) # 按钮布局
按钮实现很简单,需要注意的是command参数将按钮与start_thread方法绑定,点击按钮即可执行该方法。
3. 整合各组件及数据
3.1 获取各组件得到的数据
# get通过各组件得到的数据
def getVal(self):
messagebox.showinfo('', '提交成功') # 弹框显示提交成功提示
# 根据e1文本框输入获取token
self.token = e1.get()
# 根据时段下拉框的值确定将来请求时ptId的值,具体时段与ptId的对应关系,网站+F12,查看网页源代码可以看到
if combobox.get() == '18:30-19:30':
self.time = '18'
elif combobox.get() == '19:30-20:30':
self.time = '19'
else:
self.time = '20'
# 根据复选框输入获取weekday
self.week = [checkVar1.get(), checkVar2.get(), checkVar3.get(), checkVar4.get(), checkVar5.get(),
checkVar6.get(), checkVar7.get()]
单独写一个函数,用来整合数据,将各组件传入的数据处理后分别存储在token、time、week变量中。(v2.0 是搞了一个列表存储所有数据,方便传递参数给后台函数,但v3.0将线程执行函数和getVal放在了同一个类里,那就没必要再搞列表了)
3.2 整合各组件
# 整合一下,运行定义好的每个组件,这样的话在__init__中就只需要写main_window一个函数即可。方便一点。
def main_window(self):
self.get_token()
self.get_time()
self.get_week()
self.creat_text()
self.creat_btn()
4. 多线程
4.1 定时运行keep_token
# 运行keep_token方法,并在日志文本框中插入keep_token的运行结果
# 抛弃之前用的schedule直接定时,转而用sleep定时
def do_keep_token(self,token):
while True: # 死循环,让程序一直运行
self.log.insert(1.0,oppoint.keep_token(token)) # 实时显示:向log中插入keep_token的返回值,也就是相关日志内容。
self.log.insert(1.0,'\n') # 插入换行
sleep(10*60) #10*60秒就是10分钟,每隔十分钟执行一次keep_token,以此来维持token的有效性
直接通过sleep实现keep_token的定时执行,并实时将其返回值插入到日志文本框中。
4.2 定时运行oppoint
# 运行oppoint方法,并在日志文本框中插入
# 抛弃之前用的schedule定时,转而用sleep+判断定时
# 虽然schedule很方便,但是对应在后面的包括定时任务以及非定时任务的多线程里就很难操作,无法适配需求。
def do_oppoint(self,token,ptid,week,time):
list = []
# 获取week列表中的有效值,得到的list列表内容为用户需要预订的星期,格式为——例子:[2,3,5],需要预订星期二、星期三和星期五。
for i in range(len(week)):
if week[i] == '1':
list.append(i + 1)
while True: # 死循环让程序一直运行
'''自己写的一个定时的小算法,主要利用获取当前时间与预订时间字符串比较来实现定时'''
for i in range(len(list)):
if list[i] == datetime.now().isoweekday(): # 判断当天星期是否在需要预约的星期列表中
# 获取当前日期时间
d = datetime.now()
# 类型转换成str,然后进行字符串分割、拼接的操作,得到当前时间“hour:minute”
s = str(d)
l1 = s.split(' ')
l2 = l1[1].split(':')
t = l2[0] + ':' + l2[1]
# 判断时间是否符合,执行目标函数
if t == time:
self.log.insert(1.0, oppoint.oppoint(token, ptid))
self.log.insert(1.0, '\n')
sleep(60 * 60 * 24 - 20)
else:
pass
注释中均标明详情。其中,按指定星期的指定时间执行没有采用schedule,而是自己写了一个小算法,大概描述一下:
- 判断当天星期是否在指定的星期列表中
- 如果在,获取当前时间点
- 将当前时间点转换为str形式,对字符串进行分割和拼接,得到当前时间,格式为:“时:分”
- 将得到的当前时间与传入的指定时间对比,如果相同,则执行目标函数
4.3 实现多线程并行
def start_thread(self):
t = '10:17' # 发送预约请求的时间
self.get_val() # 运行getVal()函数,获取用户在GUI上的输入
# 定义两个线程,注意:args参数以元组存储,括号及后面的逗号不可省略,否则程序报错
a1 = threading.Thread(target = self.do_keep_token,args = (self.token,))
a2 = threading.Thread(target = self.do_oppoint, args = (self.token,self.time,self.week,t,))
# 执行定义的线程
a1.start()
a2.start()
定义线程,然后执行,没什么难点在里面。
4.4 调用类体时初始化
# 调用时初始化
def __init__(self):
global win # 全局变量
# 创建主窗口
win = tkinter.Tk()
win.title("体育馆自动化预约工具")
win.geometry("650x350+650+20") # 长x宽+x*y
win.resizable(0, 0) # 窗口大小固定
self.main_window() #在窗口中创建元素的函数
win.mainloop()
定义窗口,然后执行相应函数。(这点放在多线程这个主题下不妥,但是一个小小的初始化函数,没必要多写一个主题了,将就着看吧,强迫症勿怪。)
5. 过程中的问题备忘
5.1 没定义class
刚开始直接上代码,没定义class,对修改代码带来很多不便,然后痛定思痛,把这些版块都给整理了一下,看似多花了时间,实则不然。对于一个完整的项目而言,class至关重要,无论是给修改代码带来的便捷,还是给读者展现的层次感,都是非常实用的。
5.2 没有搞git
后面一定搞一个git,我就麻了,之前程序是好的,写了半天代码结果bug多的修不好了,想回又回不去,只能一遍遍ctrl+z,结果到了撤销步数限制了,又得重新改,愣是多花了老鼻子时间从原来的代码该回到原来的代码。
5.3 多线程定时
很痛苦,这个问题鏖战了我三天时间,可以说,大把时间都是在写bug,然后删掉,重来。(我写的bug代码真少hhhh。)
主要还是思路。
- 刚开始掉进了schedule多线程定时任务的坑里,跳不出来,浪费了大把时间。
- 后面决定不使用schedule,改用Aspscheduler框架(被其他博主迷惑了),结果写完才知道,这玩意就是一个框架,跟schedule一模一样,只是框架性比较强,更容易维护、可读性比较强。
- 之后下定决心自己写算法设置定时任务,不得不说,是真的香。虽然可读性不强,但其实和schedule没啥区别,我要是把它封装成库,它就是另一个schedule。
- 自己写的定时,对自己的程序运行起来简直不要太爽,完美搭配。
6. 结束语
如果需要源代码,评论区留言,我发下载地址。
对于这个自动预约工具,目前先更新到这里,最近要忙着刷题了,后面有需要了再把自动预约乒乓球馆、羽毛球馆、图书馆的功能加上去,这个就简单了,很快就可以,如果有什么关于这个的预约需求可以留言告诉我,你们就是我的产品经理,给我提需求,我给你们肝代码。
当然bolg还会持续更新。球球点赞+关注,谢谢铁子们。