利用Python搶票,攻破12306的最後一道防線

利用Python搶票,攻破12306的最後一道防線

為了方面和節約時間,本次使用的python編譯器和直譯器分別為pycharm,python3.6.1RC

逢年過節
有一個神奇的網站
你一定不陌生
“12306”
是不是總搶不到票啊
是不是觀察著餘票最新動態
告訴你一個新技能
賊666
12306自動搶票
前方高能,請繫好好全帶~~

首先在買票前我們需要先確認是否有票,那麼進行正常的查票,開啟12306查票網站輸入出發地和目的地進行搜尋。
1

那麼一般在看到這個頁面的時候我們能想到的獲取車次及相關資訊的方式是什麼呢?對於零基礎的同學而言第一時間就會想到在原始碼裡面找,但這裡事實上原始碼裡面根本沒有相關內容,因為該請求是採用的js中ajax非同步請求的方式動態載入的,並不包含在原始碼裡面,所以我們只能夠通過抓包的方式來檢視瀏覽器與伺服器的資料互動情況,我用的是谷歌瀏覽器所以開啟開發者工具的快捷鍵是
F12(crtl alt i),此時只要是瀏覽器和伺服器發生資料互動都會在下面列表框顯示出來,我們再次點選查詢按鈕。
2
3
然後我們點選查詢按鈕以後瀏覽器向伺服器發起了兩次請求,那麼我們來通過返回值分析下那個請求才是真正獲取到車次相關資料的請求,以便我們用Python來模擬瀏覽器操作。

第一次請求:

第一次
很明顯第一次請求返回的值沒有我們需要的車次資訊。

第二次請求:

第二次

第二次請求裡面看到了很多資料,雖然我們暫時還沒看到車次資訊,但是我們發現它有個特性,就是有個列表的值裡面有6個元素,而剛好我們搜尋出來的從西安到達州的車輛也是6條資料,所以這兩者肯定有一定關係,那麼我們先用Python來獲取到這些資料再進行下一步分析。

# -*- coding: utf-8 -*-
import urllib2
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
def getList():
req = urllib2.Request('https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-07-10&leftTicketDTO.from_station=CDW&leftTicketDTO.to_station=CSQ&purpose_codes=ADULT')
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36')
html = urllib2.urlopen(req).read()
return html
print getList()

首先定義一個函式來獲取車次列表資訊。
從抓包資料中獲取到該請求的。
url:https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-07-10&leftTicketDTO.from_station=CDW&leftTicketDTO.to_station=CSQ&purpose_codes=ADULT

為了防止被12306檢測到遮蔽我們的請求那麼我們可以簡單的增加個頭資訊來模擬瀏覽器的請求。
url:https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-07-10&leftTicketDTO.from_station=CDW&leftTicketDTO.to_station=CSQ&purpose_codes=ADULT
為了防止被12306檢測到遮蔽我們的請求那麼我們可以簡單的增加個頭資訊來模擬瀏覽器的請求。

req.add_header(‘User-Agent’,’Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36’)

其中的
ssl._create_default_https_context =ssl._create_unverified_context
是因為12306採用的是https協議,而ssl證書是它自己做的並沒有得到瀏覽器的認可,所以Python預設是不會請求不受信任的證書的網站的,我們可以通過這行程式碼來關閉掉證書的驗證。
那麼我們先來看看能不能正常獲取到我們想要的資訊。
7

事實證明我們的操作沒有問題,接下來先拿到包含有6條資料的這個列表再說。
返回的資料是json格式,但是Python標準資料型別中沒有json這個型別,所以對於Python而言它就是個字串,如果要非常方便的操作這個json我們就可以藉助Python中的json這個包來把json這個字串變成dict型別,然後通過dict的鍵值對操作方法把列表取出來並進行返回。

# -*- coding: utf-8 -*-
import urllib2
import ssl
import json
ssl._create_default_https_context = ssl._create_unverified_context
def getList():
req = urllib2.Request('https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-07-10&leftTicketDTO.from_station=CDW&leftTicketDTO.to_station=CSQ&purpose_codes=ADULT')
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36')
html = urllib2.urlopen(req).read()
dict = json.loads(html)
result = dict['data']['result']
return result

最終返回的是一個list資料,我們先把這個資料for出來再看看每一條資料都有些什麼東西。

for i in getList():
print i
for出來之後我們先來看看第一條資料是什麼樣的:
|預定|76000G131805|G1318|ICW|IZQ|ICW|CWQ|07:54|18:54|11:00|N|UHESFcaIDeX22Z0zWfqttDuZXJFuWPdIa148i6TNk5spIqfp|20170710|3|W2|01|16|0|0|||||||||||無|無|無||O0M090|OM9
其實我們稍微留一下就會發現裡面有包含G1318,07:54,18:54,無這樣的車次資訊的,只不過看起來比較亂,但是他們都有一個特點,每個資料都是由|這個符號分開的,所以我們可以通過用|分割看看能發現什麼呢?
for i in getList():
for n in i.split('|'):
print n
break

8

可以看到所有的值都列印出來了,我們再在前面加上一個序號就能清楚到看到每個序號所對應的值到底是什麼了,比如有輛火車硬座還剩3張票,軟臥還剩8張票,那我們就檢視哪個序號對應的值是3哪個序號對應的值是8就搞清楚了哪個序號是代表什麼座次或者其他引數了。

c = 0
for i in getList():
for n in i.split('|'):
print '[%s] %s' %(c,n)
c  = 1
c = 0
break
#索引3=車次
#索引8=出發時間
#索引9=到達時間

9

到了這裡不知道同學們有沒有發現一個問題,就是我用的這個函式只能夠獲取到從長沙到成都的資料,而別人不一定是買這個方向的火車,所以我們還得搞清楚請求的url當中的出發站和到達站的值是怎麼來的?
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-07-10&leftTicketDTO.from_station=CDW&leftTicketDTO.to_station=CSQ&purpose_codes=ADULT
先找到出發站和到達站的引數分別是:

  • leftTicketDTO.from_station=CDW
  • leftTicketDTO.to_station=CSQ

然而通過查詢和分析我並沒有發現這兩個引數有規律,那麼也就是說這兩個值是在之前的請求裡面就已經獲取到了的,通過檢查網頁原始碼沒有找到,那麼又只能通過抓包的方式來找。
在抓包過程中找到了一個包的返回值是附帶有各城市的代號的,url如下:
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9018
10

那麼我們把這裡面的城市資料複製出來單獨新建一個cons.py的檔案儲存起來。
11

然後我們通過把引數做成通過輸入出發城市和到達城市就可以直接在這個資料裡面匹配到相應的城市代號,程式碼如下:

station = {}
for i in cons.station_names.split('@'):
if i:
tmp = i.split('|')
station[tmp[1]] = tmp[2]
\#print station
train_date = raw_input('請輸入出發時間')
from_station = station[raw_input('請輸入出發城市')]
to_station = station[raw_input('請輸入到達城市')]

到這裡就已經能夠通過輸入“時間,城市”獲取相應的車次資訊了
12

那麼我們再進行一些簡單的判斷,就能實現檢查相應的時間,地點,車次是否有餘票了。同時再結合登入,購票等流程,通過自動判斷是否有票,如果無票就繼續重新整理,直到有票之後自動登入下單後通過簡訊或者電話等方式全自動聯絡購票人手機就可以了。
13