一直以来都感觉12306的中转查询很不好用,加上最近决定每周写一篇和专业有关的博文,理论也好(估计目前是写不出什么理论性的东西的
),实践也好,一来供初学者参考,二来算是对自己所学知识的一种巩固以及对学习新知识的一种动力。因此就挑火车票中转查询这个小程序开个头咯
由于是底层程序员敲代码不免有很多不足甚至误导人的地方,欢迎大家指正
下面废话不多说进入正题。
No1、先对12306网站页面进行分析,首先用浏览器打开12306购票页面,随意输入两个地点,点击查询后查看页面的元素(大多数浏览器可通过F12直接查看,F12查看不了的鼠标右键)
通过查看消息头部的请求网址栏,获取车票信息查询的目的地址为
/otn/leftTicket/log?leftTicketDTO.train_date=-03-17&leftTicketDTO.from_station=DTV&leftTicketDTO.to_station=BJP&purpose_codes=ADULT
接着查看参数栏
可以看到该目的地址的四个变量的具体参数值,通过以上两步基本可以确定车票信息查询的目的地址的获取方式,不过从参数看,车站信息并不是汉字,而是车站对应的编码,因此需要分析一下页面其他内容,以便了解车站信息编码的获取方式,通过对页面的分析(这个就不知道该怎么描述了,总之就是凭经验和感觉大概看看,总能看出点眉目来)可以发现在首页的代码里有一个关于station_name的js
大胆猜想这就是放车站编码的地方,按标记的网址找过去看一下
因此可以断定这个就是存放车站信息编码的地方,这样关于网址的查询流程,信息基本上就了解了
No2、在对页面信息了解了之后。就要进入正题,开始敲代码了
a、首先通过requests获取车站编码信息,由编码信息可见,车站信息都是以@分割,并且每个车站有几个不同的编码,根据前面的页面分析可以知道咱们要的是紧跟车站名称后面的大写字母编码,所以需要对获取的内容做一个分割,并将分割后的数据以车站为键,编码为值存储在字典中,具体代码如下
def get_station_info():
stationlist_url =
'/otn/resources/js/framework/station_name.js'
try:
r = requests.get(stationlist_url, timeout=10,
verify=False)
r.raise_for_status()
stationlist = r.content
stationlist = str(stationlist, encoding =
"utf-8")
dic_station={}
list_station = stationlist.split('@')
for each in list_station[1:]:
key =
each.split('|')[1]
val =
each.split('|')[2]
dic_station.update({key:val})
return dic_station
except:
return '网络链接异常'
*此处需注意的知识点:对数据类型列表和字典的使用,包括其常用到的函数
b、获取了车站编码信息,接着就应该根据用户输入的车站去查询相应的车次了,获取用户信息代码较简单,就不赘述了,这里需要提一下的是,我是讲用户输入信息放在列表中,以便根据下标去访问想要的信息
*此处需注意的知识点:字符串的截取与拼接
c、由以上两步,就可以获取到目的网址的参数值,即时间,两个站点,一个我也不知道是什么意思的参数
即 FromStation =
dic_station[query_info[0]] #出发车站
ToStation =
dic_station[query_info[1]]#终点车站
Date =
query_info[2]#出发时间,最后一个参数我也不清楚12306是用来干嘛的,就不深究了,直接原样传入,由此便可生产要访问的目的网址:
query_url='/otn/leftTicket/query?leftTicketDTO.train_date='+Date+'&leftTicketDTO.from_station='+FromStation+'&leftTicketDTO.to_station='+ToStation+'&purpose_codes=ADULT'
,生产目的网址后,就是根据网址去获取页面内容了(当然这里需要对目标页面做一个简单分析,以便知道目标数据是已什么形式存储的)
try:
r =
requests.get(query_url,timeout=10,verify=False)
r.raise_for_status()
r.encoding = r.apparent_encoding
if r.json()["data"]:
dic_info =
r.json()["data"]
return
dic_info
else:
return
'这两城市之间没有直达列车'
except:
return '网络链接异常'
*次数需注意的知识点:了解html页面基本数据组织类型,这里用到的是json类型
d、获取信息后,最后一步将信息打印出来
format1 =
'{0:^4}\t{1:^4}\t{2:^4}\t{3:^4}\t{4:^4}\t{5:^4}\t{6:{9}^7}\t{7:^4}\t{8:^4}'
print(format1.format('始发车次','发车时间','始发站','到达时间','中转车次','中转站点','中转站发车时间','到达站','到达时间',chr(12288)))
for i in
range(0,len(train_info),2):
for j in train_info[i]:
for k in
train_info[i+1]:
print(format1.format(j['queryLeftNewDTO']['station_train_code'],j['queryLeftNewDTO']['start_time'],j['queryLeftNewDTO']['from_station_name'],
j['queryLeftNewDTO']['arrive_time'],
k['queryLeftNewDTO']['station_train_code'],k['queryLeftNewDTO']['from_station_name'],k['queryLeftNewDTO']['start_time'],
k['queryLeftNewDTO']['to_station_name'],k['queryLeftNewDTO']['arrive_time'],chr(12288)))
*此处需要注意的知识点:格式化输出时汉字的对齐问题
至此,一个基本火车票查询的核心部分就完成了,由于篇幅问题,中转查询就不详细描述,只简单说一下笔者的大概思路,就是通过始发站,去遍历车站字典,获取出由始发站发出的每一趟车的信息,然后再以每一趟车的终点站为起点,以用户输入的终点站为终点站,获取车次信息,之后将车次信息写入列表中。在这里有一点要注意,当始发站发出来的车对应的终点站并没有到实际终点站的车次时,那么应该将记入的前一条记录弹出列表,即:if
r.json()["data"]:
dic_info =
r.json()["data"]
train_info.append(dic_info)
else:
train_info.pop()。
最后附上全部代码,供大家参考。如有任何疑问欢迎留言讨论。
说明:此程序只用于python基本交流,对于算法并没有做太深入研究,因此中转查询可能较慢,并且对查询后的数据并没有根据实际情况做过滤筛选(如中转车发车时间比出发站的到站时间早等问题)
import requests
import re
import warnings
import types
warnings.filterwarnings("ignore") #忽略警告信息
def get_query():
from_station =
str(input('请输入出发站点:'))
to_station =
str(input('请输入到达站点:'))
Date =
str(input('请输入出行日期:'))
type_query =
str(input('查询直达请输入1,查询中转请输入0:'))
Date =
Date[0:4]+'-'+Date[4:6]+'-'+Date[6:8]
return
(from_station,to_station,Date,type_query)
def get_station_info():
stationlist_url =
'/otn/resources/js/framework/station_name.js'
try:
r = requests.get(stationlist_url, timeout=10,
verify=False)
r.raise_for_status()
stationlist = r.content
stationlist = str(stationlist, encoding =
"utf-8")
dic_station={}
list_station = stationlist.split('@')
for each in list_station[1:]:
key =
each.split('|')[1]
val =
each.split('|')[2]
dic_station.update({key:val})
return dic_station
except:
return '网络链接异常'
def post_query_zz(dic_station,query_info):
count = 0
train_info = []
station_list =
dic_station.keys()
sum1 =
len(station_list)
for i in station_list:
FromStation = dic_station[query_info[0]]
ToStation = dic_station[i]
Date = query_info[2]
#print('FromStation:%s ToStation:%s
Date%s'%(FromStation,ToStation,Date))
query_url='/otn/leftTicket/query?leftTicketDTO.train_date='+Date+'&leftTicketDTO.from_station='+FromStation+'&leftTicketDTO.to_station='+ToStation+'&purpose_codes=ADULT'
r =
requests.get(query_url,timeout=100,verify=False)
r.raise_for_status()
r.encoding = r.apparent_encoding
if r.json()["data"]:
dic_info =
r.json()["data"]
train_info.append(dic_info)
FromStation = dic_station[i]
ToStation
= dic_station[query_info[1]]
Date =
query_info[2]
query_url='/otn/leftTicket/query?leftTicketDTO.train_date='+Date+'&leftTicketDTO.from_station='+FromStation+'&leftTicketDTO.to_station='+ToStation+'&purpose_codes=ADULT'
r =
requests.get(query_url,timeout=100,verify=False)
r.raise_for_status()
r.encoding
= r.apparent_encoding
if
r.json()["data"]:
dic_info =
r.json()["data"]
train_info.append(dic_info)
else:
train_info.pop()
count += 1
#if count == 100:
#break
if countP0 == 0:
print('当前查询进度:%.2f'%(count/sum1*100)+'%')
return train_info
def ptinr_info_zz(train_info):
format1 =
'{0:^4}\t{1:^4}\t{2:^4}\t{3:^4}\t{4:^4}\t{5:^4}\t{6:{9}^7}\t{7:^4}\t{8:^4}'
print(format1.format('始发车次','发车时间','始发站','到达时间','中转车次','中转站点','中转站发车时间','到达站','到达时间',chr(12288)))
for i in
range(0,len(train_info),2):
for j in train_info[i]:
for k in
train_info[i+1]:
print(format1.format(j['queryLeftNewDTO']['station_train_code'],j['queryLeftNewDTO']['start_time'],j['queryLeftNewDTO']['from_station_name'],
j['queryLeftNewDTO']['arrive_time'],
k['queryLeftNewDTO']['station_train_code'],k['queryLeftNewDTO']['from_station_name'],k['queryLeftNewDTO']['start_time'],
k['queryLeftNewDTO']['to_station_name'],k['queryLeftNewDTO']['arrive_time'],chr(12288)))
def post_query(dic_station,query_info):
try:
FromStation = dic_station[query_info[0]]
ToStation = dic_station[query_info[1]]
Date = query_info[2]
query_url='/otn/leftTicket/query?leftTicketDTO.train_date='+Date+'&leftTicketDTO.from_station='+FromStation+'&leftTicketDTO.to_station='+ToStation+'&purpose_codes=ADULT'
except:
return '输入有误,请检查后重新输入!'
try:
r =
requests.get(query_url,timeout=10,verify=False)
r.raise_for_status()
r.encoding = r.apparent_encoding
if r.json()["data"]:
dic_info =
r.json()["data"]
return
dic_info
else:
return
'这两城市之间没有直达列车'
except:
return '网络链接异常'
def ptinr_info(dic_info):
format1 =
'{0:^5}\t{1:^3}\t{2:^3}\t{3:^4}\t{4:^4}\t{5:^4}\t{6:^4}\t{7:^4}\t{8:^4}\t{9:^4}\t{10:^4}'
print(format1.format('车次','出发站','到达站','发车时间','到达时间','一等座','二等座','软卧','硬卧','硬座','无座'))
for i in dic_info:
print(format1.format(i['queryLeftNewDTO']['station_train_code'],
i['queryLeftNewDTO']['from_station_name'],i['queryLeftNewDTO']['to_station_name'],
i['queryLeftNewDTO']['start_time'],i['queryLeftNewDTO']['arrive_time'],
re.sub('--','无',i['queryLeftNewDTO']['zy_num']),re.sub('--','无',i['queryLeftNewDTO']['ze_num']),
re.sub('--','无',i['queryLeftNewDTO']['rw_num']),re.sub('--','无',i['queryLeftNewDTO']['yw_num']),
re.sub('--','无',i['queryLeftNewDTO']['yz_num']),re.sub('--','无',i['queryLeftNewDTO']['wz_num'])))
def main():
while 1 == 1:
dic_station = get_station_info()
#获取个车站的车站编码信息
if isinstance(dic_station,dict):
#获取用户输入的查询条件
query_info
= get_query()
#根据查询条件,访问服务器获取最新数据
if
query_info[3] == '1':
dic_info =
post_query(dic_station,query_info)
if
isinstance(dic_info,list):
#打印查询结果
ptinr_info(dic_info)
else:
print(dic_info)
else:
train_info =
post_query_zz(dic_station,query_info)
ptinr_info_zz(train_info)
else:
print(dic_station)
flag = str(input('继续查询请按y/Y,否则按任意键退出:'))
if flag == 'Y' or flag == 'y':
continue
else:
break
main()
最后附程序运行界面
下周主题预告:网购指定物品比价查询(比价平台:淘宝,当当,京东等)