實時車票查詢及登陸CTC

  • 2019 年 10 月 6 日
  • 筆記

實時車票查詢及登陸CTC

0.說在前面

1.項目架構

2.模擬登陸

2.1 登陸分析

2.2 登陸實現

3.余票查詢

3.1 查詢分析

3.2 查詢實現

4.運行展示

5.作者的話

0.說在前面

又是一年國慶節,祝各位國慶節快樂,玩的開心!

從大學至今,唯一一個宅的國慶,讓自己多點思考,少點外出。

這幾天也沒更文,忙於之前的小遊戲pygame的開發,這方面的軟文,隨後幾日更新。

前兩天老表發了個12306軟文,忽然想起,自己的公眾號也好久沒更新爬蟲系列了,今天就開始琢磨一下,本次的爬蟲主要有兩大方面的功能。

第一】 如何登陸12306

第二】 如何做到實時車票查詢

當你們在排隊等候伺服器響應的時候,我已經買下票了;

當你們在搶購最後一張車票的時候,已經沒了;

當你們在等待放票的時候,我已經調整好買票方案了。

哈哈,有點難拉仇恨。。那麼沒事,學好接下來的操作,會有助於你解決車票麻煩。

車票查到了,離心中的遠方還遠?

Close To Close

1.項目架構

項目架構圖

login_spider        # 登陸類 用於12306全局登陸與管理      downloadCode    # 用於下載驗證碼      verifi_Code     # 用於驗證驗證碼是否輸入成功      main_Login      # 用於賬戶登陸      get_Tk          # 登陸不成功的uamtk獲取      tk_Auth         # uamtk驗證      Login           # 真實登陸的跳轉頁面      main            # 對上述程式碼的調用  ticker_spider      get_StationName_En  # 獲取出發站(抵達站)的字母簡寫      search_Ticket       # 余票查詢      get_StationName     # 獲取真實的中文表示的站點      print_TicketInfo    # 列印余票查詢結果

2.模擬登陸

2.1 登陸分析

驗證碼

分為以下幾種情況:

第一種情況:驗證碼失敗,會發現如下圖校驗結果,並且沒有login的相關資訊。

驗證碼失敗圖

第二種情況:用戶名或密碼錯誤,驗證碼正確,此時會出現login的資訊

用戶名登錄請求圖

用戶名錯誤圖

第三種情況:登陸成功

登陸成功圖

綜上對於登陸的流程為,先下載驗證碼,手動驗證,然後傳入正確的用戶名與密碼,再進行登陸。

在登陸之前,12306會對你的驗證碼做校驗,如果失敗了,則直接不用管你的用戶名與密碼,所以先對驗證碼進行手動驗證。然後再去用賬戶名與密碼進行POST提交。

就這麼簡單?

當然不是,在你登陸後,最後會發現並未成功,搜索你的姓名並未發現,那麼就得繼續抓包。最後發現頁面上需要uamtk驗證,然後才可以進行正常的爬取操作。

接下來我們進入實戰環節。

2.2 登陸實現

上述的頁面訪問較多,未了更方便的操作,本次採用requests裡面的Session統一進行管理Cookie!

封裝

import requests  class login_spider(object):      def __init__(self):          headers = {              'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'          }          sess = requests.Session()          self.headers=headers          self.sess = sess

驗證碼識別

當我們把圖中的八個驗證碼進行點擊的時候,會出現如下的坐標位置,那麼我們只要將上述的坐標放進list中,當出現是哪個數據時候,就輸入相應位置即可。輸入的範圍未(0~7) ,index從0開始!並且當驗證碼中有多個滿足條件時候,輸入一定要連著輸入。

驗證碼全選圖

def verifi_Code(self):      verifi_url = 'https://kyfw.12306.cn/passport/captcha/captcha-check'      verifi_axis = ['36,46','109,44','181,47','254,44','33,112','105,116','186,116','253,115']      axis = input("請輸入驗證碼坐標>> ")      verifi_list = []      for point in axis:          verifi_list.append(verifi_axis[int(point)])      axis_pos = ','.join(verifi_list)      post_data = {          "answer": axis_pos,          "login_site": "E",          "rand": "sjrand",      }      res = self.sess.post(url=verifi_url,headers=self.headers,data=post_data)      res_json = res.json()      if not res_json['result_code']=='4':          print("驗證失敗")          return False      print(res_json)      return True

