实时车票查询及登陆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.作者的话

最后,您如果觉得本公众号对您有帮助,欢迎您多多支持,转发,谢谢!