史上最“脑残”的“抢火车票”程序(node.js版)

【背景】

快过年了,我妈一个电话打过来叫我给他买火车票,我到12306一查,硬座和硬卧基本没有了,高铁又太贵.

最后只抢了3张无座票,但是我妈说能不能买有座位的啊,我说没有了啊,我妈:你过两天再帮我看看。我:...

为了帮老妈抢到有座的票,后来用了360抢票插件,还用了网上的一个别人用c#写的客户端来抢票,妈的,用了两三天都没用。

最后还是打算自己用node写一个,当时我的想法就是写个简单的,能用就行。

所以,思路如下:

用node写一个爬虫,每过一分钟就爬取12306,查询某一辆火车是否还有余票,有余票就给我发一封邮件,提醒我有余票了,然后我立马登录12306改签。

这个思路的有两点前提,第一、要自己提前确定好想买哪一辆火车,包括:火车车次,日期。第二、自己要经常在电脑前,只要一来邮件就去12306买票,这对于程序猿来说已经满足了。

【代码实现】

要想实现我的想法,运用到了2个node库:nodemailernode-schedule,分别实现邮件和定时执行功能。

因为12306是https协议的,所以node的http模块还是不行,这里可以用node的https模块。

当然12306还需要有浏览器证书,我代码里已经有了,大家下下来就可以用。

代码:

var https = require('https');
var fs = require('fs');
var ca = fs.readFileSync('./cert/srca.cer.pem');
var nodemailer = require('nodemailer');
var schedule = require('node-schedule');
var config = {
    time:'2017-01-21',//日期格式必须是这样
    from_station:'BJP',//始发站车站代码,这里是北京北
    end_station:'XMS',//厦门
    train_num:'K571'//车次
    your_mail:'****@163.com',//你自己的邮箱,我这里用的是163邮箱,如果你要改其他类型的邮箱的话,那请你修改transporter里的服务器信息
    mail_pass:'****'//放心写吧
};
var yz_temp = '',yw_temp = '';//保存余票状态
function queryTickets(config){
    var options = { 
        hostname: 'kyfw.12306.cn',//12306
        path: '/otn/leftTicket/queryA?leftTicketDTO.train_date='+config.time+'&leftTicketDTO.from_station='+config.from_station+'&leftTicketDTO.to_station='+config.end_station+'&purpose_codes=ADULT',
        ca:[ca]//证书
    };
    var req = https.get(options, function(res){ 
    var data = '';
    var transporter = nodemailer.createTransport({
        host: "smtp.163.com",//邮箱的服务器地址,如果你要换其他类型邮箱(如QQ)的话,你要去找他们对应的服务器,
        secureConnection: true,
        port:465,//端口,这些都是163给定的,自己到网上查163邮箱的服务器信息
        auth: {
            user: config.your_mail,//邮箱账号
            pass: config.mail_pass,//邮箱密码
        }
    });
    res.on('data',function(buff){
        data += buff;//查询结果(JSON格式)
    }); 
    res.on('end',function(){
        // console.log('res',data);
        var jsonData = JSON.parse(data).data;
        for(var i=0;i<jsonData.length;i++){
            var cur = jsonData[i];
            if(cur.queryLeftNewDTO.station_train_code==config.train_num){
                // console.log(cur);
                var yz = cur.queryLeftNewDTO.yz_num;//硬座数目
                var yw = cur.queryLeftNewDTO.yw_num;//硬卧数目
                var trainNum = cur.queryLeftNewDTO.station_train_code;//车次
                console.log('硬座',yz);
                console.log('硬卧',yw);
                if(yz!='无'&&yz!='--'||yw!='无'&&yw!='--'){
                    if(yw_temp == yw && yz_temp == yz){//当余票状态发生改变的时候就不发送邮件
                        console.log('状态没改变,不重复发邮件');
                        return;
                    }
                    var mailOptions = {
                        from: config.your_mail, // 发件邮箱地址
                        to: config.your_mail, // 收件邮箱地址,可以和发件邮箱一样
                        subject: trainNum+'有票啦,硬座:'+yz+',硬卧:'+yw, // 邮件标题
                        text: trainNum+'有票啦\n'+'时间是'+cur.queryLeftNewDTO.start_train_date+',\n出发时间:'+cur.queryLeftNewDTO.start_time+',\n到达时间:'+cur.queryLeftNewDTO.arrive_time+',\n历时:'+cur.queryLeftNewDTO.lishi+',\n始发站:'+cur.queryLeftNewDTO.from_station_name+',\n到达:'+cur.queryLeftNewDTO.to_station_name, // 邮件内容
                    };

                    // 发邮件部分
                    transporter.sendMail(mailOptions, function(error, info){
                        if(error){
                            return console.log(error);
                        }
                        console.log('Message sent: ' + info.response);
                        yw_temp = yw;//保存当前列车的余票数量
                        yz_temp = yz;
                    });
                }else{
                    console.log('硬座/硬卧无票');
                }
                
                break;
            }
        }
        // fs.writeFile('./train.json',data);
    })  
});

req.on('error', function(err){
    console.error(err.code);
});
}
var rule = new schedule.RecurrenceRule();  
rule.second = [0];
schedule.scheduleJob(rule, function(){
        queryTickets(config);
        console.log('scheduleCronstyle:' + new Date());
}); 