登陸

def main_Login(self):      login_url = 'https://kyfw.12306.cn/passport/web/login'      data_post = {          "username":"輸入您的用戶名",          "password": "輸入您的密碼",          "appid": "otn"      }      res = self.sess.post(login_url, headers=self.headers, data=data_post)      print(res.json())

登陸後驗證

def get_Tk(self):      url_uamtk = 'https://kyfw.12306.cn/passport/web/auth/uamtk'      data_uamtk = {"appid":"otn"}      res = self.sess.post(url_uamtk,headers=self.headers,data=data_uamtk)      print(res)      res_json = res.json()      data_verifi = {"tk":res_json["newapptk"]}      return data_verifi  def tk_Auth(self):      uamauthclient_url = "https://kyfw.12306.cn/otn/uamauthclient"      res = self.sess.post(uamauthclient_url,headers=self.headers,data=self.get_Tk())      print(res)

3.余票查詢

3.1 查詢分析

余票查詢可以使用之前的Session管理的cookie用賬戶許可權去抓取,也可以不用登陸就可以!

難點

  • 查詢的結果在哪
  • 結果如何處理
  • 查詢途中的站點名字與字母簡寫如何處理

對於第一個難點,直接打開f12檢查即可,會發現,如下圖所示結果:

余票查詢圖

上圖中的result裡面的就是余票查詢結果!

但是問題來了,查詢出來的數據是這麼的亂,那麼怎麼處理呢?到底哪一塊表示始發站,硬座,軟卧等?

這個處理是直接打開12306隨機去是個查詢結果,然後到了這個頁面後,去搜索相應的車次,然後對應的一行就是顯示介面的數據,最後發現各條數據之前從|預訂|開始後面所有的數據是很規則的,那麼前面的所有東西我直接通過正則匹配以|預訂|分開,然後得到一個list,取index=1的數據即為我們需要的完整的數據,然後將其與頁面數據進行匹配,最後就可以鎖定哪個index表示硬座,軟卧等。

在前面去請求數據的時候,會發現請求的數據並不是你所輸入的中文,比如要查詢重慶到成都,那麼按照我們正常思路是直接用原字元串重慶與成都訪問,但是實際不是,如下圖:

真實請求圖

看到了沒,重慶對應CQW,成都對應CDW,中文又是怎麼變為這些英文字母的呢?

針對這個問題,想必又是js作祟,於是打開js篩選,找到了有關station_name的相關js,如下兩圖:

js請求圖

js內容圖

發現了js裡面中文後的下一個便是請求的英文字元串,那麼我可以不費吹灰之力便可以拿到頁面的js,然後先將var='去掉,並將js的末尾字元去掉,保留中間需要的,然後通過split對字元串分割成list,直接找到list當中請求的中文站點名字對應的index,然後加1獲取真實的英文字元,然後再去請求相應的url即可!

js頭圖

js尾圖

3.2 查詢實現

封裝

import re  from prettytable import PrettyTable  from login_spider import login_spider  class ticker_Spider(object):      def __init__(self):          headers = {              'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'          }          search_url = 'https://kyfw.12306.cn/otn/index/init'          ls = login_spider()          self.headers = headers          self.ls = ls          self.search_url = search_url

真實的站點名字

def get_StationName_En(self,name):      # 此處可以不需要session操作即可      url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9069'      res = self.ls.sess.get(url,headers=self.headers).text      # print(res)      with open('name.txt', 'w', encoding='utf-8') as f:          # 去掉js的開頭與結尾          res = res.replace('var station_names =', '').replace(''', '').replace(';', '')          f.write(res)      with open('name.txt', 'r', encoding='utf-8') as f:          line = f.read()          # print(line)          sn_list = line.split('|')          # print(sn_list)          # print(sn_list.index(name))          name_index = sn_list.index(name) + 1          return sn_list[name_index]

余票查詢

