还原整个思路,遇到的一些问题也会写出来,纯新手向,因为打包在了一个类里,所以单独拷贝一个函数可能会有些问题,后面有完整代码地址,如有错误可以指正- -有问题也可以留言,后面写的有些赶,可以去完整源代码中看看
截至2018/10/9 0:15代码还是有效的
环境:
python3.6
涉及的库:
requests
json
prettytable
re
pprint
1.url分析
先打开12306的余票查询,我用的是火狐浏览器,按F12打开调试工具选择network(网络)选项,选择出发地,目的地,日期,点击查询
(小白问题:调试窗口里啥也没有是因为 你一番操作猛如虎,结果完事后想起:哎?调试窗口没打开!)
右上角有一排选项,咱选js,xhr 然后从这一条条里找出我们需要的链接(点的时候在右边选择响应,可以看到这条请求返回啥)哇塞!你找到一串data,里面还有各种车票的信息,好了,就是你了:
https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-23&leftTicketDTO.from_station=HZH&leftTicketDTO.to_station=RZH&purpose_codes=ADULT
PS:找json链接这种活就看经验了,老手可能马上就能找到,新手可能就要一条条点(汗~~)我这给一个快速的方法:进入余票查询界面,选择好出发地和目的地及日期,点击查询,好!然后再打开F12调试界面在点查询!哇塞只有1条而且就是我们需要的!(深入原因的话就是在你打开了调试界面后你只进行了这一条查询的post请求,而返回的当然就是你要的了)
好,继续正题,我们来观察这条json的链接,很容易明白那些参数吧,如下:
https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-23&leftTicketDTO.from_station=HZH&leftTicketDTO.to_station=RZH&purpose_codes=ADULT
leftTicketDTO.train_date=日期
leftTicketDTO.from_station=出发点的地点码
leftTicketDTO.to_station=目的地的地点码
purpose_codes=票的种类
2.获取车站对照字典
上文链接中有两个参数是地点码,即一个地名对应一个值,一开始我是直接下载了网上(baidu喽)的一个字典,然后最后写完.不对!我靠!而且调试半天定位到字典错了!(这么多的地名偏偏被窝测试到错误的也是没谁了…)
好!自己动手!
import requests
import re
#生成字典 运行一次后抛弃之~~~
def new_dictionary():
url="https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9069"
station_namesweb=requests.get(url)
stationdic = re.findall(r'([\u4e00-\u9fa5]+)\|([a-zA-Z]+)', station_namesweb.text)
stationdic=dict(stationdic)
print(stationdic)
new_dictionary()
requests请求,json打包,正则提取,over!
打印出一个字典复制然后在一个文件里就ok了(上面代码中的url其实也是一样找的,可以试试手去找找看)哈哈,其实这也是今天整个工程的浓缩版吧,思路也是差不多的
3.输入出发点,目的地,时间,获得需要的查询链接
废多看码!!!
def getUrl(self):
startflag = False
endflag = False
while startflag == False:
start = input("请输入始发地:\n")
startflag = stations.__contains__(start)
if startflag == False:
print('始发地输入错误!')
while endflag == False:
end = input("请输入目的地:\n")
endflag = stations.__contains__(end)
if endflag == False:
print('目的地输入错误!')
self.date = input("请输入日期(格式为xxxx-xx-xx):\n")
url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=' \
+ self.date + '&leftTicketDTO.from_station=' \
+ stations[start] + '&leftTicketDTO.to_station=' \
+ stations[end] + '&purpose_codes=ADULT'
return url
4.获取车辆信息
因为12306在这里没做啥验证所以可以直接请求,不妨直接在浏览器里看一下,emmm是不是头都大了!!!
车辆的信息都在这里了,用|分隔开,我们要做的就是从里面提取出我们想要的信息,emmm其实我花的时间最多的是在这里,因为要一一对应知道每个信息代表什么,而我又找不到那么一辆每个信息都齐全的车来让我对照…只能用好几辆的信息来对比喽(还不一定是对的,眼力有限)…我的眼睛!!!
乱码|预订|030000K5540E|K551|MDB|RZH|HZH|RZH|02:31|10:50|08:19|Y|乱码|20181021|3|B2|44|52|0|0||||无|||无||有|10|||||10401030|1413|0
上代码:
def getData(self,url):
dataweb = requests.get(url)
datajson = json.loads(dataweb.text)
datatrains = datajson['data']['result']
restations=datajson['data']['map']#地点缩写:地点全称的字典
dataans = []
for train in datatrains:
per = {
'train_no': '',
'from_station_no': '',
'to_station_no': '',
'seat_types': '',
'chufazhan':'',
'dadaozhan':'',
'checi': '',
'chufasj': '',
'didasj': '',
'lishi': '',
'erdeng': '',
'yideng': '',
'shangwu': '',
'wuzuo':''
}
train = train.split('|')
per['train_no'] = train[2]
per['from_station_no'] = train[16]
per['to_station_no'] = train[17]
per['seat_types'] = train[35]
per['chufazhan']=train[6]
per['dadaozhan']=train[7]
#将得到站点名字由缩写转换全称
per['chufazhan']=restations[per['chufazhan']]
per['dadaozhan']=restations[per['dadaozhan']]
per['checi'] = train[3]
per['chufasj'] = train[8]
per['didasj'] = train[9]
per['lishi'] = train[10]
per['erdeng'] = train[30]
per['yideng'] = train[31]
per['shangwu'] = train[32]
per['wuzuo']=train[26]
for value in per:
if (per[value] == ''):
per[value] = '-'
dataans.append(per)
return dataans
PS:这里有个问题要说明下:比如我朋友温州-金华,而金华有两个站,他只要其中一个站的,所以我们得显示出出发站和抵达站到底是哪个,但是在信息中我们获得地点是一个码而不是一个站的名字,而我们的字典中是 {名字:码},一开始我想要不干脆再做个反转的字典,后来往上翻的时候发现:原来上面已经给我们了!解决!计划通~
5.获取票价信息
票价的url是另外一条了,获取方式同上上,老方法请求,解析,打包
def GetPrice(self,train_no,from_station_no,to_station_no,seat_types,date,price):
url = 'https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice?train_no=' \
+ train_no + '&from_station_no=' \
+ from_station_no + '&to_station_no=' \
+ to_station_no + '&seat_types=' \
+ seat_types + '&train_date=' + date
priceweb=requests.get(url)
pricejson=json.loads(priceweb.text)
pricedata=pricejson['data']
shangwu=pricedata.__contains__('A9')#商务
yideng=pricedata.__contains__('M')#一等
erdeng=pricedata.__contains__('O')#二等
wuzuo=pricedata.__contains__('WZ')#无座
if shangwu:
price['shangwu']=pricedata['A9']
else :
price['shangwu']=''
if yideng:
price['yideng']=pricedata['M']
else :
price['yideng']=''
if erdeng:
price['erdeng']=pricedata['O']
else :
price['erdeng']=''
if wuzuo:
price['wuzuo']=pricedata['WZ']
else :
price['wuzuo']=''
return price
6.输出
用了prettytable库输出表格
def Print(self):
price={
'shangwu':'',
'yideng':'',
'erdeng':'',
'wuzuo':'',
}
table = prettytable.PrettyTable()
table.field_names = ["车次","出发站","达到站" ,"出发时间", "抵达时间", "历时", "二等座", "一等座", "商务座","无座"]
for per in self.data:
price=self.GetPrice(per['train_no'],per['from_station_no'],per['to_station_no'],per['seat_types'],self.date,price)
table.add_row([per['checi'],per['chufazhan'],per['dadaozhan'], per['chufasj'], per['didasj'], per['lishi'], per['erdeng']+'\n'+price['erdeng'], per['yideng']+'\n'+price['yideng'],
per['shangwu']+'\n'+price['shangwu'],per['wuzuo']+'\n'+price['wuzuo']])
print(table)
7.演示
8.完整代码
https://github.com/senjay/12306tickets_and_price_search