下面说下上述代码中的config里面的参数如何找到:

譬如我要找北京到厦门的火车:

首先进入12306余票查询页面:

点击查询之后控制台出现以下信息:

看最后一个点击打开:

看到红框里的内容就是config里面需要配置的选项了。

然后运行node main.js,然后一直放在那运行(可以放到自己的服务器上去运行)

运行结果:

总结一下,我这个如果想用这个买票,你只要配置config,替换里面的邮箱和密码(你自己的邮箱),这样就会收到邮件通知了。

目前我已经用这个把之前买的3张无座全都改签为硬座票了(因为有人要退票啊,哈哈)

大家最好用163邮箱和163的手机客户端吧,通知及时,一有邮件我的手机就会震动提示。

【更新于2017-4-1】

已经解决不能请求成功导致查询不到余票信息的问题,同时修改了若干错误,现在已经可以正常使用。

现在的运行结果:

查询结果:

代码地址:node_12306

(希望大牛勿喷,多多指点,有空会完善功能。)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java架构师

关于GET和POST请求

网上看了一篇关于这两种请求的区别,感觉和之前看到的不太一样。 大众版: 1. GET使用URL或Cookie传参。而POST将数据放在BODY中。 2. GET...

34670
来自专栏信安之路

网络安全渗透测试

针对网络的渗透测试项目一般包括:信息收集、端口扫描、指纹识别、漏洞扫描、绘制网络拓扑、识别代理、记录结果等。下面就一一介绍。

25500
来自专栏知识分享

八,ESP8266 文件保存数据(基于Lua脚本语言)

应该是LUA介绍8266的最后一篇,,,,,,下回是直接用SDK,,然后再列个12345.......不过要等一两个星期,先忙完朋友的事情 前面几篇 用AT指令...

42270
来自专栏Theo Tsao

Ionic3学习笔记(十三)HttpClient 实现 HTTP 请求以及踩过的一些坑

当然是基于这篇古老的文章啦 ==> http://www.jianshu.com/p/9855610eb1d4 因为是2015年的文章,已经时隔2年多,很难确保...

59910
来自专栏FreeBuf

XSS的原理分析与解剖:第三章(技巧篇)

作者 Black-Hole 0×01 前言: 关于前两节url: 第一章:http://www.freebuf.com/articles/web/40520....

21570
来自专栏张戈的专栏

HTTP加速器varnish安装小记(1)

上午共享的那个varnish 安装手册,个人看了下,有点不知所云,好吧~看来还是先安装玩玩! 苦逼公司服务器没法连外网,不能用什么 wget 或 yum 命令直...

43580
来自专栏Android 开发者

Android P 行为变更

33020
来自专栏数据和云

匪夷所思:罕见的 Oracle 全局事务锁等待事件分析

杨廷琨,云和恩墨CTO,Oracle ACED,ITPUB Oracle 数据库管理版版主 ,人称"杨长老”,十数年如一日坚持进行Oracle技术研究与写作,号...

21310
来自专栏Seebug漏洞平台

披着狼皮的羊——寻找惠普多款打印机中的RCE漏洞

原文:《A Sheep in Wolf’s Clothing – Finding RCE in HP’s Printer Fleet》

41080
来自专栏拂晓风起

Loader拉取图片,由于redirect重定向,导致策略文件无效 设置checkPolicyFile后还是无效:需要一个策略文件,但在加载此媒体时未设置 checkPolicyFile 标志

12460

扫码关注云+社区

领取腾讯云代金券