实验仪器自动预约脚本

仪器预约

最近女票总是要每周半夜预约实验仪器,虽然难度并不大,手快一点总能抢到,但是这种简单重复性的劳动岂是一个程序员能忍的?必然要靠脚本解决啊。

目标


目标是在周六凌晨零点,根据提前设定好的帐号密码和预约信息,到达零点时自动完成预约,期间不需要人为干预。

方案选择


预约需要登录,要用到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中登录

实验仪器自动预约脚本_第1张图片

点击登录后查看登录请求:

实验仪器自动预约脚本_第2张图片
实验仪器自动预约脚本_第3张图片

找到了登录的URL以及提交的数据。

登录数据:

login_url = 'http://cem.ylab.cn/doLogin.action'
login_data = urllib.urlencode(dict(
        origUrl='',
        origType='',
        rememberMe='false',
        username=email,
        password=password
))

2. 预约

手动预约一次

实验仪器自动预约脚本_第4张图片

用chrome查看数据:

实验仪器自动预约脚本_第5张图片
实验仪器自动预约脚本_第6张图片

找到了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'

你可能感兴趣的:(实验仪器自动预约脚本)