1. 程式人生 > >轉載--12306刷票記

轉載--12306刷票記

轉載自:http://www.360doc.com/content/13/0122/17/453497_261790962.shtml

我也記不清啥時候動了寫bot刷票這個念頭的。原因很簡單,我一直認為作為一個以程式碼謀生的不合格程式設計師,只有把生產工具用好,才能增加自己存在的價值。

首先說明一下主要開發環境:Windows 7,PHP 5.3,php_curl。

翻到了 第一條關於刷票的微博,附了圖

很不低調地炫耀。

要刷票,首先自然得熟悉目標系統,所謂踩點。firefox+firebug,抓了一個標準流程的請求:登入、查票、訂票。確認訂單一開始沒敢點,怕會有什麼影響,後來去註冊了幾個測試號,然後嘗試了確認訂單的操作。流程本身不復雜,但是提交引數有點太多,一步一步來。

回到圖1,登入,其實核心在驗證碼。

1 驗證碼識別
登入的驗證碼處理起來很簡單,圖在 https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=lrand,提供一個示例
這個驗證碼結構一直沒變,字型比較規則,無變形,直接就想到了tesseract,上一次用這個的時候是2010年12月初為了刷順豐快遞的訂單追蹤。
流程很簡單:
1 下載圖片;
2 轉tiff(使用了ImageMagick的convert);
3 tesseract識別;
4 讀取識別檔案;
5 簡單判斷識別輸出是否合法;
6 拼登入請求。

程式碼裡留了個 decaptcha_valid()的方法,不過只判斷了是不是4位數字+字母。
寫完第一版程式碼後,清理了一些個人資訊,把程式碼發給了

@也雲,不過當時畢竟比較早,他沒怎麼看,但是後期驗證碼識別這塊提供了一些改進,包括:
1 replace識別輸出裡的空字元,因為由於字元間距不確定,tesseract偶爾會識別出空格;
2 訓練tesseract,優化識別效果;
3 這次tesseract用的是v3,已經支援多種圖片格式,而我還是按上次使用的經驗convert jpeg 2 tiff,這次直接省去這個過程;
4 最關鍵的,登入驗證碼在登入請求提交後並不會刪除上次訪問驗證碼圖片生成的session欄位,也就是說,只需要識別一次驗證碼,後續只需要暴力提交即可

按4調整了驗證碼識別方法,增加了人肉輸入的支援。使用system(‘out.jpg’)直接利用Windows 7 CMD的高階功能,同時發現了Win7自帶的圖片檢視器system執行後程序可以繼續往下走而不必關掉圖片檢視器,這樣可以alt+tab切換cmd和圖片檢視器,確認人肉識別的輸入是否正確。

上述邏輯在1月19號之前也同樣適用於確認訂單的驗證碼,那天早起發現這塊兒驗證碼需要每次請求了,幸好之前人肉識別的改造增加了配置開關,很輕鬆地就完成了改動(雖然19號刷票無一成功,但是20號回家客車上刷5張票輕鬆到手)。

2 查票
查票的過程其實沒啥特別可以說的,簡單拼個請求,查票就是了。不過返回值是需要處理的,處理的依據,從頁面看,就是查票結果輸出裡的“預訂”按鈕。

預訂的引數就是頁面輸出裡的getSelected()這個js呼叫的引數,最初的結構我從另外一條微博裡找到了例子。
查票輸出
正如頁面上所示,結構是“車次號#歷時(分鐘單位)#發車時間#某個ID#始發站編號#目標站編號”,這個引數由頁面解析後帶入實際的預訂請求。
不過後來某一天,這個引數變了,一個輸出示例是“Z67#11:26#20:06#2400000Z6705#BXP#NCG#07:32#北京西#南昌#10175000003030800001404860000160895000001017503001”。同樣按上邊的解釋,結構是“車次號#歷時#發車時間#某個ID#始發站編號#目標站編號#到站時間#某個帶入ypInfoDetail欄位的引數”,@也雲同學研究了一下最後這個欄位,認定是“餘票資訊”的意思,對我們沒有特別的意義,可無視(後續也證實了)。

這裡可能有用的資訊包括上述的“某個ID”。而查票的時候也需要輸出裡的始發站和目標站編號,這個編號可以從 這個地址裡找到 。

3 訂票 && 提交訂單
訂票使用了查票的輸出引數,提交url是 https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=submutOrderRequest。明顯有個Typo,而且這裡名字是“提交訂單”。不管那麼多,說技術細節。

這個使用查票引數構造了一個POST請求,如果提交成功,伺服器端會發一個302,重定向到一個新的頁面 https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=init。

流程很簡單,curl實現只需要開 CURLOPT_FOLLOWLOCATION 即可。但是這個地方卡了我很久,直到12月初的某天才查出來為什麼每次提交在這裡總是出現 HTTP 500,雖然還是不知道原因,但是補上了幾個http頭,一切ok了(具體看程式碼)。

