懶人法寶:定時(shí)訂票詳解

?

現(xiàn)在科技越來越發(fā)達(dá),人們的生活越來越便捷,但是這樣子卻導(dǎo)致人類越來越懶了拷淘!到底是懶惰推動(dòng)了科技,還是科技助長了懶惰指孤。

背景

訂票網(wǎng)站:韻動(dòng)株洲游泳館訂票網(wǎng)站
訂票規(guī)則:用戶當(dāng)天7:00—22:00启涯,預(yù)約第二日免費(fèi)游泳公益券領(lǐng)取資格,每位用戶每天只能預(yù)訂一張(如有余票當(dāng)天也可預(yù)訂)恃轩。
游泳館概況

注意:本腳本只實(shí)現(xiàn)簡單的訂票功能结洼,因?yàn)樵摼W(wǎng)站無需驗(yàn)證碼(很多外行的朋友,都問能不能幫忙去12306搶票叉跛。松忍。。)

功能目標(biāo)

  • 自動(dòng)登錄功能(無驗(yàn)證碼?昀濉)

  • 自動(dòng)選擇預(yù)定場地鸣峭、時(shí)間等信息,并提交表單

  • 支持多賬號(hào)同時(shí)進(jìn)行刷票任務(wù)

  • 定時(shí)任務(wù)

**郵件提醒搶票結(jié)果

工具模塊

  • python

  • splinter

  • shell

  • crontab 或 plist

流程分析

直接進(jìn)入游泳館預(yù)訂界面(還有很多其他的運(yùn)動(dòng)項(xiàng)目可以預(yù)約哦酥艳,羽毛球摊溶、室內(nèi)足球...真想給株洲政府點(diǎn)個(gè)贊)

點(diǎn)擊右上角登錄按鈕進(jìn)入登錄頁面

輸入手機(jī)賬號(hào)和密碼,點(diǎn)擊登錄按鈕進(jìn)入登錄狀態(tài)充石,此時(shí)頁面會(huì)跳轉(zhuǎn)到預(yù)訂界面

選擇好預(yù)定日期莫换、預(yù)定時(shí)間,點(diǎn)擊確認(rèn)預(yù)訂按鈕確認(rèn)預(yù)訂

確認(rèn)對(duì)話框點(diǎn)擊確認(rèn)骤铃,完成所有預(yù)訂過程(非預(yù)訂時(shí)間或者預(yù)定完了所以這里顯示 "undefined" )
以上就是整個(gè)預(yù)定流程浓镜,很簡單吧!

功能實(shí)現(xiàn)

◆ Splinter 環(huán)境配置

  • 下載并安裝 splinter

  • 下載并安裝 chrome Web 驅(qū)動(dòng)

  • python splinter 參考教程

◆ 訪問游泳館預(yù)定界面

from splinter.browser import Browser
from time import sleep
import datetime
import mail
import sys
url = "http://www.wentiyun.cn/venue-722.html"
#配置自己的chrome驅(qū)動(dòng)路徑
executable_path = {'executable_path':'/usr/local/Cellar/chromedriver/2.31/bin/chromedriver'}

def visitWeb(url):
    #訪問網(wǎng)站
    b = Browser('chrome', **executable_path)
    b.visit(url)
    return b

◆ 進(jìn)入登錄頁面并賬號(hào)密碼登錄

    try:
        lf = b.find_link_by_text(u"登錄")#登錄按鈕是鏈接的形式
        sleep(0.1)
        b.execute_script("window.scrollBy(300,0)")#下滑滾輪劲厌,將輸入框和確認(rèn)按鈕移動(dòng)至視野范圍內(nèi)
        lf.click()
        b.fill("username",username) # username部分輸入自己的賬號(hào)
        b.fill("password",passwd) # passwd部分輸入賬號(hào)密碼
        button = b.find_by_name("subButton")
        button.click()
    except Exception, e:
        print "登錄失敗膛薛,請檢查登陸相關(guān):", e
        sys.exit(1)

◆ 持續(xù)刷票策略

一旦以用戶的身份進(jìn)入到預(yù)訂界面,就需要按時(shí)間补鼻、場地信息要求進(jìn)行選擇哄啄,并確認(rèn)雅任。考慮到很可能提前預(yù)約或其他情況導(dǎo)致某次訂票失敗咨跌,所以沪么,僅僅一次訂票行為是不行的,需要反復(fù)訂票行為锌半,直到訂票成功禽车,于是,訂票策略如下:

  • 反復(fù)訂票行為刊殉,退出條件:訂票一分鐘殉摔,即到七點(diǎn)過一分后退出,或預(yù)訂成功后退出

  • 一次完整的訂票退出后(滿足1退出條件)记焊,為了保險(xiǎn)逸月,重啟 chrome,繼續(xù)預(yù)訂操作遍膜,十次操作后碗硬,退出預(yù)訂程序

  • 時(shí)間選擇:獲取明天日期,選擇預(yù)訂明天的游泳票

