还原整个思路,遇到的一些问题也会写出来,纯新手向,因为打包在了一个类里,所以单独拷贝一个函数可能会有些问题,后面有完整代码地址,如有错误可以指正- -有问题也可以留言,后面写的有些赶,可以去完整源代码中看看
截至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
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!