最近也快到春运高峰期了,每年到这个时候都是铁路部门最忙的时候,同时也是12306(以后都以网站来代替)大考的时候,出于一个技术人员对于网站的好奇所以尝试获取它的最新数据。
之前准备实现一个第三方订票系统,后来分析之后发现破解验证码这第一关就没办法完成,所以最后放弃这个想法。
后来考虑即使不能通过第三方来订票,是否可以通过第三方来获得现在最新的余票信息?后来分析网站的查询余票的传输的数据发现的确可以实现。现在就将获取最新余票数据、以及在自己的系统中完全实现实时查询余票信息的方法介绍给大家。另外在此申明本人并没有针对网站的恶意,也没有丝毫炫耀的意思,本人就一普通的技术爱好者。好了,废话不说正题开始了:
一、分析网站查询余票在网络上传输的数据
分析一个网站的网络传输数据一般都采用Firefox的firebug进行分析,见图:
在网络上传的地址为:http://dynamic.12306.cn/otsquery/query/queryRemanentTicketAction.do?method=queryLeftTicket&orderRequest.train_date=2013-01-22&orderRequest.from_station_telecode=BJP&orderRequest.to_station_telecode=GZQ&orderRequest.train_no=&trainPassType=QB&trainClass=QB%23D%23Z%23T%23K%23QT%23&includeStudent=00&seatTypeAndNum=&orderRequest.start_time_str=00%3A00--24%3A00
大家刚看到这一段可能不知识每个参数是什么意思,现在我就为大家解释一下:
首先知识struts的都知道后台采用struts1来进行接收
http://dynamic.12306.cn/otsquery/query/queryRemanentTicketAction.do?method=queryLeftTicket [后台方法名为:queryLeftTicket]
&orderRequest.train_date=2013-01-22 [出发日期]
&orderRequest.from_station_telecode=BJP [出发站的编号,随后会为大家解析BJP这些编号都是什么意思,这里代表就是北京]
&orderRequest.to_station_telecode=GZQ [到站为广州]
&orderRequest.train_no= [车次]
&trainPassType=QB[有三个值:全部、始发、过路]
&trainClass=QB%23D%23Z%23T%23K%23QT%23
&includeStudent=00
&seatTypeAndNum=
&orderRequest.start_time_str=00%3A00--24%3A00 [出发时间]
最主要的就只有三个参数:
&orderRequest.train_date=2013-01-22
&orderRequest.from_station_telecode=BJP
&orderRequest.to_station_telecode=GZQ
再看看网站返回的数据是什么:
{"datas":"0,<spanid='id_330000K5980K' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('330000K5980K#BXP#GZQ')οnmοuseοut='onStopOut()'>K599<\/span>, 北京西 <br> 05:25,<imgsrc='/otsquery/images/tips/last.gif'> 广州 <br> 10:50,29:25,--,--,--,--,--,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,--,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,--,\\n1,<spanid='id_2400000G7101' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('2400000G7101#BXP#IZQ')οnmοuseοut='onStopOut()'>G71<\/span>,<img src='/otsquery/images/tips/first.gif'> 北京西 <br> 08:00, 广州南 <br> 17:38,09:38,7,--,75,539,--,--,--,--,--,--,--,\\n2,<spanid='id_2400000G7900' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('2400000G7900#BXP#IZQ')οnmοuseοut='onStopOut()'>G79<\/span>,<imgsrc='/otsquery/images/tips/first.gif'> 北京西 <br> 10:00,<imgsrc='/otsquery/images/tips/last.gif'> 广州南 <br> 17:59,07:59,20,--,105,243,--,--,--,--,--,--,--,\\n3,<spanid='id_2400000T150A' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('2400000T150A#BXP#GZQ')οnmοuseοut='onStopOut()'>T15<\/span>,<imgsrc='/otsquery/images/tips/first.gif'> 北京西 <br> 11:01,<imgsrc='/otsquery/images/tips/last.gif'> 广州 <br> 07:32,20:31,--,--,--,--,14,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,--,<fontcolor='darkgray'>无<\/font>,91,--,\\n4,<spanid='id_2400000G8101' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('2400000G8101#BXP#IZQ')οnmοuseοut='onStopOut()'>G81<\/span>,<imgsrc='/otsquery/images/tips/first.gif'> 北京西 <br> 13:05,<imgsrc='/otsquery/images/tips/last.gif'> 广州南 <br> 22:32,09:27,26,--,104,557,--,--,--,--,--,--,--,\\n5,<spanid='id_2400000T970D' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('2400000T970D#BXP#GGQ')οnmοuseοut='onStopOut()'>T97<\/span>,<imgsrc='/otsquery/images/tips/first.gif'> 北京西 <br> 13:08,<imgsrc='/otsquery/images/tips/last.gif'> 广州东 <br> 10:10,21:02,--,--,--,--,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,<fontcolor='darkgray'>无<\/font>,--,<font color='darkgray'>无<\/font>,117,--,\\n6,<spanid='id_1200000T1203' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('1200000T1203#BJP#GGQ')οnmοuseοut='onStopOut()'>T13<\/span>, 北京 <br> 15:00,<imgsrc='/otsquery/images/tips/last.gif'> 广州东 <br> 12:43,21:43,--,--,--,--,--,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,--,<fontcolor='darkgray'>无<\/font>,66,--,\\n7,<spanid='id_240000T2010E' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('240000T2010E#BXP#GZQ')οnmοuseοut='onStopOut()'>T201<\/span>,<imgsrc='/otsquery/images/tips/first.gif'> 北京西 <br> 18:11, 广州 <br> 15:04,20:53,--,--,--,--,<fontcolor='darkgray'>无<\/font>,<font color='darkgray'>无<\/font>,<fontcolor='darkgray'>无<\/font>,--,<font color='darkgray'>无<\/font>,84,--,","time":"13:57"}
初看好像完全没有规律,但是规律肯定会有的,不然如何展示到页面上呢?
看看怎么分析:
【{"datas":"0,】这一段没什么用,咱们先看第一条记录是怎么存在后面的字符串中的,我们在页面上看到的第一条记录是:K599次车,所对应的字符串为:
<spanid='id_330000K5980K' class='base_txtdiv'οnmοuseοver=javascript:onStopHover('330000K5980K#BXP#GZQ') οnmοuseοut='onStopOut()'>K599<\/span>,
北京西 <br> 05:25,
<imgsrc='/otsquery/images/tips/last.gif'> 广州 <br> 10:50,
29:25,
--,
--,
--,
--,
--,
<fontcolor='darkgray'>无<\/font>,
<fontcolor='darkgray'>无<\/font>,
--,
<fontcolor='darkgray'>无<\/font>,
<fontcolor='darkgray'>无<\/font>,
--,
\\n1,
上面就是第一条记录,也就是后面的所有要生成表格的数据都是查出来之后通过逗号来拼接起来的,并且每隔16就是一条数据。
即然这个规律找到了那只需要我们在页面上组织好起点和终点的代码和出发日期再传到我们的后台,再通过java网络提交的方式提交到指定的地址就可以了,
然后获得返回数据再按逗号进行分隔解析就可以得到最新的余票信息了。
二、如何实现
1、得到各个站点的编码与汉字的对应信息
在网站上买过票的人都知道如果想输入起始站为北京,只需要输入bj即可然后会显示一个div里面有北京、北京西、北京南等以bj开头的站名,然后选择一个
即可。这是怎么实现的呢?有过开发经验的人都知道专业名称叫自动补全或者自动完成或者自动填充(大概意思就是这样),先谈谈我对自动补全的个人理解吧
讲的不对还请手下留情。
自动补全有两种获取方式:一是输入一个字符后,通过ajax访问后台,然后后台再经过一系列的操作将结果返回到前台,通过ajax进行接收
到之后再填充到div中显示在页面上,这种方式需要与服务器交互;二是服务器提前将需要自动补全的编码及名称信息生成好并放在一个js文件
中然后页面加载的时候会请求这个js文件,然后通过js代码自动为某些需要补全的文本框注册一个监听,在监听代码中会将用户输入的
值与已经生成好的js补全编码进行比较如果一样就取出来并封装到div中,最后显示到页面上,这种方式不需要与服务器进行交互。
这是我对自动补全的理解,通过上面的描述我想大家应该看出来了这两种方式的优缺点,但是第二种方式尤其适合访问量很大的网站
例如12306。不错网站的确使用第二种方式,提前将所有站点的编码信息生成到js文件中。这需要点右键查看源码,给大家列出部分的
站点编码信息:@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2@bjn|北京南|VNP|beijingnan|bjn|3@bjx|北京西|BXP|beijingxi|bjx|4
大家应该都知识在页面上输入什么,然后得到显示到页面上的汉字,最后进行查询的时候用的是哪个编码了吧。
不错,如果页面上输入bj,这些都会查出,如果选择"北京",则编码为"BJP"。
另外自动补全已经有很多成型的开源js框架都非常好用,大家想了解可以直接去问谷哥。
2、数据传输及解析
取得到站点编码之后就可以轻松的拼接出URL串,如何访问指定的URL以及解析返回的信息呢?可以使用java原生的网络编程的知识,也可以
使用apache提供的HttpClient工具包这个会方便具体的使用方式可以参考官方文档。
列出部分代码:
public static String retHTML(String url) throws ClientProtocolException, IOException
{
// HttpClient主要负责执行请求
HttpClient httpclient = new DefaultHttpClient();
// 利用HTTP GET向服务器发起请求
HttpGet get = new HttpGet(url);
// 获得服务器响应的所有信息
HttpResponse response =httpclient.execute(get);
// 获得服务器响应回来的消息体(不包括HTTP HEAD)
HttpEntity entity = response.getEntity();
StringBuffer content = new StringBuffer();
if (entity != null)
{
InputStream is = entity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String line = null;
while ((line = br.readLine()) != null)
{
content.append(line);
}
is.close();
}
// 释放所有的链接,一般在所有的请求处理完成之后,才需要释放
httpclient.getConnectionManager().shutdown();
return content.toString();
}
通过这个方法就可以得到返回回来的结果串。
由于返回回来的结果串有很多html的标签,我们需要去掉这些标签,可以使用Jsop工具来解析,具体使用方法可以问谷哥或百度。
public static String decodeSpan(Stringtmp){
Document doc = Jsoup.parse(tmp);
Elements spans = doc.getElementsByTag("span");
String spanText = tmp;
for (Element span : spans) {
spanText = span.text();
}
return spanText;
}
以上代码就是解析span标签中的文本。
最后解析完之后就可以存到本地的数据库中了。
同时也可以进一步开发一个线程,可以先在数据库中配置几条路线,比如从北京到广州、从广州到北京等,然后
启动多个线程,每个线程对应查一条线程,然后每隔一段时间会启动这些线程去网站查询最新的余票信息,然后
将结果存入本地库中,最后在本地项目中就可以看到最新的信息了。最后声明写这篇文间纯粹个人爱好不针对
任何组织及个人,很多技术都是在互想学习中提高的,另外严厉鄙视那种使用自动工具来恶意提交的不法份子
说不定影响的就是你我自己的购票流程。