本文叙述的是使用C#调用12306的API做余票查询程序的方法。
先看一下程序运行截图。本程序使用WPF。
1. 了解12306API
登陆12306网站,点击余票查询,我们查询从北京到上海的火车票。
抓取到的数据包如下:
请求报文头:
这就是我们封装查询报文时需要模仿的格式。
返回报文为一个json格式的字符串,下面是一个样例:
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"datas":[{"train_no":"240000T1090K","station_train_code":"T109","start_station_telecode":"BJP","start_station_name":"北京","end_station_telecode":"SHH","end_station_name":"上海","from_station_telecode":"BJP","from_station_name":"北京","to_station_telecode":"SHH","to_station_name":"上海","start_time":"19:33","arrive_time":"10:44","day_difference":"1","train_class_name":"","lishi":"15:11","canWebBuy":"Y","lishiValue":"911","yp_info":"10177531664047650000101775000060879500063030450000","control_train_day":"20201231","start_train_date":"20150110","seat_feature":"W343136333","yp_ex":"1040106030","train_seat_feature":"3","seat_types":"14163","location_code":"P4","from_station_no":"01","to_station_no":"11","control_day":59,"sale_time":"1000","is_support_card":"0","note":"","gg_num":"--","gr_num":"6","qt_num":"--","rw_num":"无","rz_num":"--","tz_num":"--","wz_num":"166","yb_num":"--","yw_num":"无","yz_num":"无","ze_num":"--","zy_num":"--","swz_num":"--"},{"train_no":"240000D3130B","station_train_code":"D313","start_station_telecode":"VNP","start_station_name":"北京南","end_station_telecode":"SHH","end_station_name":"上海","from_station_telecode":"VNP","from_station_name":"北京南","to_station_telecode":"SHH","to_station_name":"上海","start_time":"19:34","arrive_time":"07:27","day_difference":"1","train_class_name":"动车","lishi":"11:53","canWebBuy":"Y","lishiValue":"713","yp_info":"O030900114O0309030154061500346","control_train_day":"20301231","start_train_date":"20150110","seat_feature":"O343W3","yp_ex":"O0O040","train_seat_feature":"3","seat_types":"OO4","location_code":"P3","from_station_no":"01","to_station_no":"04","control_day":59,"sale_time":"1230","is_support_card":"0","note":"","gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"346","rz_num":"--","tz_num":"--","wz_num":"15","yb_num":"--","yw_num":"--","yz_num":"--","ze_num":"114","zy_num":"--","swz_num":"--"},{"train_no":"240000D31102","station_train_code":"D311","start_station_telecode":"VNP","start_station_name":"北京南","end_station_telecode":"SHH","end_station_name":"上海","from_station_telecode":"VNP","from_station_name":"北京南","to_station_telecode":"SHH","to_station_name":"上海","start_time":"21:16","arrive_time":"08:58","day_difference":"1","train_class_name":"动车","lishi":"11:42","canWebBuy":"Y","lishiValue":"702","yp_info":"O030900158O0309030174061500296","control_train_day":"20301231","start_train_date":"20150110","seat_feature":"O343W3","yp_ex":"O0O040","train_seat_feature":"3","seat_types":"OO4","location_code":"P2","from_station_no":"01","to_station_no":"04","control_day":59,"sale_time":"1230","is_support_card":"0","note":"","gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"296","rz_num":"--","tz_num":"--","wz_num":"17","yb_num":"--","yw_num":"--","yz_num":"--","ze_num":"158","zy_num":"--","swz_num":"--"},{"train_no":"240000D32109","station_train_code":"D321","start_station_telecode":"VNP","start_station_name":"北京南","end_station_telecode":"SHH","end_station_name":"上海","from_station_telecode":"VNP","from_station_name":"北京南","to_station_telecode":"SHH","to_station_name":"上海","start_time":"21:23","arrive_time":"09:12","day_difference":"1","train_class_name":"动车","lishi":"11:49","canWebBuy":"Y","lishiValue":"709","yp_info":"O030900210O0309030154061500236","control_train_day":"20301231","start_train_date":"20150110","seat_feature":"O343W3","yp_ex":"O0O040","train_seat_feature":"3","seat_types":"OO4","location_code":"P2","from_station_no":"01","to_station_no":"05","control_day":59,"sale_time":"1230","is_support_card":"0","note":"","gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"236","rz_num":"--","tz_num":"--","wz_num":"15","yb_num":"--","yw_num":"--","yz_num":"--","ze_num":"210","zy_num":"--","swz_num":"--"}],"flag":true,"searchDate":"2015年01月10号 周六"},"messages":[],"validateMessages":{}}在这个json串中包含了查询到的车辆余票信息,也包含查询者的信息。我们只需要提取其中有用的部分。现将其中有用的部分列举如下:
其中有一些字段没有标注释,目前意义未知
通过解析这个json串,就能完成余票查询的工作。
2. 具体操作方法
根据上面抓取到的数据包,我们知道API的地址为:
string uri = @"https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes={0}&queryDate={1}&from_station={2}&to_station={3}";可以看出,其中一共有4个参数,perpose表示票的种类(成人票,学生票等),date表示查询火车开车的日期(格式为:yyyy-mm-dd),from表示出发车站代码,end表示到达车站代码。代码均为3位数大写英文字母,每一个代码唯一对应一个车站。
uri = string.Format(uri, purpose, date, from, to);
车站代码可以到下面链接处下载:
http://download.csdn.net/detail/xiahn1a/8348211
对于上面给出的链接中下载到的代码的处理方法:
文档中代码是如下格式:
bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1车站间使用@分割,每条信息间使用|分割。我们所需要的只是第2项名称和第3项代码,其他的拼音,编号等不是我们所需要的内容。
根据代码文档我们可以建立一个站名到代码的映射关系。
给一个链接的例子,在2015年1月10日查询从北京到上海的成人票:
https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2015-01-10&from_station=BJP&to_station=SHH剩下的就是按照上面截图的格式封装报文即可,需要注意的是我们需要采用ssl,有一个验证证书的过程,还需要建立一个CookieContainer。
对于返回的json格式串,使用NewtonSoft.Json类中的相应函数将json字符串转换为jclass对象,然后可以直接通过Linq语句查询出所需要的对象。
相关的封装报文步骤代码如下:
private CookieContainer cc = new CookieContainer();
public void GetInfo(string purpose, string date, string from, string to)
{
try
{
string uri = @"https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes={0}&queryDate={1}&from_station={2}&to_station={3}";
uri = string.Format(uri, purpose, date, from, to);
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.CookieContainer = cc;
request.ProtocolVersion = HttpVersion.Version10;
request.Accept = @"*/*";
request.UserAgent = @"Mozilla/5.0 (Windows NT 6.4; WOW64; Trident/7.0; rv:11.0) like Gecko";
request.Referer = @"https://kyfw.12306.cn/otn/lcxxcx/init";
request.ContentType = @"application/x-www-form-urlencoded";
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
string res = reader.ReadToEnd();
jclass jc1 = JsonConvert.DeserializeObject<jclass>(res);
getData(jc1);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return ;
}
private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true; //总是接受
}
本文完成于2015.1.10,到目前为止,该API仍可用。