首页 > 技术文章 > Python-爬虫-12306购票业务实现

ygzhaof 2019-01-09 10:39 原文

待续。。。

  1 import requests
  2 from requests import  Request,Session
  3 import requests.cookies
  4 import urllib.parse as parse
  5 
  6 import re
  7 import random
  8 import lxml
  9 import os
 10 
 11 class TicketObject():
 12 
 13     def __init__(self):
 14         #这里没有写登录,则通过网页登录后,根据车次,日期查询后的cookie信息粘贴这里,暂时的(里面包含车次查询条件信息字段)
 15         self.Cookies='JSESSIONID=19EED0F307740A8B8A80B69917547E15; tk=wYsN5viUHaif1cAWJNkDnb6bdkTp5QdD4OuMwwafz2z0; RAIL_EXPIRATION=1547346452769; RAIL_DEVICEID=TamgucQvy9ZbuKgQQPahgPaiAaGBPANQhca1rft3YUSmWw6ra-J47njLWFqEm1TwFYZVWNNA6pIYyhPySCECMb7qjfkBgXfCM3C7a9yNAH9juhwJvKasPdWs3_hZ2N2v710gIoY2XYT7bvUwogWBEH1Z8sYNT7wM; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u54C8%u5C14%u6EE8%2CHBB; _jc_save_fromDate=2019-01-31; _jc_save_toDate=2019-01-09; _jc_save_wfdc_flag=dc; BIGipServerotn=468713994.50210.0000; BIGipServerpassport=1005060362.50215.0000; route=9036359bb8a8a461c164a04f8f50b252; BIGipServerpool_passport=183304714.50215.0000; current_captcha_type=Z'
 16         self.trainInfoJSON={}#查询车次json列表
 17         self.trainCodeItems=[]#车次[车次,车次...]
 18         self.trainSecretStrDict={}#查询是每个车次都有一个这个字符串,在订单提交时需要提交该字段值 {车次:secretStr,....}
 19         self.trainStartDict={}#每个车次起始站
 20         self.trainEndDict={}  #每个车次终点站
 21         self.trainStartTime={}#发车时间
 22         self.trainEndTime={}  #到站时间
 23         self.trainDuration={} #历时多久
 24         self.trainSeatTotal_swz={}#商务座数量
 25         self.trainSeatTotal_ydz={}#一等座数量
 26         self.trainSeatTotal_edz={}#二等座数量
 27         self.trainSeatTotal_gjrw = {}  # 高级软卧座数量
 28         self.trainSeatTotal_rw = {}    # 软卧一等座数量
 29         self.trainSeatTotal_dw= {}    # 动卧数量
 30         self.trainSeatTotal_rz= {}    # 软座数量
 31         self.trainSeatTotal_yz= {}   # 硬座座数量
 32         self.trainSeatTotal_wz= {}   # 无座数量
 33 
 34         self.repeatSubmitToken=""
 35         self.keyIsChange=""
 36         self.leftTicketStr=""
 37 
 38         #查询车票条件
 39         self.date='2019-01-31'#查询日期
 40         self.station_1='BJP'#起始站
 41         self.station_2='HBB'#终点站
 42         self.trainCode='K4011'#预定车次
 43 
 44         # 封装cookie[下面封装了也暂时没用上,因为上面使用了cookies字符串]
 45         self.cookiesJar = requests.cookies.RequestsCookieJar()
 46         #联系人信息json
 47         self.passengersDict=None
 48 
 49 
 50         #登陆后查询车次信息列表url,即登陆后,点击查询按钮发送的请求URL[查询车次列表信息]
 51         self.queryURL="https://kyfw.12306.cn/otn/leftTicket/queryZ"
 52         #点击预定,则会有两个URL,1、检查用户是否在线 2、提交订单 即下面两个url请求
 53         # (点击预定),先检查用户是否在线)检查用户是否在线URL
 54         self.checkUserURL = 'https://kyfw.12306.cn/otn/login/checkUser'
 55         #(点击预定)https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest
 56         self.submitOrderRequestURL="https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest"
 57         #点击预定按钮访问的界面,获取token使用,注意必须是上面submitOrderRequestURL提交成功后,才可以通过下面url获取token
 58         self.initDcURL = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
 59         #点击预定查询联系人列表,待用户勾选
 60         self.passengerDTOURL='https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
 61         #点击提交,确认订单信息
 62         self.chekOrderURL='https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
 63         #将购票订单加入队列URL
 64         self.queueURL='https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
 65         #等待订单结果URL
 66         self.orderResultURL='https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue'
 67 
 68         self.session = Session()
 69 
 70         #异步请求headers
 71         self.headers={
 72            "Cookie":self.Cookies,
 73              "User - Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0",
 74             "Host":"kyfw.12306.cn",
 75             "X-Requested-With":"XMLHttpRequest"
 76         }
 77 
 78     #一、根据始发站和目的站点,查询日期 获取车次信息
 79     def sendRequest(self):
 80 
 81         req = Request('GET', self.queryURL,   headers=self.headers, params={'leftTicketDTO.train_date':self.date,
 82               "leftTicketDTO.from_station": self.station_1,"leftTicketDTO.to_station":self.station_2,"purpose_codes":"ADULT"})
 83         prep = self.session.prepare_request(req)
 84         res = self.session.send(prep)
 85         print(res.status_code)
 86         print(res.json())
 87         self.trainInfoJSON=res.json()
 88         for a in res.json()['data']['result']:
 89             code=a.split("|")[3]#车次
 90             self.trainCodeItems.append(code)#封装车次
 91             self.trainSecretStrDict[code]=a.split('|')[0]#封装车次对应的secretStr,在点击预定提交订单使用;注意每次查询同一个车次的secretStr是不同的,每次都在变
 92 
 93             self.trainSeatTotal_swz[code] = a.split('|')[32]  # 商务座数量
 94             self.trainSeatTotal_ydz[code] = a.split('|')[31]  # 一等座数量
 95             self.trainSeatTotal_edz[code] = a.split('|')[30]  # 二等座数量
 96             self.trainSeatTotal_gjrw[code] = a.split('|')[21] # 高级软卧座数量
 97             self.trainSeatTotal_rw[code] = a.split('|')[25]   # 软卧一等座数量
 98             self.trainSeatTotal_dw[code] = a.split('|')[33]   # 动卧数量
 99             self.trainSeatTotal_rz[code] = a.split('|')[24]   # 软座数量
