儀器預(yù)約
最近女票總是要每周半夜預(yù)約實驗儀器,雖然難度并不大,手快一點總能搶到愕鼓,但是這種簡單重復(fù)性的勞動豈是一個程序員能忍的钙态?必然要靠腳本解決啊。
目標(biāo)
目標(biāo)是在周六凌晨零點菇晃,根據(jù)提前設(shè)定好的帳號密碼和預(yù)約信息册倒,到達(dá)零點時自動完成預(yù)約,期間不需要人為干預(yù)磺送。
方案選擇
預(yù)約需要登錄驻子,要用到cookie,因此這里用python自帶的urllib和urllib2兩個庫來實現(xiàn)估灿〕绾牵基本用法如下:
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默認(rèn)的urlopen不支持cookie,所以要自定義一個opener
要實現(xiàn)定時啟動馅袁,需要用到apscheduler域慷,基本用法:
from apscheduler.schedulers.blocking import BlockingScheduler
def job():
#要定時啟動的任務(wù)
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中設(shè)定好時間,只要等著時間到汗销,任務(wù)就會自動開始執(zhí)行犹褒。
分析數(shù)據(jù)
1. 登錄
接下來從chrome中進(jìn)行手動操作,從控制臺中獲取請求的URL和數(shù)據(jù)
首先要登錄弛针。在chrome中登錄
點擊登錄后查看登錄請求:
找到了登錄的URL以及提交的數(shù)據(jù)叠骑。
登錄數(shù)據(jù):
login_url = 'http://cem.ylab.cn/doLogin.action'
login_data = urllib.urlencode(dict(
origUrl='',
origType='',
rememberMe='false',
username=email,
password=password
))
2. 預(yù)約
手動預(yù)約一次
用chrome查看數(shù)據(jù):
找到了POST的URL以及要提交的數(shù)據(jù)。后面經(jīng)過測試削茁,currentDate可以不加宙枷。
預(yù)約數(shù)據(jù)
reserve_url = 'http://cem.ylab.cn/user/doReserve.action'
reserve_data = urllib.urlencode({
'reserveDate': reserveDate,
'instrumentId': instrumentId,
'reserveStartTime': reserveStartTime,
'reserveEndTime': reserveEndTime
})
開始編碼
初始化函數(shù)
先設(shè)定好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)
登錄函數(shù)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
用戶名和密碼填入請求的數(shù)據(jù)慰丛,用urllib.urlencode轉(zhuǎn)化成對應(yīng)的格式,通過前面定義的opener發(fā)送POST請求瘾杭,并根據(jù)是否重定向判斷是否登錄成功璧帝,若登錄成功則重定向到首頁。
login_result.geturl()
是重定向之后的URL富寿,若與self.login_url
相同,則沒有重定向锣夹,登錄失敗页徐,否則登錄成功。
預(yù)約函數(shù)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"]:
# 預(yù)約成功
print id2instrument[instrumentId] + ' 預(yù)約成功! 預(yù)約時間:' + reserveDate + ' ' + \
reserveStartTime + '-' + reserveEndTime
return result["reserveRecordId"]
else:
print id2instrument[instrumentId] + ' 預(yù)約失敗…… ' + result['errorCode'].encode('utf-8')
return None
與登錄類似银萍,將預(yù)約時間和儀器ID轉(zhuǎn)格式变勇,作為POST數(shù)據(jù),發(fā)送POST請求,返回結(jié)果是JSON格式搀绣。根據(jù)返回結(jié)果中"errorType"字段判斷是否預(yù)約成功飞袋,成功則返回預(yù)約記錄ID,否則返回None链患。
id2instrument
是儀器ID到儀器名稱的字典巧鸭,要在前面定義。這樣就可以根據(jù)儀器ID得到預(yù)定的儀器名稱麻捻,用于打印輸出纲仍。
儀器ID號可以在瀏覽器控制臺中看到。
# 實驗儀器ID
INSTRUMENT_OLD_F20 = '28ad18ae3ebb4f91b1d52553019ca381'
INSTRUMENT_NEW_F20 = '563e690aae7b41dfb6da1880f291e65b'
id2instrument = {INSTRUMENT_OLD_F20: '老F20', INSTRUMENT_NEW_F20: '新F20'}
開始預(yù)約
job函數(shù)調(diào)用login和reserve函數(shù)贸毕,實現(xiàn)登錄成功后預(yù)約郑叠。
def job():
rsv = ReserveTem()
rsv.login(email, password)
# 預(yù)定
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
是預(yù)訂信息,需提前定義好明棍,包括預(yù)定時間和儀器ID乡革,可以定義多個,一次預(yù)約多個儀器多個時間摊腋。同樣沸版,帳號和密碼也要提前定義
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
)
]
定時任務(wù)
最后就是定時啟動了
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'
為避免到預(yù)定時間才發(fā)現(xiàn)帳號密碼錯誤,先嘗試登錄歌豺。
登錄成功則利用apscheduler
實現(xiàn)定時啟動推穷,啟動時間start_time
需要提前定義,在運行程序前填好
start_time = dict(
day_of_week='sat',
hour=0,
minute=0,
second=0
)
'sat'是周六类咧,上面的時間代表周六的零點馒铃。
定時任務(wù)啟動,輸出程序要開始的時間 如 SAT. 00:00:00
這樣就程序的基本功能就實現(xiàn)了痕惋。下面再添加進(jìn)一步的功能:
額外功能
多次嘗試
為了提高成功率区宇,應(yīng)該嘗試多次預(yù)約,用while循環(huán)值戳。
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)
# 預(yù)定
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
記錄預(yù)約成功的數(shù)量议谷,小于reserve_info
的長度代表沒有全部預(yù)約成功,需要繼續(xù)嘗試堕虹。每次嘗試間隔一秒sleep(1)
卧晓,每次計數(shù)器try_time
+1,最多嘗試100次赴捞。
添加預(yù)約信息以及刪除預(yù)約
在瀏覽器中預(yù)約成功后需填寫預(yù)約信息逼裆,程序中也可以實現(xiàn)此功能。和前面一樣赦政,分析瀏覽器中的URL和數(shù)據(jù)胜宇,同樣的方法就可以實現(xiàn),只不過這里需要提供預(yù)約ID號和儀器ID號,這在預(yù)約的返回值和預(yù)約信息中可以得到桐愉。刪除預(yù)約同理财破,這里不再贅述。
嘗試運行
代碼完成后从诲,復(fù)制到我的云服務(wù)器上左痢,填寫帳號密碼、開始時間以及預(yù)約信息盏求,配置python環(huán)境抖锥,開始運行。到達(dá)時間后成功預(yù)約到儀器碎罚。以后終于不用再等著零點預(yù)約啦磅废!
完整代碼
完整的代碼可以在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"]:
# 預(yù)約成功
print id2instrument[instrumentId] + ' 預(yù)約成功! 預(yù)約時間:' + reserveDate + ' ' + \
reserveStartTime + '-' + reserveEndTime
return result["reserveRecordId"]
else:
print id2instrument[instrumentId] + ' 預(yù)約失敗…… ' + 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)
# 預(yù)定
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'])
# 填寫預(yù)訂信息
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'