訂票輸出的頁面,會有formtoken,引數名是 org.apache.struts.taglib.html.TOKEN。有點Web程式設計經驗就應該能知道是幹啥的。不多說,引數請求必須帶上,所以正則匹配,無技術含量。
確認訂單頁只是拼引數,補上驗證碼識別就ok,無技術含量。

第一個細節,這個頁面是填身份證的頁面,入口有兩個,訂票 和 確認訂單失敗(後期確認訂單失敗不再直接回到該頁面,不過驗證碼錯誤的應該還是會回來),所以確認訂單的錯誤處理是可以考慮增加的。這個一開始程式碼就加上了,不過後來@也雲改的lite版把這個流程改為 提交訂單 + 確認訂單,兩步走,確認失敗直接重訂票。

第二個細節,驗證碼URL不一樣了,https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=randp,不過輸出還是一樣的。

第三個細節,最關鍵的,某個晚上睡前突然意識到的問題。回到form token上,我們可以觀察到這個系統很多地方都會有form token,包括但不限於確認訂單、支付訂單、撤銷訂單等等。但是我意識到一個問題,提交訂單這個過程可能會很慢,慢到一個不能忍的狀態,尤其是高峰期。而,使用者中心的表單裡,也有form token,而且引數名完全一樣,那麼,這兩個token會是同一個session欄位嗎?
接下來做的事情是改造form token的獲取方法,改從使用者中心的頁面匹配,僅作測試。測試結果很樂觀,從使用者中心匹配到的token能作為確認訂單token,而且這個請求自然會比從提交訂單拿到的頁面匹配要快,於是匹配token的方法可以優化了。(但是16號早上,訂單系統似乎出問題了,各種慢,未支付訂單頁面刷不出來,支付訂單也失敗了,3張票扣了我1.3k RMB,沒出票,極度想粗口!!!)

說到第三細節,如果你順著讀下來,可能會覺得有疑問:不是有很多引數要從頁面匹配嗎?如果不去拿提交訂單後的頁面,匹配變數,怎麼確認訂單?
答案是:你所需要的引數從你拿到查票輸出的時候就已經足夠了。你所需要的車次相關的資訊,查票的輸出已經能滿足所有欄位的需求;你所需要的訂票人資訊,以及所需坐席,你自己是已經知道了的。而,經過觀察,查票輸出裡除了餘票資訊欄位會變,其他的都不變!!!所以,你只需要在確定要刷票的前一天晚上,做一下準備工作,查查你要刷票的車次的資訊,第二天拿著刷票便是了!!!
具體不多說,看程式碼便知。

到此,基本能說的都說了。

後邊還有一些其他的細節優化。
支付
最初的支付表單是在一個獨立的頁面上,從未完成訂單裡選擇訂單,點支付,彈出的一個新頁面匹配幾個引數再submit,輸出頁面只有一個簡單的form,body onload的時候觸發了submit操作。也就是說,只需要拿下這個頁面完成支付即可,這個最早@也雲實驗成功了,後來我有幾次支付也用這個方法完成。過了幾天,支付的表單在未完成訂單點支付後的頁面裡直接寫死了,於是新的lite版bot直接匹配了頁面表單,存了個獨立頁面。
完後,彈出IE,支付。
本來想做招行的手機支付自動化的,後來實在懶了,自己訂票的需求滿足了,沒心做這種優化。

登入
登入是件苦力活,經常就人滿了,頻率高了IP也會被封。所以登入成功一次得好好珍惜。
做法是: CURLOPT_COOKIEFILE + CURLOPT_COOKIEJAR。
指令碼執行結束後,手工改 cookie jar檔案,把expiration時間改長點。
但是由於經常性在登入完成後ctrl+c,沒法觸發__destruct()的呼叫,按@也雲的建議,改成了登入完成後觸發一次curl close操作並改expiration,執行結束後的繼續保留。

執行引數 php bot.php 0 test 2>err.txt
只保證win下可用,soff改過一個mac下可用的版本,沒找他要。
test 是 config/ 下的 test.php
0 是 test.php 裡的tickets_info欄位的索引,其實叫train_info更合適

然後為了騷擾自己,加了個vbs檔案,內容很簡單,取名messagebox.vbs,這段內容感謝@linxinsnow

' Message box Script
Set objArgs = WScript.Arguments
For I = 0 to objArgs.Count - 1
msgbox objArgs(I)
Next

寫在最後
雖然我經常在微博XY,但是一直沒公開原始碼。最終可用的程式碼也只有也雲和soff拿到了。
一直不開源或者不釋出的原因很簡單,我不希望我的程式碼成為別人系統“有壓力”的理由。我也看到了幾個網上放出來的刷票工具或者js解決方案,但是,越多人知道這個,就意味著越多人會用你的“刀”“殺”鐵道部。
或許我想的有點多。

感謝也雲同學對本年度刷票的支援。感謝soff提供的驗證碼識別優化方案,雖然效果還不如我們的。