100             self.trainSeatTotal_yz[code] = a.split('|')[29]   # 硬座座数量
101             self.trainSeatTotal_wz[code] = a.split('|')[26]   # 无座数量
102             self.trainStartTime[code] = a.split('|')[8]  # 发车时间
103             self.trainEndTime[code] =  a.split('|')[9]  # 到站时间
104             self.trainDuration[code] =  a.split('|')[10]  # 历时多久
105 
106         #封装cookies
107         self.cookiesJar = requests.cookies.RequestsCookieJar()
108         for items in self.Cookies.split(";"):
109             k, v = items.split('=', 1)  # =号分割,分割成2个字段
110             self.cookiesJar.set(k, v)
111             #print(k,v)
112 
113         print('车次列表:',self.trainCodeItems)#获取车次
114         print('车次secretStr:',self.trainSecretStrDict)#该字段存在,表示该车次可以提交订单
115         print(self.trainSeatTotal_swz)
116         print(self.trainSeatTotal_ydz)
117         print(self.trainSeatTotal_edz)
118         print(self.trainSeatTotal_wz)
119         print("开始时间:",self.trainStartTime)
120         print("结束时间:",self.trainEndTime)
121         print("历时:",self.trainDuration)
122         print(">>>查询车次信息成功")
123 
124 
125     #二、检查 用户是否在线;点击预定,走二和三两个请求
126     def check_user(self):
127         data = {"_json_att": ""}
128         try:
129             response = self.session.post(url=self.checkUserURL, data=data, headers=self.headers,
130                                          verify=False)
131             print(response.status_code)
132             print(response.json())
133             dic = response.json()
134             if dic['data']['flag']:
135                 print(">>>用户检查是否在线>>>用户在线验证成功")
136                 return True
137             else:
138                 print('>>>用户检查是否在线>>>检查到用户不在线,请重新登陆')
139                 return False
140         except   BaseException:
141             print(">>>用户检查是否在线>>>网络异常!")
142             return False
143 
144 
145     #三、点击预定,走二和三两个请求;注意:该请求cookie中一定要包含车次信息
146     def submit_order(self):
147         #print(self.trainSecretStrDict['K4011'])
148         #print(self.trainStartTime['K4011'])
149         #print(self.trainEndTime['K4011'])
150         data = {"secretStr":parse.unquote(self.trainSecretStrDict[self.trainCode]),#注意这里一定要解码 parse.unquote解码,否则提交不会成功(在查询和提交时通过浏览器debug发现该字段不同,问题就在这里)
151                         "train_date": self.date,
152                         "back_train_date": self.cookiesJar.get("_jc_save_toDate"),
153                         "tour_flag": "dc",
154                         "purpose_codes": "ADULT",
155                         "query_from_station_name": '北京',#self.trainInfoJSON['data']['map'][self.station_1], #注意这里提交时候参数为汉字
156                         "query_to_station_name":  '哈尔滨',#self.trainInfoJSON['data']['map'][self.station_2],
157                         "undefined": ""
158                         }
159 
160         response = self.session.post(url=self.submitOrderRequestURL, data=data, headers=self.headers, verify=False)
161         print(response.status_code)
162         try:
163             dic =  response.json()
164             print(dic)
165         except BaseException:
166             return "NetWorkError"
167 
168         if dic['status']:
169             print('>>>提交订单成功')
170             return True
171         elif dic['messages'] != []:
172             if dic['messages'][0] == "车票信息已过期,请重新查询最新车票信息":
173                 print('>>>车票信息已过期,请重新查询最新车票信息')
174                 return "ticketInfoOutData"
175         else:
176             print(">>>提交失败")
177             return False
178 
179     # 四、访问https://kyfw.12306.cn/otn/confirmPassenger/initDc获取响应页面中js的globalRepeatSubmitToken 字段值;即后期查询联系人时需要请求的token值
180     # globalRepeatSubmitToken、以及在 ticketInfoForPassengerForm 字段中的两个key值:key_check_isChange和leftTicketStr两个key
181     # 点击预定时,查询的联系人信息列表url'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'需要两个数据:_json_att=&REPEAT_SUBMIT_TOKEN
182     # 后者那个Token就是下面getToken方法要获取的;注意;只有提交成功后才会获取到该code
183     def getToken(self):
184         data = {"_json_att": ''}
185         response = self.session.post(url=self.initDcURL, data=data, headers=self.headers,
186                                          verify=False)
187         self.repeatSubmitToken = re.findall(u'globalRepeatSubmitToken = \'(\S+?)\'', response.text)[0]
188         print("repeatSubmitToken", self.repeatSubmitToken)
189         self.keyIsChange = re.findall(u'key_check_isChange\':\'(\S+?)\'', response.text)[0]
190         print("keyIsChange:", self.keyIsChange)
191         self.leftTicketStr = re.findall(u'leftTicketStr\':\'(\S+?)\'', response.text)[0]
192         print("leftTicketStr", self.leftTicketStr)
193         print(">>>成功获取token")
194 
195     #五、上面方法提交后,获取用户信息列表注意:该请求前要获取一个token
196     def loadPassengers(self):
197         response = self.session.post(url=self.passengerDTOURL, data={"_json_att": "","REPEAT_SUBMIT_TOKEN":self.repeatSubmitToken}, headers=self.headers, verify=False)
198         print(response.status_code)
199         print(">>>联系人获取是否成功:",response.json()['status'])
200         #联系人信息
201         self.passengersDict=response.json()
202 
203     #六、点击提交,确认订单信息
204     def checkOrder(self):
205         #确认订单联系人信息数据,如下两个字段
206         passengerKicketStr = ""
207         oldPassengerStr = ""
208         data = {
209             "cancel_flag": "2",
210             "bed_level_order_num": "000000000000000000000000000000",
211             "passengerTicketStr":"1,0,1,"+self.passengersDict['data']['normal_passengers'][0]['passenger_name']+',1,'+self.passengersDict['data']['normal_passengers'][0]['passenger_id_no']+','+self.passengersDict['data']['normal_passengers'][0]['mobile_no']+',N_' ,
212             #这里我只选了默认第一个用户,只选了个无座;passengerTicketStr:座位类型,0,车票类型,姓名,身份正号,电话(多个的话,以逗号分隔) 多人用_下划线隔开
213             #例如:1,0,1,张三,1,身份证号码略,18622455880,N_1,0,1,李四,1,身份证号码略,N 注意最后这个N[这里没介绍是什么字段]
214 
215             "oldPassengerStr": self.passengersDict['data']['normal_passengers'][0]['passenger_name']+',1,'+self.passengersDict['data']['normal_passengers'][0]['passenger_id_no']+'1_',
216             #这里我只选了默认第一个用户;oldPassengerStr:姓名,证件类别,证件号码,用户类型 多个用户_下划线隔开
217             #例如:张三,1,身份证号码略,1_李四,1,身份证号码略,1_
218 
219             "tour_flag": "dc",
220             "randCode": "", #randCode:预定验证码
221             "whatsSelect": "1",
222             "_json_att": "",
223             "REPEAT_SUBMIT_TOKEN": self.repeatSubmitToken
224         }
225 
226         response = self.session.post(url=self.chekOrderURL, data=data, headers=self.headers, verify=False)
227         dic=response.json()
228         print(dic)#注意:'ifShowPassCode': 'N',响应后的json中如果该字段为Y,则需要填验证码验证;一般非购票高峰期,没这个验证码
229         if dic['data']['submitStatus'] is True:
230             if dic['data']['ifShowPassCode'] == 'N':
231                 return True
232             if dic['data']['ifShowPassCode'] == 'Y':
233                 return "需要填验证码!"
234         else:
235             print("checkOrderFail")
236             return False
237 
238     #提交订单可能出现验证码,这里是获取那个验证码图片
239     def getCodeImage(self):
240         url = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&{}'.format(random.random())
241         response = self.session.get(url=url, headers=self.headers, verify=False)
242         #path = os.path.abspath('..')
243         with open("img.jpg", 'wb') as f:
244             f.write(response.content)
245 
246      #将订单加入购票队列
247 
248      #等待订单结果
249 
250 
251 if __name__=="__main__":
252     obj=TicketObject()
253     #-------------填日期,起始和终点站,点击查询-------------#
254     obj.sendRequest()#查询所有车次信息
255     #-------------点击预定------------#
256     flag=obj.check_user()#验证用户是否在线
257     if flag:
258         submitFlag=obj.submit_order()#提交订单
259 
260         if submitFlag:
261             #-----------提交成功后,获取用户列表--------------#
262             obj.getToken()#获取提交订单时查询列表需要使用token,该token必须是提交成功后后去到
263             obj.loadPassengers()
264             #-----------勾选乘客,确认订单提交----------#
265             checkFlag=obj.checkOrder()
266             if checkFlag:
267                 print("购票订单确认成功,可加入购票队列!")
268                 #---------加入购票等待队列(排队)---------#
269 
270             else:
271                 print("购票信息确认失败!")

 

推荐阅读