写在前面的话
每年逢年过节,一票难求读者肯定不陌生。这篇文章,我们带领读者从零实现一款12306刷票软件,其核心原理还是通过发送http请求模拟登录12306 站的购票的过程,最后买到票。
关于http请求的格式和如何组装http数据包给服务器发送请求,我们在上一篇文章《从零实现一个http服务器》中已经详细介绍过了,如果还不明白的朋友可以去那篇文章看下。
1、Chrome浏览器(其他的浏览器也可以,都有类似的界面,如Chrome,装了httpwatch的IE浏览器等)
2、一个可以登录12306 址并且可以购票的12306账
3. Visual Studio(版本随意,我这里用的是VS 2013)
一、查票与站点信息接口
之所以先分析这个接口,是因为查票不需要用户登录的,相对来说最简单。我们在Chrome浏览器中打开12306余票查询页面, 址是:https://kyfw.12306.cn/otn/leftTicket/init,如下图所示:
然后在页面中右键菜单中选择【检查】菜单,打开后,选择【 络】选项卡。如下图所示:
打开后页面变成二分窗口了,左侧是正常的 页页面,右侧是浏览器自带的控制台,当我们在左侧页面中进行操作后,右侧会显示我们浏览器发送的各种http请求和应答。我们这里随便查一个票吧,如查2018年5月20日从上海到北京的票,点击查询后,我们发现右侧是这样的:
通过图中列表的type值是xhr,我们可以得出这是一个ajax请求(ajax是浏览器原生支持的一种异步请求,详情请自行百度)。我们选择这个请求,你能看到这个请求的细节——请求和响应结果:
在reponse中,我们可以看到我们的这个http的去除http头的响应结果:
这是一个json格式,我们找个json格式化工具,把这个json格式化后贴出来给大家看一下,其实您后面会发现12306的http请求结果中与购票相关的数据基本上都是json格式。这里的json如下:
其中含有的余票信息在result节点中,这是一个数组。每个节点以|分割,我们可以格式化后显示在自己的界面上:
我这里做的界面比较简陋,读者如果有兴趣可以做更精美的界面。我们列下这个请求发送的http数据包和应答包:
请求包:
通过上一篇文章《从零实现一个http服务器》我们知道这是一个http GET请求,其中在url后面是请求附带的参数:
这四个参数分别是购票日期、出发站、到达站和票类型(这里是成人票(普通票)),正好对应我们界面上的查询信息:
但是,读者可能会问,这里的出发站和到达站分别是SHH和BJP,这些站点代码,我如何获得呢为只有知道这些站点编码我才能自己购买指定出发站和到达站的火车票啊。如果您是一位细心的人,您肯定会想到,我们查票的时候再进入查票页面,这些站点信息就已经有了,那么可能是在这个查票页面加载时,从服务器请求的站点信息,所以我们刷新下查票页面,发现果然是这样:
进入查票页面之前,浏览器从https://kyfw.12306.cn/otn/resources/js/framework/station_name.jstation_version=1.9053下载一个叫station.name.js文件,这是一个javascript脚本,里面只有一行代码,就是定义了一个station_names的js变量,之所以url地址后面加一个station_version=1.9053,你可以理解成版本 ,但是主要是通过一个随机值1.9053,让浏览器不要使用缓存中的station_name.js,而是每次都从服务器重新加载下这个文件,这样的话如果站点信息有更新,也可以避免因为缓存问题,导致本地的缓存与服务器上的站点信息不一致。由于站点信息比较多,我们截个图吧:
看上图,我们可以看出来,每个站点信息都是通过@符 分割,然后通过|分割每一个站点的各种信息。这样的话,根据上文的格式假如我们要查询2018年5月30日从长春到南京的火车普通票,就可以通过 址https://kyfw.12306.cn/otn/leftTicket/queryeftTicketDTO.train_date=2018-05-30&leftTicketDTO.from_station=CCT&leftTicketDTO.to_station=NJH&purpose_codes=ADULT。
当然,这里需要说明一下的就是,由于全国的火车站点信息文件比较大,我们程序解析起来时间较长,加上火车站编码信息并不是经常变动,所以,我们我们没必要每次都下载这个station_name.js,所以我在写程序模拟这个请求时,一般先看本地有没有这个文件,如果有就使用本地的,没有才发http请求向12306服务器请求。这里我贴下我请求站点信息的程序代码(C++代码):
- bool Client12306::GetStationInfo(vector
& si, bool bForceDownload/* = false*/)
- {
- FILE* pfile;
- pfile = fopen("station_name.js", "rt+");
- //文件不存在,则必须下载
- if (pfile == NULL)
- {
- bForceDownload = true;
- }
- string strResponse;
- if (bForceDownload)
- {
- if (pfile != NULL)
- fclose(pfile);
- pfile = fopen("station_name.js", "wt+");
- if (pfile == NULL)
- {
- LogError("Unable to create station_name.js");
- return false;
- }
-
- CURLcode res;
- CURL* curl = curl_easy_init();
- if (NULL == curl)
- {
- fclose(pfile);
- return false;
- }
-
- //URL_STATION_NAMES
- curl_easy_setopt(curl, CURLOPT_URL, URL_STATION_NAMES);
- //响应结果中保留头部信息
- //curl_easy_setopt(curl, CURLOPT_HEADER, 1);
- curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
-
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!