def getBookTime():
    #今天訂明天瓢颅,時(shí)間邏輯
    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 timeCondition(h=7.0,m=1.0,s=0.0):
    #退出時(shí)間判斷
    now = datetime.datetime.now()
    dateStr = now.strftime('%H-%M-%S')
    hour, minute, second = dateStr.split('-')
    t1 = h*60.0 + m + s/60.0
    t2 = float(hour)*60.0 + float(minute) + float(second)/60.0
    if t1 >= t2:
        return True
    return False
def book(b):
    #反復(fù)訂票行為,直到時(shí)間條件達(dá)到或預(yù)訂成功退出
    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()
            #按鈕移到視野范圍內(nèi)
            b.execute_script("window.scrollBy(0,100)")
            #css顯示確認(rèn)按鈕
            js = "var i=document.getElementsByClassName(\"btn_box\");i[0].style=\"display:true;\""
            b.execute_script(js)
            #點(diǎn)擊確認(rèn)
            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 "預(yù)訂頁面刷票耗時(shí):%s秒" % (end-start).seconds
            if result == "預(yù)訂成功!".decode("utf-8"):
                return True
            elif not timeCondition():
                return False
            b.reload()
        except Exception, e:
            print '預(yù)訂頁面刷票失敗,原因:', e
            end = datetime.datetime.now()
            print "共耗時(shí):%s秒" % (end-start).seconds
            #判讀當(dāng)前時(shí)間如果是7點(diǎn)過5分了恩尾,放棄訂票
            if not timeCondition():
                return False
            b.reload()
def tryBook(username, passwd):
    #持續(xù)刷票10次后,退出程序
    r = False
    for i in xrange(10):
        try:
            start = datetime.datetime.now()
            startStr = start.strftime('%Y-%m-%d %H:%M:%S')
            print "========== 第%s次嘗試,開始時(shí)間%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, 已經(jīng)七點(diǎn)1分挽懦,搶票進(jìn)入尾聲" % i
                b.quit()
            end = datetime.datetime.now()
            print "========== 第%s次嘗試結(jié)束,共耗時(shí)%s秒 ========" % (i, (end-start).seconds)
        except Exception, e:
            print '第%s次嘗試失敗特笋,原因:%s' % (i, e)
            end = datetime.datetime.now()
            print "========== 第%s次嘗試結(jié)束,共耗時(shí)%s秒 ========" % (i, (end-start).seconds)
            return False
    return r

◆ 郵件服務(wù)

  • 參考一些資料實(shí)現(xiàn)的,程序其實(shí)不麻煩巾兆,主要是郵箱的 SMTP 服務(wù)!

  • 需要郵箱開通 SMTP 代理服務(wù)虎囚,如果你 qq 號(hào)是很久之前注冊的了角塑,那我不推薦使用 qq 郵箱,一系列的密保會(huì)讓你崩潰淘讥。推薦使用新浪郵箱圃伶。

  • 發(fā)送程序如下 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協(xié)議默認(rèn)端口是25
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
'''
'''
    @subject:郵件主題 
    @msg:郵件內(nèi)容 
    @toaddrs:收信人的郵箱地址 
    @fromaddr:發(fā)信人的郵箱地址 
    @smtpaddr:smtp服務(wù)地址,可以在郵箱看蒲列,比如163郵箱為smtp.163.com 
    @password:發(fā)信人的郵箱密碼 
