实验仪器自动预约脚本

仪器预约

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

目标


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

方案选择


预约需要登录,要用到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'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容