仪器预约
最近女票总是要每周半夜预约实验仪器,虽然难度并不大,手快一点总能抢到,但是这种简单重复性的劳动岂是一个程序员能忍的?必然要靠脚本解决啊。
目标
目标是在周六凌晨零点,根据提前设定好的帐号密码和预约信息,到达零点时自动完成预约,期间不需要人为干预。
方案选择
预约需要登录,要用到cookie,因此这里用python自带的urllib和urllib2两个库来实现。基本用法如下:
cookie = cookielib.CookieJar()
handler = urllib2.HTTPCookieProcessor(self.cookie)
opener = urllib2.build_opener(self.handler)
data = urllib.urlencode(dict(
username=username,
password=password
))
result = opener.open(url, data)
urllib2默认的urlopen不支持cookie,所以要自定义一个opener
要实现定时启动,需要用到apscheduler,基本用法:
from apscheduler.schedulers.blocking import BlockingScheduler
def job():
#要定时启动的任务
pass
start_time = dict(
day_of_week='sat',
hour=0,
minute=0,
second=0
)
scheduler = BlockingScheduler()
scheduler.add_job(job, 'cron', **start_time)
scheduler.start()
在start_time中设定好时间,只要等着时间到,任务就会自动开始执行。
分析数据
1. 登录
接下来从chrome中进行手动操作,从控制台中获取请求的URL和数据
首先要登录。在chrome中登录
点击登录后查看登录请求:
找到了登录的URL以及提交的数据。
登录数据:
login_url = 'http://cem.ylab.cn/doLogin.action'
login_data = urllib.urlencode(dict(
origUrl='',
origType='',
rememberMe='false',
username=email,
password=password
))
2. 预约
手动预约一次
用chrome查看数据:
找到了POST的URL以及要提交的数据。后面经过测试,currentDate可以不加。
预约数据
reserve_url = 'http://cem.ylab.cn/user/doReserve.action'
reserve_data = urllib.urlencode({
'reserveDate': reserveDate,
'instrumentId': instrumentId,
'reserveStartTime': reserveStartTime,
'reserveEndTime': reserveEndTime
})
开始编码
初始化函数
先设定好URL,建立opener:
class ReserveTem(object):
def __init__(self):
self.login_url = 'http://cem.ylab.cn/doLogin.action'
self.reserve_url = 'http://cem.ylab.cn/user/doReserve.action'
self.cookie = cookielib.CookieJar()
self.handler = urllib2.HTTPCookieProcessor(self.cookie)
self.opener = urllib2.build_opener(self.handler)
登录函数login
def login(self, email, password):
login_data = urllib.urlencode(dict(
origUrl='',
origType='',
rememberMe='false',
username=email,
password=password
))
login_result = self.opener.open(self.login_url, login_data)
if login_result.geturl() != self.login_url:
# 重定向则登录成功
print '登录成功!'
return True
else:
print '登录失败……'
return False
用户名和密码填入请求的数据,用urllib.urlencode转化成对应的格式,通过前面定义的opener发送POST请求,并根据是否重定向判断是否登录成功,若登录成功则重定向到首页。
login_result.geturl()
是重定向之后的URL,若与self.login_url
相同,则没有重定向,登录失败,否则登录成功。
预约函数reserve
def reserve(self, reserveDate, reserveStartTime, reserveEndTime, instrumentId):
reserve_data = urllib.urlencode({
'reserveDate': reserveDate,
'instrumentId': instrumentId,
'reserveStartTime': reserveStartTime,
'reserveEndTime': reserveEndTime
})
reserve_result = self.opener.open(self.reserve_url, reserve_data)
result = json.loads(reserve_result.read())
if 'success' in result["errorType"] and result["reserveRecordId"]:
# 预约成功
print id2instrument[instrumentId] + ' 预约成功! 预约时间:' + reserveDate + ' ' + \
reserveStartTime + '-' + reserveEndTime
return result["reserveRecordId"]
else:
print id2instrument[instrumentId] + ' 预约失败…… ' + result['errorCode'].encode('utf-8')
return None
与登录类似,将预约时间和仪器ID转格式,作为POST数据,发送POST请求,返回结果是JSON格式。根据返回结果中"errorType"字段判断是否预约成功,成功则返回预约记录ID,否则返回None。
id2instrument
是仪器ID到仪器名称的字典,要在前面定义。这样就可以根据仪器ID得到预定的仪器名称,用于打印输出。
仪器ID号可以在浏览器控制台中看到。
# 实验仪器ID
INSTRUMENT_OLD_F20 = '28ad18ae3ebb4f91b1d52553019ca381'
INSTRUMENT_NEW_F20 = '563e690aae7b41dfb6da1880f291e65b'
id2instrument = {INSTRUMENT_OLD_F20: '老F20', INSTRUMENT_NEW_F20: '新F20'}
开始预约
job函数调用login和reserve函数,实现登录成功后预约。
def job():
rsv = ReserveTem()
rsv.login(email, password)
# 预定
for info in reserve_info:
if info.get('success', False):
continue
id = rsv.reserve(reserveDate=info['reserveDate'],
reserveStartTime=info['reserveStartTime'],
reserveEndTime=info['reserveEndTime'],
instrumentId=info['instrumentId'])
reserve_info
是预订信息,需提前定义好,包括预定时间和仪器ID,可以定义多个,一次预约多个仪器多个时间。同样,帐号和密码也要提前定义
email = os.getenv("username")
password = os.getenv("password")
reserve_info = [
dict(
reserveDate='', # '2017年01月01日'
reserveStartTime='', # '12:00'
reserveEndTime='', # '13:00'
instrumentId='' # INSTRUMENT_OLD_F20
)
]
定时任务
最后就是定时启动了
if __name__ == '__main__':
try_login = ReserveTem()
if try_login.login(email, password):
scheduler = BlockingScheduler()
scheduler.add_job(job, 'cron', **start_time)
print 'job will start at : ' + start_time['day_of_week'].upper() + \
'. %02d:%02d:%02d' % (start_time['hour'], start_time['minute'], start_time['second'])
scheduler.start()
else:
print 'invalid username or password'
为避免到预定时间才发现帐号密码错误,先尝试登录。
登录成功则利用apscheduler
实现定时启动,启动时间start_time
需要提前定义,在运行程序前填好
start_time = dict(
day_of_week='sat',
hour=0,
minute=0,
second=0
)
'sat'是周六,上面的时间代表周六的零点。
定时任务启动,输出程序要开始的时间 如 SAT. 00:00:00
这样就程序的基本功能就实现了。下面再添加进一步的功能:
额外功能
多次尝试
为了提高成功率,应该尝试多次预约,用while循环。
def job():
rsv = ReserveTem()
success_num = 0
try_time = 0
while success_num < len(reserve_info) and try_time < 100:
try_time += 1
# 登录
rsv.login(email, password)
# 预定
for info in reserve_info:
if info.get('success', False):
continue
id = rsv.reserve(reserveDate=info['reserveDate'],
reserveStartTime=info['reserveStartTime'],
reserveEndTime=info['reserveEndTime'],
instrumentId=info['instrumentId'])
sleep(1)
用success_num
记录预约成功的数量,小于reserve_info
的长度代表没有全部预约成功,需要继续尝试。每次尝试间隔一秒sleep(1)
,每次计数器try_time
+1,最多尝试100次。
添加预约信息以及删除预约
在浏览器中预约成功后需填写预约信息,程序中也可以实现此功能。和前面一样,分析浏览器中的URL和数据,同样的方法就可以实现,只不过这里需要提供预约ID号和仪器ID号,这在预约的返回值和预约信息中可以得到。删除预约同理,这里不再赘述。
尝试运行
代码完成后,复制到我的云服务器上,填写帐号密码、开始时间以及预约信息,配置python环境,开始运行。到达时间后成功预约到仪器。以后终于不用再等着零点预约啦!
完整代码
完整的代码可以在github上下载
https://github.com/a188616786a/zju_tem_reserve
# encoding:utf-8
import json
import os
import urllib
import urllib2
import cookielib
from time import sleep
from apscheduler.schedulers.blocking import BlockingScheduler
INSTRUMENT_OLD_F20 = '28ad18ae3ebb4f91b1d52553019ca381'
INSTRUMENT_NEW_F20 = '563e690aae7b41dfb6da1880f291e65b'
id2instrument = {INSTRUMENT_OLD_F20: '老F20', INSTRUMENT_NEW_F20: '新F20'}
# 以下需填写
# --------------------------------------
email = os.getenv("username")
password = os.getenv("password")
reserve_info = [
dict(
reserveDate='', # '2017年01月01日'
reserveStartTime='', # '12:00'
reserveEndTime='', # '13:00'
instrumentId=INSTRUMENT_NEW_F20 # INSTRUMENT_OLD_F20
)
]
start_time = dict(
day_of_week='sat',
hour=0,
minute=0,
second=0
)
# --------------------------------------
class ReserveTem(object):
def __init__(self):
self.login_url = 'http://cem.ylab.cn/doLogin.action' # GET or POST
self.reserve_url = 'http://cem.ylab.cn/user/doReserve.action' # POST
self.add_comment_url = 'http://cem.ylab.cn/user/addReserveComment.action' # POST
self.delete_reserve_url = 'http://cem.ylab.cn/user/deleteReserve.action' # GET or POST
self.cookie = cookielib.CookieJar()
self.handler = urllib2.HTTPCookieProcessor(self.cookie)
self.opener = urllib2.build_opener(self.handler)
def login(self, email, password):
login_data = urllib.urlencode(dict(
origUrl='',
origType='',
rememberMe='false',
username=email,
password=password
))
login_result = self.opener.open(self.login_url, login_data)
if login_result.geturl() != self.login_url:
# 重定向则登录成功
print '登录成功!'
return True
else:
print '登录失败……'
return False
def reserve(self, reserveDate, reserveStartTime, reserveEndTime, instrumentId):
reserve_data = urllib.urlencode({
'reserveDate': reserveDate,
'instrumentId': instrumentId,
'reserveStartTime': reserveStartTime,
'reserveEndTime': reserveEndTime
})
reserve_result = self.opener.open(self.reserve_url, reserve_data)
result = json.loads(reserve_result.read())
if 'success' in result["errorType"] and result["reserveRecordId"]:
# 预约成功
print id2instrument[instrumentId] + ' 预约成功! 预约时间:' + reserveDate + ' ' + \
reserveStartTime + '-' + reserveEndTime
return result["reserveRecordId"]
else:
print id2instrument[instrumentId] + ' 预约失败…… ' + result['errorCode'].encode('utf-8')
return None
def add_comment(self, instrumentId, reserveRecordId, msg):
add_comment_data = urllib.urlencode({
'instrumentId': instrumentId,
'hideRest': '1',
'reserveRecordId': reserveRecordId,
'commentMandatory': 'true',
'comment': msg
})
self.opener.open(self.add_comment_url, add_comment_data)
def delete_reserve(self, reserveRecordId):
delete_data = urllib.urlencode(dict(
hideRest='1',
reserveRecordId=reserveRecordId))
result = self.opener.open(self.delete_reserve_url, delete_data)
if '操作失败' in result.read():
# 失败
print '无此记录,删除失败'
else:
print '删除成功!'
def job():
rsv = ReserveTem()
success_num = 0
try_time = 0
while success_num < len(reserve_info) and try_time < 100:
try_time += 1
# 登录
rsv.login(email, password)
# 预定
for info in reserve_info:
if info.get('success', False):
continue
id = rsv.reserve(reserveDate=info['reserveDate'],
reserveStartTime=info['reserveStartTime'],
reserveEndTime=info['reserveEndTime'],
instrumentId=info['instrumentId'])
# 填写预订信息
if id:
info['success'] = True
success_num += 1
rsv.add_comment(instrumentId=info['instrumentId'], reserveRecordId=id, msg='f20')
sleep(1)
if __name__ == '__main__':
try_login = ReserveTem()
if try_login.login(email, password):
scheduler = BlockingScheduler()
scheduler.add_job(job, 'cron', **start_time)
print 'job will start at : ' + start_time['day_of_week'].upper() + \
'. %02d:%02d:%02d' % (start_time['hour'], start_time['minute'], start_time['second'])
scheduler.start()
else:
print 'invalid username or password'