相應位置對應的數據資訊表

    '''      1-車次  checi      2/4-始發站 from_station      3/5-終點站 to_station      6-出發時間 from_time      7-到達時間 to_time      8-歷時 total_time      11-出發日期 from_datetime      -16-高級軟卧 high_soft      -14-軟卧 common_soft      -11-無座 no_seat      -4-動卧 move_down      -5-商務座(特等座) special_seat      -6-一等座 first_seat      -7-二等座 second_seat      -9-硬卧 hard_seat      '''
def search_Ticket(self):      self.ls.main()      # 'leftTicketDTO.train_date=2018-10-04&leftTicketDTO.from_station=CQW&leftTicketDTO.to_station=SHH&purpose_codes=ADULT'      print("時間輸入格式為>> 2018-10-02")      raw_from_station = input("請輸入出發地>> ")      raw_to_station = input("請輸入目的地>> ")      train_date = input("請輸入出發日>> ")      # back_train_date = input("請輸入返程日>> ")      base_url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?'      from_station_En = self.get_StationName_En(raw_from_station)      to_station_En = self.get_StationName_En(raw_to_station)        url = base_url + 'leftTicketDTO.train_date=' + train_date + '&leftTicketDTO.from_station=' + from_station_En + '&leftTicketDTO.to_station=' + to_station_En + '&purpose_codes=ADULT'      res = self.ls.sess.get(url,headers=self.headers).json()      tick_res = res['data']['result']      print(len(tick_res))      search_res = len(tick_res)      checi = []      from_station = []      to_station = []      from_time = []      to_time = []      total_time = []      from_datetime = []      no_seat = []      high_soft = []      common_soft = []      special_seat = []      move_down = []      first_seat = []      second_seat = []      hard_seat = []      for each in tick_res:          print("-----")          # print(i)          # a = i.find('預訂')            need_data = re.split(r'|預訂|', each)[1]          need_data = need_data.split('|')          print(need_data)          checi.append(need_data[1])          from_station.append(self.get_StationName(need_data[2]))          to_station.append(self.get_StationName(need_data[3]))          from_time.append(need_data[6])          to_time.append(need_data[7])          total_time.append(need_data[8])          from_datetime.append(need_data[11])          high_soft.append(need_data[-16])          common_soft.append(need_data[-14])          no_seat.append(need_data[-11])          move_down.append(need_data[-4])          special_seat.append(need_data[-5])          first_seat.append(need_data[-6])          second_seat.append(need_data[-7])          hard_seat.append(need_data[-9])      return search_res,raw_from_station,raw_to_station,checi,from_station,to_station,from_time,to_time,total_time,from_datetime,high_soft,common_soft,no_seat,move_down,special_seat,second_seat,first_seat,hard_seat

余票展示

def get_StationName(self,name):      with open('name.txt', 'r', encoding='utf-8') as f:          line = f.read()          # print(line)          sn_list = line.split('|')          # print(sn_list)          # print(sn_list.index(name))          name_index = sn_list.index(name) - 1          return sn_list[name_index]    def print_TicketInfo(self):      search_res, raw_from_station, raw_to_station,checi, from_station, to_station, from_time, to_time, total_time, from_datetime, high_soft, common_soft, no_seat, move_down, special_seat, second_seat, first_seat, hard_seat = self.search_Ticket()        pt = PrettyTable()      print("---------從" + str(raw_from_station) + '到' + str(raw_to_station) + '共' + str(search_res) + '個車次'+ '---------')      pt.add_column('車次', checi)      pt.add_column('始發站', from_station)      pt.add_column('終點站', to_station)      pt.add_column('出發時間', from_time)      pt.add_column('到達時間', to_time)      pt.add_column('歷時', total_time)      pt.add_column('出發日期', from_time)      pt.add_column('高級軟卧', high_soft)      pt.add_column('軟卧', common_soft)      pt.add_column('無座', no_seat)      pt.add_column('動卧', move_down)      pt.add_column('商務座', special_seat)      pt.add_column('一等座', first_seat)      pt.add_column('二等座', second_seat)      pt.add_column('硬卧', hard_seat)      return pt

4.運行展示

驗證登陸圖

余票結果圖

余票官網圖

驗證上述查詢結果,對比之後,正確!

5.作者的話

最後,您如果覺得本公眾號對您有幫助,歡迎您多多支援,轉發,謝謝!