`

懒人法宝:定时订票详解

阅读更多

前言

暑假闲来无事,每天上午的宝贵时间想去游泳,减减肚子,练练耐力,正好我们那个地方游泳馆上午提供免费的票,但是,需要前一天早上七点开始预定第二天上午 的免费游泳票。往年暑假,我是每天早上六点五十五准时起床,眼睛半睁不睁的等着七点一到,立马抢票!抢完一脸解脱地瘫倒在床上继续睡觉。简直就是煎熬啊, 我在学校都没起这么早过。
今年暑假,我实在是不想再早起了,考虑到订票网站的订票流程非常简易,是否能写一个脚本代替我每天早上完成订票任务呢。答案是肯定的。最后我大概虽然其实 用到的方法很简单,但是既然是在生活中难得遇到的实际问题,我也做一个分享。之前我是没有任何刷票、爬虫经历的。(本人专注数据挖掘)
技术改变生活,本篇博客的目的仅仅是分享并记录一下用互联网方法解决懒人在生活中的实际问题。

背景

订票网站:韵动株洲游泳馆订票网站 
订票规则:用户当天7:00—22:00,预约第二日免费游泳公益券领取资格,每位用户每天只能预订一张(如有余票当天也可预订)。 
游泳馆概况:(嘿嘿,我大株洲就是厉害) 
这里写图片描述 
这里写图片描述 
注意:本脚本只实现简单的订票功能,因为该网站无需验证码(很多外行的朋友,虽然我也是外行,都问我能不能帮忙去12306抢票。。。)

功能目标

  1. 自动登录功能(无验证码!
  2. 自动选择预定场地、时间等信息,并提交表单
  3. 支持多账号同时进行刷票任务
  4. 定时任务
  5. 邮件提醒抢票结果

工具模块

  1. python
  2. splinter
  3. shell
  4. crontabplist

流程分析

直接进入游泳馆预订界面(还有很多其他的运动项目可以预约哦,羽毛球、室内足球…真想给株洲政府点个赞) 
这里写图片描述 
点击右上角登录按钮进入登录页面 
这里写图片描述 
输入手机账号和密码,点击登录按钮进入登录状态,此时页面会跳转到预订界面 
这里写图片描述 
选择好预定日期、预定时间,点击确认预订按钮确认预订 
这里写图片描述 
确认对话框点击确认,完成所有预订过程(非预订时间或者预定完了所以这里显示”undefined”) 
以上就是整个预定流程,很简单吧!正是这么简单,让我萌生了花点时间写个脚本来代替我订票的邪恶想法!

功能实现

Splinter环境配置

访问游泳馆预定界面

from splinter.browser import Browser
from time import sleep
import datetime
import mail
import sys
url = "http://www.wentiyun.cn/venue-722.html"
#配置自己的chrome驱动路径
executable_path = {'executable_path':'/usr/local/Cellar/chromedriver/2.31/bin/chromedriver'}

def visitWeb(url):
    #访问网站
    b = Browser('chrome', **executable_path)
    b.visit(url)
    return b

进入登录页面并账号密码登录

 

def login(b, username, passwd):
    try:
        lf = b.find_link_by_text(u"登录")#登录按钮是链接的形式
        sleep(0.1)
        b.execute_script("window.scrollBy(300,0)")#下滑滚轮,将输入框和确认按钮移动至视野范围内
        lf.click()
        b.fill("username",username) # username部分输入自己的账号
        b.fill("password",passwd) # passwd部分输入账号密码
        button = b.find_by_name("subButton")
        button.click()
    except Exception, e:
        print "登录失败,请检查登陆相关:", e
        sys.exit(1)
 

 

持续刷票策略

一旦以用户的身份进入到预订界面,就需要按时间、场地信息要求进行选择,并确认。考虑到很可能提前预约或其他情况导致某次订票失败,所以,仅仅一次订票行为是不行的,需要反复订票行为,直到订票成功,于是,订票策略如下: 
1. 反复订票行为,退出条件:订票一分钟,即到七点过一分后退出,或预订成功后退出 
2. 一次完整的订票退出后(满足1退出条件),为了保险,重启chrome,继续预订操作,十次操作后,退出预订程序 
3. 时间选择:获取明天日期,选择预订明天的游泳票

 

def getBookTime():
    #今天订明天,时间逻辑
    date = datetime.datetime.now() + datetime.timedelta(days=1)
    dateStr = date.strftime('%Y-%m-%d')
    year, month, day = dateStr.split('-')
    date = '/'.join([month, day])
    return date

 

def book(b):
    #反复订票行为,直到时间条件达到或预订成功退出
    while(True):
        start = datetime.datetime.now()
        startStr = start.strftime('%Y-%m-%d %H:%M:%S')
        print "********** %s ********" % startStr
        try:
            #选择日期
            date = getBookTime()
            b.find_link_by_text(date).click()
            #按钮移到视野范围内
            b.execute_script("window.scrollBy(0,100)")
            #css显示确认按钮
            js = "var i=document.getElementsByClassName(\"btn_box\");i[0].style=\"display:true;\""
            b.execute_script(js)
            #点击确认
            b.find_by_name('btn_submit').click()
            sleep(0.1)
            b.find_by_id('popup_ok').click()
            sleep(0.1)
            #测试弹出框
            #test(b)
            #sleep(0.1)
            result = b.evaluate_script("document.getElementById(\"popup_message\").innerText")
            b.find_by_id('popup_ok').click()
            sleep(0.1)
            print result
            end = datetime.datetime.now()
            print "预订页面刷票耗时:%s秒" % (end-start).seconds
            if result == "预订成功!".decode("utf-8"):
                return True
            elif not timeCondition():
                return False
            b.reload()
        except Exception, e:
            print '预订页面刷票失败,原因:', e
            end = datetime.datetime.now()
            print "共耗时:%s秒" % (end-start).seconds
            #判读当前时间如果是7点过5分了,放弃订票
            if not timeCondition():
                return False
            b.reload()
 
def tryBook(username, passwd):
    #持续刷票10次后,退出程序
    r = False
    for i in xrange(10):
        try:
            start = datetime.datetime.now()
            startStr = start.strftime('%Y-%m-%d %H:%M:%S')
            print "========== 第%s次尝试,开始时间%s ========" % (i, startStr)
            b = visitWeb(url)
            login(b, username, passwd)
            r = book(b)
            if r:
                print "book finish!"
                b.quit()
                break
            else:
                print "try %s again, 已经七点1分,抢票进入尾声" % i
                b.quit()
            end = datetime.datetime.now()
            print "========== 第%s次尝试结束,共耗时%s秒 ========" % (i, (end-start).seconds)
        except Exception, e:
            print '第%s次尝试失败,原因:%s' % (i, e)
            end = datetime.datetime.now()
            print "========== 第%s次尝试结束,共耗时%s秒 ========" % (i, (end-start).seconds)
            return False
    return r
 

邮件服务

  • 参考廖雪峰老师的实现哦,程序其实不麻烦,主要是邮箱的SMTP服务!
  • 需要邮箱开通SMTP代理服务,如果你qq号是很久之前注册的了,那我不推荐使用qq邮箱,一系列的密保会让你崩溃。推荐使用新浪邮箱。
  • 发送程序如下mail.py
import smtplib  
import traceback  
from email.mime.text import MIMEText  
from email.mime.multipart import MIMEMultipart  
from email.header import Header
from email.utils import parseaddr, formataddr
'''
to_addr = "844582201@qq.com"  
password = "*****"  
from_addr = "m13072163887@163.com"  
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
server = smtplib.SMTP("smtp.163.com") # SMTP协议默认端口是25
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
'''
'''
    @subject:邮件主题 
    @msg:邮件内容 
    @toaddrs:收信人的邮箱地址 
    @fromaddr:发信人的邮箱地址 
    @smtpaddr:smtp服务地址,可以在邮箱看,比如163邮箱为smtp.163.com 
    @password:发信人的邮箱密码 
''' 
def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

def sendmail(subject,msg,toaddrs,fromaddr,smtpaddr,password):  
    mail_msg = MIMEMultipart()  
    if not isinstance(subject,unicode):  
        subject = unicode(subject, 'utf-8')  
    mail_msg['Subject'] = subject  
    mail_msg['From'] = _format_addr('Python-auto <%s>' % fromaddr)
    mail_msg['To'] = ','.join(toaddrs)  
    mail_msg.attach(MIMEText(msg, 'plain', 'utf-8'))  
    try:  
        s = smtplib.SMTP()  
        s.set_debuglevel(1)
        s.connect(smtpaddr,25)  #连接smtp服务器  
        s.login(fromaddr,password)  #登录邮箱  
        s.sendmail(fromaddr, toaddrs, mail_msg.as_string()) #发送邮件  
        s.quit()  
    except Exception,e:  
       print "Error: unable to send email", e  
       print traceback.format_exc()  

def send(msg):
    fromaddr = "mynameislps@sina.com"  
    smtpaddr = "smtp.sina.com"
    password = "*****"  
    subject = "这是邮件的主题"
    toaddrs = ["844582201@qq.com"]
    sendmail(subject,msg,toaddrs,fromaddr,smtpaddr,password)

 

定时任务策略

每天七点,抢票开始。为了保险并且考虑到上文所构建的抢票策略,我们可以六点五十九分开始操作(考虑到还要访问预订页面、登录页面以及登录操作等,万一有一定的延时)。于是我们将任务布置在每天早上的六点五十九分。 
定时任务的工具有两种,一种是使用Linux自带的定时工具crontab,一种是使用比较优雅的Mac自带的定时工具plist。这两种工具非常简单实用,这里也不做太多介绍。

多账号同时订票操作策略

这就需要借助强大的shell脚本,我们把需要订票的帐号密码信息配置在shell内,同时shell根据这些帐号信息启动不同的进程来同时完成订票任务。

#!/bin/bash
my_array=("130****3887" "****"\
        "187****4631" "****")
#待操作用户个数
len=${#my_array[@]}
len=`expr $len / 2`
i=0
while (($i < $len))
do 
    echo "第($i)个用户为: ${my_array[2*i]}"
    logname="/Users/lps/work/program/ticketReservation/log/${my_array[2*i]}.log"
    nohup /Users/lps/anaconda/bin/python /Users/lps/work/program/ticketReservation/book.py ${my_array[2*i]} ${my_array[2*i+1]} > ${logname} 2>&1 &
    i=`expr $i + 1`
done

日志服务

良好、健壮的程序需要一套比较完备的日志系统,本程序的日志服务都在上文中的程序中反映了,当然不见得是最好的。仅供参考。这方便我们定位错误或失败的发生位置!

完整的工程在Github上:https://github.com/lps683/ticketBook

某些蛋疼的问题

  • 需要将按钮/链接显示在视野范围内才能进行点击操作。上文程序中诸如b.execute_script("window.scrollBy(300,0)")等操作都是上下调整页面位置,将按钮显示在视野范围内;如果某些按钮是invisible的,那么我们可以通过修改JS中控件的属性来显示按钮。如上文程序中的
#css显示确认按钮
js = "var i=document.getElementsByClassName(\"btn_box\");i[0].style=\"display:true;\""
b.execute_script(js)

 

  • 弹出框定位问题:最后预定成功会弹出一个确认框: 
    这里写图片描述 
    那要获得这个对话框并不容易。我尝试过诸如alert = browser.get_alert() alert.text alert.accept() alert.dismiss()之类的办法都没有成功。最后右键这个对话框,找到它的源码,根据ID信息找到这个对话框才解决的!

总结

  1. 技术上来说,本文并没有什么亮点,如果要应付12306等一系列的网站,那还有很多很麻烦的东西要研究。但是,能用技术来解决生活中的实际问题,何乐而不为呢!
  2. 其实这个定时订票程序是一个很流程化的东西,实际上就是程序在模拟人的各种行为,所以在coding前一定要好好测试网站订票流程,把握订票的规律。
  3. 有和同学交流,如果能catch到预定的消息格式,那岂不是更加简便了!嗯,我觉得很有道理,不过没有作尝试,我对真正的那些刷票软件也非常感兴趣,但是现在还没有时间去研究,也欢迎大牛指点!
1
0
分享到:
评论

相关推荐

    定时无忧 定时关机 懒人模式

    【定时无忧 定时关机 懒人模式】是一款专为用户打造的高效便捷的定时关机软件,尤其适合那些需要在特定时间自动关闭电脑的“懒人”用户。这款软件通过简单易用的界面,让即便是电脑操作不熟练的用户也能轻松设置定时...

    懒人包:UTF-8的ApacheJMeter_http.jar和ApacheJMeter_core.jar

    Jmeter5.3版本,修改源码RequestViewHTTP.class,字符编码修改为utf-8 ,解决上传文件与录制脚本的中文乱码问题,下载jar文件替换到Jmeter的lib/ext目录下即可,重启jmeter,重新选择上传文件,在请求中设置内容编码...

    懒人工具箱V1.0(懒人专用)

    "懒人工具箱V1.0(懒人专用)"是一款专为不熟悉或不愿进行复杂操作的用户设计的实用工具集合。它旨在简化日常电脑使用中的各种任务,让用户能够快速、简便地完成一些常见的系统管理和优化工作。尽管这款工具箱可能...

    按键精灵的懒人插件12

    4. **定时任务**:设置定时任务是自动化流程中的常见需求,懒人插件可能提供了更直观的定时器设置,允许用户设定脚本在特定时间自动运行。 5. **多线程支持**:通过多线程技术,懒人插件可能使得脚本可以同时处理多...

    lua 懒人精灵懒人精灵懒人精灵懒人精灵

    懒人精灵懒人精灵懒人精灵懒人精灵懒人精灵懒人精灵

    懒人办公插件_Office_插件_

    标题中的“懒人办公插件”是一个针对Office软件的扩展工具,旨在提升用户在处理Word文档和Excel表格时的工作效率。这些插件通常包含了各种自动化功能、快捷操作和定制化选项,以简化复杂的任务,让办公更为便捷。...

    经典51懒人5步速成法

    ### 经典51懒人5步速成法知识点总结 #### 一、经典51单片机概述 - **定位**: 将51单片机视为一款玩具而非高科技产品,以此降低学习门槛,增强学习趣味性。 - **特点**: 结构简单、功能齐全,适合初学者快速入门。 ...

    懒人听书用耳朵看书

    2. **智能播放**:支持定时播放、定集播放、睡眠模式等功能,用户可以根据自身需求设定播放设置,确保在合适的时间享受听书体验。 3. **下载缓存**:用户可以选择将喜欢的书籍下载到本地,即使在没有网络的情况下也...

    懒人精灵源码懒人精灵源码懒人精灵源码懒人精灵源码

    3. 自动化流程:懒人精灵可能包含自动化任务调度和执行的机制,涉及事件驱动编程或定时任务。 4. 用户接口:如果是一个图形用户界面应用,那么UI设计和交互逻辑是关键。 5. 数据处理:可能涉及到数据的读取、存储和...

    懒人许工具.7z懒人许工具

    "懒人许工具.7z"是一款针对CAD(计算机辅助设计)软件的便捷插件集合,主要用于提升用户在使用CAD时的工作效率。该工具可能包含了多个版本,适用于不同的CAD平台,如2004年至2010年的版本。从压缩包的文件名来看,...

    按键精灵的懒人插件

    按键精灵的懒人插件

    【lxj1985】懒人插件V15.0112.0.7

    "【lxj1985】懒人插件V15.0112.0.7"是一款专为程序员和开发者设计的便捷工具,它提供了丰富的功能接口,旨在简化和加速开发过程。这款插件的核心是COM接口DLL文件,全称为Component Object Model Dynamic Link ...

    懒人C51(简单的51系列代码自动生成器)

    《懒人C51:轻松入门51系列芯片编程》 C51是为8051微控制器家族设计的一种高级编程语言,它基于标准的C语言并加入了针对51系列芯片的特定扩展。这款“懒人C51”工具正是为了简化C51编程而开发,特别适合初学者快速...

    AutoHotKey 懒人包+快餐店+编辑器

    AutoHotKey 懒人包 内容: AHK 中文汉化 AHK 脚本编辑器 SciTe AHK 快餐店系列教程 实用脚本

    图片切换-懒人图库22222

    懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片切换 懒人图库图片...

    【新版】懒人办公插件V15.0106.0.11

    【新版】懒人办公插件V15.0106.0.11是一款针对办公软件设计的高效工具,旨在提升用户在处理Excel、Word和金山ET表格时的工作效率。这款插件通过COM接口DLL文件实现了对这些常用办公软件的强大功能集成,使得复杂的...

    OS X 10.8.3懒人镜像

    【OS X 10.8.3懒人镜像】是一个专为简化Mac OS X操作系统安装流程而设计的特别版本,适用于那些希望快速、简便地完成安装的用户。这个镜像文件,通常被称为"懒人版",包含了必要的驱动程序和其他组件,使得用户在...

    从零开始,图文详解Win7下制作懒人版和MAC系统安装过程

    从零开始,不需MacDrive,图文详解Win7下用原版Lion制作懒人版和MacPE及系统安装过程。 本教程看起来很大,实际上纯粹的文字描述并不多,主要是图片占用了很大篇幅,照着制作的话过程还是很简单的,多熟悉下几个要点...

    CE修改器懒人1.0

    在这种情况下,CE修改器懒人1.0应运而生,为希望在游戏中获得额外乐趣的玩家提供了一种简便的解决方案。 Cheat Engine,简称CE,是一款流行的游戏内存修改工具。它的核心功能是提供强大的搜索、修改、以及分析程序...

Global site tag (gtag.js) - Google Analytics