''' 
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服務(wù)器  
        s.login(fromaddr,password)  #登錄郵箱  
        s.sendmail(fromaddr, toaddrs, mail_msg.as_string()) #發(fā)送郵件  
        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)

◆ 定時(shí)任務(wù)策略

每天七點(diǎn)窒朋,搶票開始。為了保險(xiǎn)并且考慮到上文所構(gòu)建的搶票策略蝗岖,我們可以六點(diǎn)五十九分開始操作(考慮到還要訪問預(yù)訂頁面侥猩、登錄頁面以及登錄操作等,萬一有一定的延時(shí))抵赢。于是我們將任務(wù)布置在每天早上的六點(diǎn)五十九分欺劳。
定時(shí)任務(wù)的工具有兩種唧取,一種是使用 Linux 自帶的定時(shí)工具 crontab,一種是使用比較優(yōu)雅的 Mac 自帶的定時(shí)工具 plist划提。這兩種工具非常簡單實(shí)用枫弟,這里也不做太多介紹。

◆ 多賬號(hào)同時(shí)訂票操作策略

這就需要借助強(qiáng)大的 shell 腳本鹏往,我們把需要訂票的帳號(hào)密碼信息配置在 shell內(nèi)淡诗,同時(shí) shell 根據(jù)這些帳號(hào)信息啟動(dòng)不同的進(jìn)程來同時(shí)完成訂票任務(wù)。

#!/bin/bash
my_array=("130****3887" "****"\
        "187****4631" "****")
#待操作用戶個(gè)數(shù)
len=${#my_array[@]}
len=`expr $len / 2`
i=0
while (($i < $len))
do 
    echo "第($i)個(gè)用戶為: ${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

◆ 日志服務(wù)

良好伊履、健壯的程序需要一套比較完備的日志系統(tǒng)韩容,本程序的日志服務(wù)都在上文中的程序中反映了,當(dāng)然不見得是最好的湾碎。僅供參考宙攻。這方便我們定位錯(cuò)誤或失敗的發(fā)生位置!

某些蛋疼的問題

  • 需要將按鈕/鏈接顯示在視野范圍內(nèi)才能進(jìn)行點(diǎn)擊操作介褥。上文程序中諸如b.execute_script("window.scrollBy(300,0)") 等操作都是上下調(diào)整頁面位置座掘,將按鈕顯示在視野范圍內(nèi);如果某些按鈕是 invisible 的柔滔,那么我們可以通過修改 JS 中控件的屬性來顯示按鈕溢陪。如上文程序中的
#css顯示確認(rèn)按鈕
js = "var i=document.getElementsByClassName(\"btn_box\");i[0].style=\"display:true;\""
b.execute_script(js)
  • 彈出框定位問題:最后預(yù)定成功會(huì)彈出一個(gè)確認(rèn)框:

那要獲得這個(gè)對(duì)話框并不容易。我嘗試過諸如 alert = browser.get_alert() alert.text alert.accept() alert.dismiss() 之類的辦法都沒有成功睛廊。最后右鍵這個(gè)對(duì)話框形真,找到它的源碼,根據(jù)ID信息找到這個(gè)對(duì)話框才解決的超全!

總結(jié)

技術(shù)上來說咆霜,本文并沒有什么亮點(diǎn),如果要應(yīng)付 12306 等一系列的網(wǎng)站嘶朱,那還有很多很麻煩的東西要研究蛾坯。但是,能用技術(shù)來解決生活中的實(shí)際問題疏遏,何樂而不為呢脉课!

其實(shí)這個(gè)定時(shí)訂票程序是一個(gè)很流程化的東西,實(shí)際上就是程序在模擬人的各種行為财异,所以在 coding 前一定要好好測試網(wǎng)站訂票流程倘零,把握訂票的規(guī)律。

有和同學(xué)交流戳寸,如果能 catch 到預(yù)定的消息格式呈驶,那豈不是更加簡便了!嗯疫鹊,我覺得很有道理俐东,不過沒有作嘗試跌穗,我對(duì)真正的那些刷票軟件也非常感興趣,但是現(xiàn)在還沒有時(shí)間去研究虏辫,也歡迎大牛指點(diǎn)蚌吸!

項(xiàng)目源碼:https://github.com/lps683/tic...

原文鏈接:https://segmentfault.com/a/1190000011008702

轉(zhuǎn)載 | Segmentfault

更多詳情關(guān)注我們的微信公眾號(hào):Reboot51

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砌庄,隨后出現(xiàn)的幾起案子羹唠,更是在濱河造成了極大的恐慌,老刑警劉巖娄昆,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佩微,死亡現(xiàn)場離奇詭異,居然都是意外死亡萌焰,警方通過查閱死者的電腦和手機(jī)哺眯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扒俯,“玉大人奶卓,你說我怎么就攤上這事『承” “怎么了夺姑?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掌猛。 經(jīng)常有香客問我盏浙,道長,這世上最難降的妖魔是什么荔茬? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任废膘,我火速辦了婚禮,結(jié)果婚禮上慕蔚,老公的妹妹穿的比我還像新娘丐黄。我一直安慰自己,他們只是感情好坊萝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著许起,像睡著了一般十偶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上园细,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天惦积,我揣著相機(jī)與錄音,去河邊找鬼猛频。 笑死狮崩,一個(gè)胖子當(dāng)著我的面吹牛蛛勉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睦柴,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诽凌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坦敌?” 一聲冷哼從身側(cè)響起侣诵,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狱窘,沒想到半個(gè)月后杜顺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘸炸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年躬络,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搭儒。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡穷当,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仗嗦,到底是詐尸還是另有隱情膘滨,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布稀拐,位于F島的核電站火邓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏德撬。R本人自食惡果不足惜铲咨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜓洪。 院中可真熱鬧纤勒,春花似錦、人聲如沸隆檀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恐仑。三九已至泉坐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裳仆,已是汗流浹背腕让。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歧斟,地道東北人纯丸。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓偏形,卻偏偏與公主長得像,于是被迫代替她去往敵國和親觉鼻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俊扭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容