10年想本身建个网站练练手,因而上万网申请域名,为了找个稍微心仪的域名是伤透了脑 筋。当时写了个很简单的自动提交表单的查询,是用webbrowser作的,分析表单数据累了个半死,倒也作出来个简单能用的,递归一直查询 (a,b...z,az,ab...az...)单线程,而且万网有限制,查询间隔太快会被屏蔽,扫了好久也没扫到多少数据,而后就不了了之。 javascript
12年南下深圳,在园子里看到各类对12306的思考及吐槽,打算作个简单的12306买票的小程序,也作过一些尝试,但因为本身太菜,遇到各类问题后停了下来。一晃晃过了世界末日,2013来了,买票的问题推到了眼前,硬着头皮开始编码。 php
先来看看下面这个对http请求的封装方法,做者小坦克,我这里拿来主义了。 html
须要fiddler或者相似的工具来分析http请求,简单介绍fiddler,如图: java
选择左边URL后,选择右边上下都为Raw的标签窗口看到的就是这张图了,右上角窗口为http请求(Request),右下角为http相应(Response)。 程序员
继续看上图,你在登陆页面中点击登陆实际是发送上图的第三条请求,该请求为post,它须要form数据,格式为Request区域最后一行数据: web
一:登陆 面试
url(Post): https://dynamic.12306.cn/otsweb/loginAction.do?method=login // 登陆请求 data: loginRand=随机数&loginUser.user_name=用户名&nameErrorFocus=&user.password=密码&passwordErrorFocus=&randCode=验证码&randErrorFocus=
url(Get):https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand // 验证码
url(Post):https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest // 随机数 {"loginRand":"494","randError":"Y"} // 返回值
到这里,登陆就完成了,貌似很简单啊! 数据库
二:查询 apache
url(Get): https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=queryLeftTicket&orderRequest.train_date=2013-01-27&orderRequest.from_station_telecode=GZQ&orderRequest.to_station_telecode=WHN&orderRequest.train_no=&trainPassType=QB&trainClass=QB%23D%23Z%23T%23K%23QT%23&includeStudent=00&seatTypeAndNum=&orderRequest.start_time_str=00%3A00--24%3A00 url解码: https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=queryLeftTicket&orderRequest.train_date=2013-01-27&orderRequest.from_station_telecode=GZQ&orderRequest.to_station_telecode=WHN&orderRequest.train_no=&trainPassType=QB&trainClass=QB#D#Z#T#K#QT#&includeStudent=00&seatTypeAndNum=&orderRequest.start_time_str=00:00--24:00 orderRequest.train_date:日期 orderRequest.from_station_telecode/orderRequest.to_station_telecode: 车站代码(url(Get): https://dynamic.12306.cn/otsweb/js/common/station_name.js) orderRequest.train_no:车次 trainPassType/trainClass: 车次类型 includeStudent: 学生票标识 seatTypeAndNum:貌似有牛人得出这里跟下铺有关系?对我来讲未知 orderRequest.start_time_str:起止时间
能够在登陆状态下直接请求,比在查询页面快而且没有限制,返回的json(去掉html标签)为: json
0,T264,广州12:19,兰州16:37,28:18,--,--,--,--,--,无,无,--,无,有,--,预订 \n1,K226,广州20:36,兰州07:12,34:36,--,--,--,--,--,无,无,--,1,有,--,预订 \n2,T38,广州23:53,兰州06:28,30:35,--,--,--,--,--,无,无,--,无,有,--,预订
依次分别为:
商务座,特等座,一等座,二等座,高级软卧,软卧,硬卧,软座,硬座,无座,其余。 --:没有该席别;*:未到开始时间;有:有而且数量充足;数字:有但数量有限:无:已售完
查询也是这样简单,其实这里还返回来很重要的信息,这里咱们卖个关子,继续:
下一步干什么呢?预约按钮,这一步比较麻烦,Post提交的信息比较多,很繁琐,须要细心的去反复调试
三:预约
url(Post): https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=submutOrderRequest // 预约 data: station_train_code=T38&train_date=2013-01-27&seattype_num=&from_station_telecode=GZQ&to_station_telecode=LZJ&include_student=00&from_station_telecode_name=%E5%B9%BF%E5%B7%9E&to_station_telecode_name=%E5%85%B0%E5%B7%9E&round_train_date=2013-01-27&round_start_time_str=00%3A00--24%3A00&single_round_type=1&train_pass_type=QB&train_class_arr=QB%23D%23Z%23T%23K%23QT%23&start_time_str=00%3A00--24%3A00&lishi=30%3A35&train_start_time=23%3A53&trainno4=6300000T3803&arrive_time=06%3A28&from_station_name=%E5%B9%BF%E5%B7%9E&to_station_name=%E5%85%B0%E5%B7%9E&from_station_no=01&to_station_no=22&ypInfoDetail=1*****30884*****00001*****00003*****0000&mmStr=3C8A201EB0DAF5F17803BF07AAFC2016A2D44E0C4302D3469551C86A&locationCode=Q6 url解码: station_train_code=T38&train_date=2013-01-27&seattype_num=&from_station_telecode=GZQ&to_station_telecode=LZJ&include_student=00&from_station_telecode_name=广州&to_station_telecode_name=兰州&round_train_date=2013-01-27&round_start_time_str=00:00--24:00&single_round_type=1&train_pass_type=QB&train_class_arr=QB#D#Z#T#K#QT#&start_time_str=00:00--24:00&lishi=30:35&train_start_time=23:53&trainno4=6300000T3803&arrive_time=06:28&from_station_name=广州&to_station_name=兰州&from_station_no=01&to_station_no=22&ypInfoDetail=1*****30884*****00001*****00003*****0000&mmStr=3C8A201EB0DAF5F17803BF07AAFC2016A2D44E0C4302D3469551C86A&locationCode=Q6
前面的参数再也不赘述(有疑问可回头看看查询的参数及说明),看看这段:
&ypInfoDetail=1*****30884*****00001*****00003*****0000&mmStr=3C8A201EB0DAF5F17803BF07AAFC2016A2D44E0C4302D3469551C86A&locationCode=Q6
坦率的讲,我也不知道它是干吗的,我只知道它是从哪里来的,这里就是上文卖的关子,其实在点击预约时附加了该信息(查询时得到)
onclick=javascript:getSelected('T38#26:47#23:53#6300000T3803#GZQ#TSJ#02:40#广州#天水#01#20#1*****30884*****00001*****00003*****0000#3C8A201EB0DAF5F17803BF07AAFC2016A2D44E0C4302D3469551C86A#Q6')
预约这里痛苦了好久,这里多说几句,如上图,该请求为post类型请求,返回302,即重定向,来看302以后的请求
url(Get): https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=init // 申请令牌 // 返回值 <input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="2508bfa47ec2b4d909fb30190cabf71a"> <input type="hidden" name="leftTicketStr" id="left_ticket" value="1000003166400000000010000000023000000000" />
就是说302到上面URL以后 上面请求会返回一个TOKEN(令牌,防止重复提交),这两个值在后续提交订单和确认购票时会用到。可是重定向以后的请求咱们是拿不到的,咱们能够再向它请求一次令牌(302的令牌拿不到,咱们再主动找它要一个令牌),记录便可。
这里,预约的模拟就完成了,接下来提交订单。
四:提交订单
url(Get): https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=randp // 提交订单验证码 注意该部分参数与登陆不一样
url(Post): https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=checkOrderInfo&rand=bdte // 提交订单请求 data: // 该部分数据因为涉及身份信息,见下文解码信息 url解码: org.apache.struts.taglib.html.TOKEN=ad45f047d7c4222a11c437ebd1f977f7&leftTicketStr=1026353107408145000010263500003046250000&textfield=中文或拼音首字母&checkbox1=1&orderRequest.train_date=2013-01-28&orderRequest.train_no=630000K22609&orderRequest.station_train_code=K226&orderRequest.from_station_telecode=GZQ&orderRequest.to_station_telecode=TSJ&orderRequest.seat_type_code=&orderRequest.ticket_type_order_num=&orderRequest.bed_level_order_num=000000000000000000000000000000&orderRequest.start_time=20:36&orderRequest.end_time=02:22&orderRequest.from_station_name=广州&orderRequest.to_station_name=天水&orderRequest.cancel_flag=1&orderRequest.id_mode=Y&passengerTickets=1,0,1,姓名,1,身份证号码,电话号码,Y&oldPassengers=姓名,1,身份证号码&passenger_1_seat=1&passenger_1_ticket=1&passenger_1_name=姓名&passenger_1_cardtype=1&passenger_1_cardno=身份证号码&passenger_1_mobileno=电话号码&checkbox9=Y&oldPassengers=&checkbox9=Y&oldPassengers=&checkbox9=Y&oldPassengers=&checkbox9=Y&randCode=h94b&orderRequest.reserve_flag=A&tFlag=dc
url(Get): https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=getQueueCount&train_date=2013-01-27&train_no=630000K22609&station=K226&seat=1&from=GZQ&to=LZJ&ticket=1029053183409105000010290500553050750000 // 查询余票 {"countT":0,"count":229,"ticket":"1*****31644*****00001*****00013*****0000","op_1":true,"op_2":false} // 返回值
提交订单的请求完成。
咱们回来来看 1*****31644*****00001*****00013*****0000 这 段,从查询请求开始,反复出现该部分,经过在提交订单环节余票信息分析,该数据就是返回的余票信息,即余票信息在第一次查询时就已经返回,但在第一次查询 和提交订单后的查询的数字稍微有所出入,估计为查询时得到数据的缓存时间有关系,固然,提交订单后查询得到的应该更为接近数据库,具体数据以下:
1*****31644*****00001*****00013*****0000 // 无座:164 软卧:0 硬座:1 硬卧:0 1*****3无座4*****0软卧1*****0硬座3*****0硬卧
上面不部分为较为普通车型返回的余票数据,什么是普通车型:K,T,Z系列(不包括高铁,普通慢车,临客),而且该车型包含软卧,硬卧,硬座,无座四中票种。也可能出现卧铺车(Z系列),或者无卧铺车因此返回的数据应该是1*****31644*****00001酱紫的,高铁未测试,道理亦然。
不管在最开始的查询,仍是提交订单后查询,都是操做缓存,因此在提交订单后查询数据为0时,也能够无视余票直接强行确认订单,有机会定到票哦。没有通过大量测试,一般会返回当前排队人数大于与票数或者余票不足(这里须要取舍的,推荐仍是查询余票>0时提交订单)。
工做基本完成了,临门一脚。
五:确认订单
url(Post): https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=confirmSingleForQueue data: // 该部分数据因为涉及身份信息,见下文解码信息 url解码: org.apache.struts.taglib.html.TOKEN=ad45f047d7c4222a11c437ebd1f977f7&leftTicketStr=1026353107408145000010263500003046250000&textfield=中文或拼音首字母&checkbox1=1&orderRequest.train_date=2013-01-28&orderRequest.train_no=630000K22609&orderRequest.station_train_code=K226&orderRequest.from_station_telecode=GZQ&orderRequest.to_station_telecode=TSJ&orderRequest.seat_type_code=&orderRequest.ticket_type_order_num=&orderRequest.bed_level_order_num=000000000000000000000000000000&orderRequest.start_time=20:36&orderRequest.end_time=02:22&orderRequest.from_station_name=广州&orderRequest.to_station_name=天水&orderRequest.cancel_flag=1&orderRequest.id_mode=Y&passengerTickets=1,0,1,姓名,1,身份证号码,电话号码,Y&oldPassengers=姓名,1,身份证号码&passenger_1_seat=1&passenger_1_ticket=1&passenger_1_name=姓名&passenger_1_cardtype=1&passenger_1_cardno=身份证号码&passenger_1_mobileno=电话号码&checkbox9=Y&oldPassengers=&checkbox9=Y&oldPassengers=&checkbox9=Y&oldPassengers=&checkbox9=Y&randCode=h94b&orderRequest.reserve_flag=A {"errMsg":"Y"} // 返回值
又一大堆参数,但回头对照提交订单Data,直接将 &tFlag=dc 截掉便可。
铛铛铛铛...,多想来段美妙的音乐,回家的路通了,遗憾的是,高兴的太早了。
春运(1月26日)以前若是返回Y,那么直接就表示有票了,但在春运以后,坑爹的排队又开始了,因此表示只是排上队了,不表明必定有票,若是在开售的第一个整点,排上队拿到票的概率很大,越日后拿到飘的概率越小。
若是返回的信息包含:非法的购票请求,意味着某一个请求的data部分参数错误。
以上彻底根据小坦克博文(感谢)推动,地址:
http://www.cnblogs.com/TankXiao/archive/2012/02/20/2350421.html
下面的地址分析是在完成后才找到的,遗憾没有早看到,走了不少弯路:
http://www.cnblogs.com/waninlezu/archive/2012/01/07/tran_ticket.html http://sskaje.me/index.php/2012/01/12306bot/
源码:下载 (2013.01.22,持续更新)
PS: 该文编辑时通过多个周期,其中参数级数据没有连续性,以实际Fiddler数据为准。
若是借助该文,能帮你买到票,固然最好,若是没有,试着用本身掌握的知识,能去学习和解决一些实际生活中的问题,何尝不是更大的收获。
之前工做中有问题也偶尔上园子、msdn找找资料,没什么特别大的感触,感受对.net(确切的说是asp.net)理解也仅仅是拖拖控件,而后数 据绑定完事了。园子里逛久了,看了大量技术文章及分享,如汤姆大叔,老赵,刘未鹏等大神们的博文,才知道.net的博大精深及本身的浅薄,知道本身的无知 也算知吧(聊表安慰)?。做为要奔三去了的老菜鸟一枚,惭愧的同时又满怀但愿。也感谢网络那一端无私分享的你。
下面部分为吐槽和求助:
做为一名80后、没学历(弱弱的问,高中不算学历吧?)、培训出身(著名的***鸟,被各类鄙视和吐槽,囧)、菜鸟程序员。看着园子里各类大神(园子广泛称大牛)及90后新人们的2012总结,深感愧疚。
网上投了一箩筐的简历,没有收到一个面试邀请,不知道是简历的问题(菜+低调),学历的问题,仍是人品?有招聘信息的码农推荐一下。
深圳,.net程序员,3年经验(asp.net)。