The magic weapon for lazy people: Detailed explanation of timed booking

foreword

I have nothing to do in the summer vacation. I want to go swimming every morning, lose my stomach, and practice endurance. It happens that our swimming pool provides free tickets in the morning. However, we need to book the next morning from 7:00 the morning of the previous day. Free swimming tickets. In previous summer vacations, I got up on time at 6:55 every morning, and waited for the arrival of 7:1 with half-open eyes, and immediately grabbed tickets! After grabbing, he collapsed on the bed with a relieved face and continued to sleep. It's just torture, I never got up so early in school.
This summer vacation, I really don't want to get up early. Considering that the ticket booking process of the ticket booking website is very simple, can I write a script instead of completing the booking task every morning? The answer is yes. In the end, although the method I actually used is very simple, since it is a practical problem that is rarely encountered in life, I will also share it. I have never had any experience of swiping tickets or reptiles before. (I focus on data mining)
technology changes life, the purpose of this blog is just to share and record the practical problems of lazy people in life with Internet methods.

background

Ticket booking website: Yundong Zhuzhou Swimming Pool booking website 
Ticketing rules: Users can reserve the qualification to receive free swimming public welfare coupons on the second day from 7:00 to 22:00 on the same day. Each user can only book one per day (if there are any remaining tickets) Same-day reservations are also available). 
Swimming pool overview: (Hey, I am great in Zhuzhou
write picture description here 
write picture description here 
Note: This script only implements a simple ticket booking function, because the website does not require verification codes (many lay friends, although I am also a layman, asked me if I could help go to 12306 Grab votes...)

functional goals

  1. Automatic login function ( no verification code! )
  2. Automatically select the reservation venue, time and other information, and submit the form
  3. Support multiple accounts to perform ticket brushing tasks at the same time
  4. timed task
  5. Email reminder of the result of the rush

tool module

  1. python
  2. splinter
  3. shell
  4. crontab or plist

Process analysis

Go directly to the swimming pool reservation interface (there are many other sports that can be reserved, badminton, indoor football... I really want to give a thumbs up to the Zhuzhou government) 
write picture description here 
Click the login button in the upper right corner to enter the login page 
write picture description here 
, enter your mobile phone account and password, and click the login button to enter In the login status, the page will jump to the reservation interface 
write picture description here 
to select the reservation date and time, click the Confirm reservation button to confirm the reservation 
write picture description here 
confirmation dialog box, and click Confirm to complete all the reservation process (not the reservation time or the reservation is completed, so "undefined" is displayed here) 
The above is the whole booking process, it is very simple! It was that simple that gave me the wicked idea of ​​taking a moment to write a script to book tickets on my behalf!

Function realization

Splinter environment configuration

Visit the swimming pool reservation interface

from splinter.browser import Browser
from time import sleep
import datetime
import mail
import sys
url = "http://www.wentiyun.cn/venue-722.html"
#Configure your own chrome driver path
executable_path = {'executable_path':'/usr/local/Cellar/chromedriver/2.31/bin/chromedriver'}

def visitWeb (url):
    #visit website
    b = Browser('chrome', **executable_path)
    b.visit(url)
    return b

Go to the login page and log in with your account and password

 

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 = "[email protected]"  
password = "*****"  
from_addr = "[email protected]"  
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 = "[email protected]"  
    smtpaddr = "smtp.sina.com"
    password = "*****"  
    subject = "这是邮件的主题"
    toaddrs = ["[email protected]"]
    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)

 

  • Pop-up box positioning problem: a confirmation box will pop up when the final reservation is successful: 
    write picture description here 
    it is not easy to get this dialog box. I've tried things alert = browser.get_alert() alert.text alert.accept() alert.dismiss()like this with no success. Finally, right-click the dialog box, find its source code, and find the dialog box according to the ID information to solve it!

Summarize

  1. Technically speaking, this article does not have any bright spots. If you want to deal with a series of websites such as 12306, there are still many troublesome things to study. But why not use technology to solve practical problems in life!
  2. In fact, this timed booking program is a very streamlined thing. In fact, the program is simulating various behaviors of people. Therefore, you must test the website booking process well before coding, and grasp the rules of booking tickets.
  3. If you communicate with your classmates, if you can catch a predetermined message format, wouldn’t it be easier! Well, I think it makes sense, but I didn't try it. I'm also very interested in the real ticket brushing software, but I don't have time to study it yet, and I welcome suggestions from Daniel!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326467319&siteId=291194637
Recommended