Python-爬虫-12306购票业务实现
待续。。。
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("购票信息确认失败!")