摘要:背景訂票網站韻動株洲游泳館訂票網站訂票規則用戶當天,預約第二日免費游泳公益券領取資格,每位用戶每天只能預訂一張如有余票當天也可預訂。
前言
暑假閑來無事,每天上午的寶貴時間想去游泳,減減肚子,練練耐力,正好我們那個地方游泳館上午提供免費的票,但是,需要前一天早上七點開始預定第二天上午的免費游泳票。往年暑假,我是每天早上六點五十五準時起床,眼睛半睜不睜的等著七點一到,立馬搶票!搶完一臉解脫地癱倒在床上繼續睡覺。簡直就是煎熬啊,我在學校都沒起這么早過。 今年暑假,我實在是不想再早起了,考慮到訂票網站的訂票流程非常簡易,是否能寫一個腳本代替我每天早上完成訂票任務呢。答案是肯定的。最后我大概雖然其實用到的方法很簡單,但是既然是在生活中難得遇到的實際問題,我也做一個分享。之前我是沒有任何刷票、爬蟲經歷的。(本人專注數據挖掘) 技術改變生活,本篇博客的目的僅僅是分享并記錄一下用互聯網方法解決懶人在生活中的實際問題。背景
訂票網站:韻動株洲游泳館訂票網站
訂票規則:用戶當天7:00—22:00,預約第二日免費游泳公益券領取資格,每位用戶每天只能預訂一張(如有余票當天也可預訂)。
游泳館概況:(嘿嘿,我大株洲就是厲害)
注意:本腳本只實現簡單的訂票功能,因為該網站無需驗證碼(很多外行的朋友,雖然我也是外行,都問我能不能幫忙去12306搶票。。。)
自動登錄功能(無驗證碼!)
自動選擇預定場地、時間等信息,并提交表單
支持多賬號同時進行刷票任務
定時任務
郵件提醒搶票結果
工具模塊python
splinter
shell
crontab或plist
流程分析直接進入游泳館預訂界面(還有很多其他的運動項目可以預約哦,羽毛球、室內足球...真想給株洲政府點個贊)
點擊右上角登錄按鈕進入登錄頁面
輸入手機賬號和密碼,點擊登錄按鈕進入登錄狀態,此時頁面會跳轉到預訂界面
選擇好預定日期、預定時間,點擊確認預訂按鈕確認預訂
確認對話框點擊確認,完成所有預訂過程(非預訂時間或者預定完了所以這里顯示"undefined")
以上就是整個預定流程,很簡單吧!正是這么簡單,讓我萌生了花點時間寫個腳本來代替我訂票的邪惡想法!
下載并安裝splinter
下載并安裝chrome Web驅動
python 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退出條件),為了保險,重啟chrome,繼續預訂操作,十次操作后,退出預訂程序
時間選擇:獲取明天日期,選擇預訂明天的游泳票
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 timeCondition(h=7.0,m=1.0,s=0.0): #退出時間判斷 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): #反復訂票行為,直到時間條件達到或預訂成功退出 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/tic...
某些蛋疼的問題需要將按鈕/鏈接顯示在視野范圍內才能進行點擊操作。上文程序中諸如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信息找到這個對話框才解決的!
總結技術上來說,本文并沒有什么亮點,如果要應付12306等一系列的網站,那還有很多很麻煩的東西要研究。但是,能用技術來解決生活中的實際問題,何樂而不為呢!
其實這個定時訂票程序是一個很流程化的東西,實際上就是程序在模擬人的各種行為,所以在coding前一定要好好測試網站訂票流程,把握訂票的規律。
有和同學交流,如果能catch到預定的消息格式,那豈不是更加簡便了!嗯,我覺得很有道理,不過沒有作嘗試,我對真正的那些刷票軟件也非常感興趣,但是現在還沒有時間去研究,也歡迎大牛指點!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40807.html
摘要:數據掌握著企業的命脈,而云服務器承載著企業的數據和業務。只有超高可靠的云服務器,才能夠為企業的業務保駕護航。到底這三大法寶為何物,請見下圖詳解作為華為自辦的面向產業的全球性年度旗艦大會,將于年月日日在上海隆重舉行。數據掌握著企業的命脈,而云服務器承載著企業的數據和業務。只有超高可靠的云服務器,才能夠為企業的業務保駕護航。華為云提供超高可靠云服務器套餐,成為企業上云,實現數據高效運營的三大法寶...
摘要:第一課阿里云相關概念深化學習云服務器,簡稱是一種簡單高效處理能力可彈性伸縮的計算服務,幫助您快速構建更穩定安全的應用,提升運維效率,降低成本,使您更專注于核心業務創新。第一課:阿里云相關概念深化學習 ECS 云服務器(Elastic Compute Service,簡稱 ECS)是一種簡單高效、處理能力可彈性伸縮的計算服務,幫助您快速構建更穩定、安全的應用,提升運維效率,降低 IT 成本,使...
摘要:尤其,對于組件化起了非常大的作用。今天就簡單介紹一下我的一個懶人組件百度地圖。后面詳細介紹該對象參數字符串,是你在百度開放平臺申請的,沒有這個,你的地圖顯示不出來的表達式,用來控制離線后的友好支持,后面詳細介紹各參數。 前言 AngularJS作為一個成功的框架,營造出了完備的生態系統。尤其Directive,對于組件化起了非常大的作用。很多時候,如我這般懶人,網上搜一搜,就找到一個合...
閱讀 3233·2021-11-18 10:02
閱讀 1936·2021-09-22 10:54
閱讀 2989·2019-08-30 15:43
閱讀 2576·2019-08-30 13:22
閱讀 1575·2019-08-29 13:57
閱讀 1041·2019-08-29 13:27
閱讀 731·2019-08-26 14:05
閱讀 2512·2019-08-26 13:30