声明:本文仅供学习交流,不针对学校及个人,无意破坏竞争公平性。
最近刚看了看python文档,不出半小时必打盹,就想着用它来制作点小工具玩玩。
正好学校健身房限额预约,同学们可都真自律啊!!!定着早上六点多的闹钟抢,作为一个懒惰的健身选手,我必然是要出奇制胜。
在这个过程中,解决各式各样的问题可比单纯磕文档有效率得多。
话不多说,拿刀来:
网站信息收集与分析
该网站比较简易,几乎全是get请求,分析起来也很容易。
请求机制
完整网址就不放了,大概说一下请求机制,入口在企业微信工作台,访问时会通过微信进行登录:
因为微信内置浏览器不方便抓包,通过查看第二次的url和查询资料可以推断,
首次请求时会携带微信验证信息,响应会给你一个code,这个code是临时的一个身份验证方式,时效非常短,访问这个url,进去后随便点击一下,你的code的作用就到头了,随之而来的是把把你的身份信息绑定到另一个参数token(一个令牌,用来证明身份)上。
之后,这个token在有效期内就能唯一证明你的身份,非常重要!!!
身份验证机制
token作为令牌,肯定具有时效性,否则一旦被劫持,别人就能冒充你访问网站,这是非常严重的。因此,我第一时间试验了token的可维持时间,发现差不多是10分钟(隔1分钟用该token访问一下,再隔2分钟用该token访问一下…发现过了10分钟,就会提示身份验证失效)。
因此,为了每次访问都能持有有效令牌,要么,每次访问前获取新token,要么,一直保持一个token不过期。
对于获取新的token,由于微信自身安全机制很强,不易获取登录状态,要想获取也可以,后面改进方案里面会提到。
因此我选择保持一个token不过期。
插一嘴:Oauth2.0 引入了 refresh token 机制,可以用来刷新access token,当token过期后,会调用刷新token接口。一般来说,refesh token的有效期比token要长(比如token:1小时,refresh token可以维持1天),当token过期如果access token 与refresh token都过期,才进行重新登录和授权。
但是,通过验证,我发现,该网站使用一旦10分钟内未用有效token访问,就会显示用户信息获取失败,并未触发refresh token机制,证明refresh这条路走不通。
那么,我们就只能不断访问,保持token的时效性。
正片开始——上代码
准备工作——实时发送短信
这部分代码可以自行去"短信宝"这个网站找。开个账号,找到开发者,代码人家都给你了,根据需求改一下就行,很方便。
其实本来打算做成邮箱提醒的,但是想了想邮箱提醒没有短信直观方便,就选择了短信。
# coding=utf-8
import urllib
import urllib.request
import hashlib
#加密
def md5(str):
m = hashlib.md5()
m.update(str.encode("utf8"))
return m.hexdigest()
def send(tips):
statusStr = {
'0': '短信发送成功',
'-1': '参数不全',
'-2': '服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间',
'30': '密码错误',
'40': '账号不存在',
'41': '余额不足',
'42': '账户已过期',
'43': 'IP地址限制',
'50': '内容含有敏感词'
}
smsapi = "http://api.smsbao.com/"
# 短信平台账号(做了遮挡)
user = 'xxxxx'
# 短信平台密码(做了遮挡)
password = md5('xxxxxxx')
# 要发送的短信内容
content = '健身房预约结果为:'+tips + '。'
# 要发送短信的手机号码(做了遮挡)
phone = 'xxxxxxxx'
data = urllib.parse.urlencode({'u': user, 'p': password, 'm': phone, 'c': content})
send_url = smsapi + 'sms?' + data
response = urllib.request.urlopen(send_url)
the_page = response.read().decode('utf-8')
print(statusStr[the_page])
主力军——发送网络请求
下面展示一些 带token发送网络请求,保持token不过期
。
import requests
import re
import send_message
import time
def keep_token():
# url部分遮挡了,自己改喔
url = "https://xxx.xxx.xxx.xxx/gym/?state=1#/pages/index"
# 字典格式,推荐使用,它会自动帮你按照k-v拼接url
myParams = {"token": "202xxxxx1909123888r6axxxxoqd"}
# 发送请求
res = requests.get(url=url, params=myParams)
# 如果响应码是200,也就是访问成功
if res.status_code == 200:
print('Success to keep token... '+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) # 利用time模块获取当前时间,方便后面搞个日志功能啥的
# 访问不成功
else:
print('Failure to keep token... '+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
send_message.send('token刷新异常,可能导致预约失败,请尽快查看。') # 调用函数send,发送短信提醒。
下面展示一些 发送请求,预约指定时段的健身房
def appoint():
# url部分遮挡了,自己改喔
url = "http://xxx.xxxx.xxx.xxx/ccms-gym/gym/queue/appointMulti"
#通过信息收集可知,dateId是日期id,1表示今天,2表示明天;ptId是时间段id,每个时间段对应一个id,打开网页F12就能看见。
myParams = {"token": "202208xxxxxxx888r6xxxx2oqd", "dateId": 2, "ptId": 20,
"areaId": 3}
#发送请求
res = requests.get(url=url, params=myParams)
#请求成功
if res.status_code == 200:
#正则表达式获取响应的text里面的汉字,汉字范围为:[\u4e00-\u9fa5]
chinese = re.findall('[\u4e00-\u9fa5]', res.text)
#因为获取到的汉字是存在列表里面的,所以我把它们连成字符串
tips = ''
for i in range(len(chinese)):
tips = tips + chinese[i]
#打印汉字内容+当前时间
print (tips+' '+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
#发送短信
send_message.send(tips)
#请求失败
else:
print('Failure to apply... '+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
send_message.send('请求失败,可能导致预约失败,请尽快查看。')
最简单但最巧妙——多线程并行执行
我是碰到问题了,一步步找解决方案,最后才搞出这个来,很简单、很实用、就是资料查的有点崩溃。。。
问题:保持token有效,就得不断访问,怎么实现?
答:循环+time.sleep()
又来问题:预约健身房要到开放系统的那个时间点才能发送请求,怎么实现?
答:定时任务,查了一下,schedule调度器太合适了。
那么前面的也可以用调度器来实现不断访问保持token有效啊。
我们选定,schedule!
接着,调试代码发现,schedule实现定时任务未到约定时间自身也会阻塞,阻塞就意味着执行一个任务,另一个就没法执行。保持了token有效就不能顺利提交预约请求,要提交预约请求就不能不断访问保持token有效,显然是矛盾的。
对此,多番查询,发现了python的多线程和多进程机制。
二者的区别浅显地讲一下:多线程适用于多次执行,但计算量不大;多进程适用于计算量大,但访问次数少。(多线程总是在一个进程里进行的,对资源消耗比较友好,而多进程会占用更多的cpu资源)
显然,此处更适合使用多线程。因为设置了定时,两个任务完全可以交叉进行,完美解决单线程堵塞问题。
import schedule
import threading
import oppoint
#多线程
# 讲函数作为参数传递进去,作为一个线程函数
def run_threaded(func):
job_thread = threading.Thread(target=func)
job_thread.start()
# 为了维持token有效,调度器设置每9分钟执行一次,do后面的run_threaded是要执行的函数,oppoint.keep_token是要执行函数的参数
schedule.every(9).minutes.do(run_threaded, oppoint.keep_token)
# 发送预约请求,调度器设置每天6:50执行
schedule.every().day.at("06:50").do(run_threaded,oppoint.appoint)
#死循环,让程序一直运行
while True:
#执行所有线程
schedule.run_pending()
至此,我的自动预约健身房v1.0就完成了。
运行截图
为了测试,我将keep_token的时间改成了5秒一访问,oppoint的时间改为了7:31。
改进方案和预期
这个小工具写的有点仓促,很多地方还不完善,提出问题及改进方案,为我的v2.0做准备。
问题 | 改进方案 |
token还无法自动获取 | 尝试appium自动化操作微信获取token(这时就可以抛弃不断访问这一步了) |
程序稳定性及可用性不足 | 尝试借助windows服务,开机自启,到点直接执行 |
数据修改必须在源码上进行,操作不便 | 尝试利用Tkinter实现GUI,增加可操作性 |
功能单一 | 尝试添加其他场景,包括但不限于:自动预约体育馆其他场馆、自动预约图书馆 |
心得体会
作为一个菜鸡,csdn逛了两年基本都是为了抄作业,咳咳,现在写出一点东西来了也算是自己小小的进步吧。
不断解决问题永远是学习的最佳路径,当然学习的目的也就是为了解决问题。
我喜欢这种做出一个东西来的同时学会了一大堆东西的感觉。可谓,成就感满满hhh。
前段时间看了看scrapy框架,写了几个小爬虫,但是对于原理了解还是不透彻。写了这个小工具后,对调度器、多线程并发、网络请求、管道(解决问题的过程中,还重新挖了挖管道的知识,虽然没用上)等都理解更深刻了。
共勉!!!