咱們要作12306搶票而官方又沒有提供相應的接口(也不可能提供),那麼咱們就只能經過本身尋找12306的數據包和買票流程來模擬瀏覽器行爲實現自動化操做了,說直白一點就是爬蟲,接下來進入正題,前方高能,請繫好安全帶~~html
首先在買票前咱們須要先確認是否有票,那麼進行正常的查票,打開12306查票網站https://kyfw.12306.cn/otn/leftTicket/init 輸入出發地和目的地進行搜索。ajax
那麼通常在看到這個頁面的時候咱們能想到的獲取車次及相關信息的方式是什麼呢?對於零基礎的同窗而言第一時間就會想到在源代碼裏面找,但這裏事實上源代碼裏面根本沒有相關內容,由於該請求是採用的js中ajax異步請求的方式動態加載的,並不包含在源代碼裏面,因此咱們只可以經過抓包的方式來查看瀏覽器與服務器的數據交互狀況,我用的是谷歌瀏覽器因此打開開發者工具的快捷鍵是F12。json
注意選中紅線框出來的那一個選項,此時只要是瀏覽器和服務器發生數據交互都會在下面列表框顯示出來,咱們再次點擊查詢按鈕。瀏覽器
結果發現列表當中有了兩個請求,也就是說咱們點擊查詢按鈕之後瀏覽器向服務器發起了兩次請求,那麼咱們來經過返回值分析下那個請求才是真正獲取到車次相關數據的請求,以便咱們用Python來模擬瀏覽器操做。安全
第一次請求:服務器
很明顯第一次請求返回的值沒有咱們須要的車次信息。異步
第二次請求:函數
第二次請求裏面看到了不少數據,雖然咱們暫時還沒看到車次信息,可是咱們發現它有個特性,就是有個列表的值裏面有6個元素,而恰好咱們搜索出來的從長沙到成都的車輛也是6條數據,因此這二者確定有必定關係,那麼咱們先用Python來獲取到這些數據再進行下一步分析:工具
# -*- coding: utf-8 -*- importurllib2 importsslssl._create_default_https_context = ssl._create_unverified_context defgetList():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() returnhtml printgetList()網站
首先定義一個函數來獲取車次列表信息:
從抓包數據中獲取到該請求的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
zhengshu5.com |
dajinnylee.cn |
xc.xyseo.net |
xyseo.net/xuancai/ |
是由於12306採用的是https協議,而ssl證書是它本身作的並無獲得瀏覽器的承認,因此Python默認是不會請求不受信任的證書的網站的,咱們能夠經過這行代碼來關閉掉證書的驗證
那麼咱們先來看看能不能正常獲取到咱們想要的信息 :
事實證實咱們的操做沒有問題,接下來先拿到包含有6條數據的這個列表再說。
返回的數據是json格式,可是Python標準數據類型中沒有json這個類型,因此對於Python而言它就是個字符串,若是要很是方便的操做這個json咱們就能夠藉助Python中的json這個包來把json這個字符串變成dict類型,而後經過dict的鍵值對操做方法把列表取出來並進行返回。
# -*- coding: utf-8 -*-importurllib2 importssl importjsonssl._create_default_https_context = ssl._create_unverified_contextdef 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( WindowsNT10. 0; Win64; x64)AppleWebKit/ 537. 36( KHTML, like Gecko) Chrome/ 59. 0. 3071. 115Safari/ 537. 36') html = urllib2.urlopen(req).read() dict = json.loads(html) result= dict['data'][' result'] returnresult
最終返回的是一個list數據,咱們先把這個數據for出來再看看每一條數據都有些什麼東西:
foriingetList(): 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,無這樣的車次信息的,只不過看起來比較亂,可是他們都有一個特色,每一個數據都是由|這個符號分開的,因此咱們能夠經過用|分割看看能發現什麼呢?
foriingetList(): forn ini. split('|'): print n break
能夠看到全部的值都打印出來了,咱們再在前面加上一個序號就能清楚到看到每一個序號所對應的值究竟是什麼了,好比有輛火車硬座還剩3張票,軟臥還剩8張票,那咱們就查看哪一個序號對應的值是3哪一個序號對應的值是8就搞清楚了哪一個序號是表明什麼座次或者其餘參數了。
c = 0fori ingetList(): forn ini.split( '|'): print'[%s] %s'%(c,n) c += 1c = 0break#索引3=車次#索引8=出發時間#索引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= CDWleftTicketDTO.to_station=CSQ
然而經過查找和分析我並無發現這兩個參數有規律,那麼也就是說這兩個值是在以前的請求裏面就已經獲取到了的,經過檢查網頁源代碼沒有找到,那麼又只能經過抓包的方式來找。
在抓包過程當中找到了一個包的返回值是附帶有各城市的代號的,url以下:
https:/ /kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9018
那麼咱們把這裏面的城市數據複製出來單獨新建一個cons.py的文件保存起來
而後咱們經過把參數作成經過輸入出發城市和到達城市就能夠直接在這個數據裏面匹配到相應的城市代號,代碼以下:
station = {} fori incons.station_names. split( '@'): ifi: tmp = i. split( '|') station[tmp[ 1]] = tmp[ 2] #print stationtrain_date = raw_input( '請輸入出發時間')from_station = station[raw_input( '請輸入出發城市')]to_station = station[raw_input( '請輸入到達城市')]
到這裏就已經可以經過輸入時間,城市獲取相應的車次信息了 。
那麼咱們再進行一些簡單的判斷,就能實現檢查相應的時間,地點,車次是否有餘票了。
同時再結合登陸,購票等流程,經過自動判斷是否有票,若是無票就繼續刷新,直到有票以後自動登陸下單後經過短信或者電話等方式全自動聯繫購票人手機就能夠了,以下圖: