实现抢票小工具&短信关照提醒

获取接口信息

如何使jsp函数每秒运行一次Nodejs 做一个抢票小对象 SQL

查看页面构造

这个便是订票页面,显示当前月的车票情形,根据图示,赤色为已满,绿色为已购,灰色为不可选

如果是可选便是白色的小方块,并且不才面显示余票,如下图所示:

我们可以这么做

1、定时抓取返回的接口信息

2、根据接口返回值判断是否有余票

审查下源代码看下接口信息现在微信浏览器没办法审查源代码

利用chrome 调试

首先面临个问题,如果直接copy公众年夜众号网页Url在chrome打开的话,就会显示这个画面,他被302重定向到了这个页面,所以是行不通的,只有获取OAuth2.0授权才能进去

我自己是一名从事了多年开拓的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适宜2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

以是我们得先通过抓包工具,知道手机访问微信"大众号网页的时候,须要带什么信息过去,这时候我们就得借助抓包工具,这里用的是Charles花瓶。

借助这个工具,我们只需3步就可以轻松搞定手机数据抓包:

1、获取本机IP地址和端口

2、设置代理手机上网

3、依次实行上面两步

获取本机IP地址和端口

第一步,找到端口号,一样平常默认是8088,为了确认可以打开Proxy/Proxy Setting看下

然后找到Charles的help/Local IP Address,点击它就会看到自己的本机地址,找到本机地址记下来,然后进行下一步

设置代理手机上网

首先担保手机跟电脑连接的是同一个wifi,然后在wifi设置那里会有设置代理信息。

设置如下:输入上一步获取主机名,端口号就ok了

输入完成,点击确定后。
Charles就会弹出一个对话框,问你是否赞许接入代理,点击确定allow就行了。

用手机访问目标网页

我们用手机访问微信公众年夜众号【xxxx】进入到抢票页面后,创造Charles已经成功抓包到了网页信息,当我们进入这个抢票页面的时候,他会发起两个要求,一个是获取document文档内容,一个post要求获取票务信息。

仔细剖析了下,大概明白了业务逻辑:

全体项目技能站是java+jsp,传统写法,用户身份验证紧张是cookie+session方案,前端这一块紧张是利用jQuery。

当用户进入页面的时候,会携带查询参数,如起始站点,韶光,车次等信息和cookie要求document文档, 也便是圈起来的这一块

而我们想要的核心内容:日历表,一开始是不显示的由于还要在要求一次。

第二次要求,携带cookie和以上的查询参数发起一个post要求,获取当月的车票信息,也便是日历表内容

下面这个是要求当月票务信息,然而创造他返回的是一堆html节点,估计是获取到之后直接append到div里面的,然后渲染天生日历表内容

接着在手机上操作,选择两个日期,然后点击下单,发送购票要求,拉取购票接口,我们看下购票接口的要乞降返回内容:

看下request 内容,根据字段的意思大概明白是线路,韶光,以及车票金额,还有支付办法

在看看返回的内容:返回一个json字符串数据,里面大概涵盖了下单的成功返回码,韶光,id号等等信息

记录所须要的信息内容

根据上面的剖析,总结下内容:全体项目用户身份验证是利用cookie和session方案,要求数据用的是form data办法,要求字段啥的我们也都清楚。

唯独占一点,便是要求余票的时候,返回的是html节点代码,而不是我们预期的json数据,这样就有个麻烦,我们没办法一览无余的明白他余票的时候是如何显示的

以是我们只能通过chrome进行调试,才能得出他是如何判断余票的。

1、要求余票接口和购票接口的url地址

2、cookie信息

3、各自的request参数字段

4、user-Agent信息

5、各自的response返回内容

设置chrome

有以上信息后,我们就可以开始用chrome调试了, 首先打开More tools/Network conditions

把user-Agent填入到Custom里面

Charles抓包本地要求

由于我们要把获取到的cookie填入到chrome里面,以我们的用户身份去访问网页,以是我们须要在要求目标地址的时候,改包修正cookie

首先我们须要开启 macOS Proxy,抓包我们的http要求

打开chrome访问目标网址,我们可以看到Charles上已经抓包到了我们访问的目标url地址,然后给目标url地址打上断点,方便调试

然后再次访问,这时候断点就生效了,弹出一个tab名为break points,可以看到之以是我们还是不能访问到目标网址,是由于sessionId不对,以是我们把抓取到的cookie在填入到里面,点击execute

这时候,能够精确跳到目标页面了。

大概看了下他整体布局,和jQuery代码CSS代码,特殊这天历表那一块

审查了下元素创造:

1、小方块的构造为:<td class="b"> <span>这里为日期</span> <span>如果有余票则显示余票数量</span> </td> 复制代码

2、td的样式名为a代表不可选

3、样式名为e代表已满

4、样式名为d代表已购

5、样式名为b则是我们要找的,代表可选,也便是有余票

到这一步,全体购票流程就清楚了

到时候我们通过Node.js要求的时候,处理返回数据,用正则去判断是否有余票的class名b ,有余票的话,在获取div里面的余票数量内容就Ok了

Node.js 要求目标接口

剖析须要开拓的功能点

写代码之前我们须要想好功能点,我们须要什么功能:

1、要求余票接口

2、定时要求任务

3、有余票则自动要求购票接口下订单

4、调用腾讯云短信api接口发送短信关照

5、多个用户抢票功能

6、抢某个日期的票

首先mkdir ticket 创建名为ticket的文件夹,接着cd ticket进入文件夹npm init一起瞎几把回车也无妨。
下面开始安装依赖,根据上面的功能需求,我们大概须要:

要求工具,这里选择用的是axios,毕竟axios在node端底层也是调用http.request

cnpm install axios --save

定时任务 node-schedule

cnpm install node-schedule --save

node端选择dom节点工具 cheerio

cnpm install cheerio --save

腾讯发短信的依赖包 qcloudsms_js

cnpm install qcloudsms_js  

开拓要求余票接口

接着touch index.js创建核心js文件,开始编码:

首先引入所有依赖

然后我们先定义要求参数,来一个obj

接着声明一个名为queryTicket的类,为啥要用类呢,由于基于第五个需求点,多个用户抢票的时候,我们分别new一下就行了,

同时我们希望能够记录要求余票的次数,和当抢到票后自动停滞查询余票得操作,以是给他加上个计数变量times和是否停滞的变量,布尔值stop

编写代码:

下面开始定义原型方法,为了方便掩护,我们把逻辑拆分成各个函数

所有数据都是基于查询余票的操作,因此我们先开拓这部分功能

class QueryTicket{ constructor({ data, phoneNumber, cookie, day }) { //constructor代码... } //初始化,由于涉及到异步要求,以是我们利用`async await` async init(){ let ticketList = await this.handleQueryTicket() //返回查询到的余票数组 } //查询余票的逻辑 handleQueryTicket(){ let ticketList = [] //余票数组 let res = await this.requestTicket() this.times++ //计数器,记录要求查询多少次 let str = res.data.replace(/\\/g, "") //格式化返回值 let $ = cheerio.load(`<div class="main">${str}</div>`) // cheerio载入查询接口response的html节点数据 let list = $(".main").find(".b") //查找是否有余票的dom节点 // 如果没有余票,打印出要求多少次,然后返回,不实行下面的代码 if (!list.length) { console.log(`用户${this.phoneNumber}:无票,已进行${this.times}次`) return } // 如果有余票 list.each((idx, item) => { let str = $(item).html() //str这时格式是<span>21</span><span>&$x4F59;0</span> //末了一个span 的内容实在"余0",也便是无票,只不过是被转码了而已 //因此要不才一步对其进行格式化 let arr = str.split(/<span>|<\/span>|\&\#x4F59\;/).filter(item => !!item === true) let data = { day: arr[0], ticketLeft: arr[1] } //如果是要抢指定日期的票 if (this.day) { //如果有指定日期的余票 if (parseInt(data.day) === parseInt(data.day)) { ticketList.push(data) } } else { //如果不是,则返回查询到的所有余票 ticketList.push(data) } }) return ticketList } //调用查询余票接口 requestTicket(){ return axios.post('http://weixin.xxxx.net/ebus/front/wxQueryController.do?BcTicketCalendar', this.postData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI", "Cookie": this.cookie } }) } handleBuyTicket(){} //购票干系逻辑 requestOrder(){}//调用购票接口 handleInfoUser(){}//关照用户的逻辑 sendMSg(){} //发短信接口}

来阐明下那行正则,cheerio抓取到的dom是长这样的,第一个span内容这天期,第二个是余票数量

以是我们要把它格式化变成这种数组,也便是ticketList

开拓购票功能

首先我们在init方法里做个判断,如果有余票才去购票。

class QueryTicket{  constructor({ data, phoneNumber, cookie, day }) {  //constructor代码...  }  //初始化   async init(){    let ticketList = await this.handleQueryTicket()    //如果有余票    if (ticketList.length) {    //把余票传入购票逻辑方法,返回短信关照所须要的数据      let resParse = await this.handleBuyTicket(ticketList)    }    }        //查询余票的逻辑   async handleQueryTicket(){    // 查询余票代码...    }    //调用查询余票接口    requestTicket(){    //调用查询余票接口代码...        }    //购票干系逻辑   async handleBuyTicket(ticketList){    let year = new Date().getFullYear() //年份,    let month = new Date().getMonth() + 1 //月份,拼接购票日期用得上,由于余票接口只返回几号    let {      onStationName,//起始站点名      offStationName,//结束站点名      lineId,//线路id      vehTime,//发车韶光      startTime,//估量上车韶光      onStationId,//上车的站台id      offStationId //到站的站台id      } = this.data // 初始化的数据    let station = `${onStationName}-${offStationName}` //站点,发短信时候用到:"宝安交通局-深港产学研基地"    let dateStr = ""; //车票日期    let tickAmount = "" //总张数    ticketList.forEach(item => {      dateStr = dateStr + `${year}-${month}-${item.day},`      tickAmount = tickAmount + `${item.ticketLeft}张,`    })    let buyTicket = {      lineId,//线路id      vehTime,//发车韶光      startTime,//估量上车韶光      onStationId,//上车的站点id      offStationId,//目标站点id      tradePrice: '5', //金额      saleDates: dateStr.slice(0, -1),      payType: '2' //支付办法,微信支付    }    // 调用购票接口     let data = querystring.stringify(buyTicket)     let res = await this.requestOrder(data) //返回json数据,是否购票成功等等     //把发短信所须要数据都要传入    return Object.assign({}, JSON.parse(res.data), { queryParam: { dateStr, tickAmount, startTime, station } })    }//购票干系逻辑    //调用购票接口    requestOrder(obj){    return axios.post('http://weixin.xxxx.net/ebus/front/wxQueryController.do?BcTicketBuy', obj, {      headers: {        'Content-Type': 'application/x-www-form-urlencoded',        'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI",        "Cookie": this.cookie      }    })    }    handleInfoUser(){}//关照用户的逻辑    sendMSg(){} //发短信接口}

到这里,查询余票,购票这两个核心操作已经完成。

目前还剩下,如何关照用户是否购票成功。

这里利用的是安装腾讯云的SDK,支配了一套发短信的功能。

腾讯云短信的干系内容

可以百度腾讯云的文档,可以copy文档,把稳看短信单发那部分

如果有企业认证的话,看快速入门这里就行了,一步步随着操作

看下短信正文,{Number}这些里面的数字是变量。

便是说短信的模板是固定的,但是里面有{Number}的内容可以自定义

调用的时候,里面的数字对应着传过去的参数数组序号,{1}代表数组[0]参数,以此类推

提交审核,审核一样平常很快就通过,也便是几十万毫秒吧

开拓关照功能

class QueryTicket{ constructor({ data, phoneNumber, cookie, day }) { //constructor代码... } //初始化 async init(){ let ticketList = await this.handleQueryTicket() //如果有余票 if (ticketList.length) { //把余票传入购票逻辑方法,返回短信关照所须要的数据 let resParse = await this.handleBuyTicket(ticketList) //实行关照逻辑 this.handleInfoUser(resParse) } } //查询余票的逻辑 async handleQueryTicket(){ // 查询余票代码... } //调用查询余票接口 requestTicket(){ //调用查询余票接口代码... } //购票干系逻辑 async handleBuyTicket(ticketList){ //购票代码... } //调用购票接口 requestOrder(obj){ //购票接口要求代码... } //关照用户的逻辑 async handleInfoUser(parseData){ //获取上一步购票的response数据和我们拼接的数据 let { returnCode, returnData: { main: { lineName, tradePrice } }, queryParam: { dateStr, tickAmount, startTime, station } } = parseData //如果购票成功,则返回500 if (returnCode === "500") { let res = await this.sendMsg({ dateStr, //日期 tickAmount: tickAmount.slice(0, -1), //总张数 station, //站点 lineName, //巴士名称/路线名称 tradePrice,//总价 startTime,//出发韶光 phoneNumber: this.phoneNumber,//手机号 }) //如果发信成功,则不再进行抢票操作 if (res.result === 0 && res.errmsg === "OK") { this.setStop(true) } else { //失落败不做任何操作 console.log(res.errmsg) } } else { //失落败不做任何操作 console.log(resParse['returnInfo']) } } //发短信接口 sendMSg(){ let { dateStr, tickAmount, station, lineName, phoneNumber, startTime, tradePrice } = obj let appid = 140034324; // SDK AppID 以1400开头 // 短信运用 SDK AppKey let appkey = "asdfdsvajwienin23493nadsnzxc"; // 短信模板 ID,须要在短信掌握台中申请 let templateId = 7839; // NOTE: 这里的模板ID`7839`只是示例,真实的模板 ID 须要在短信掌握台中申请 // 署名 let smsSign = "测试短信"; // NOTE: 署名参数利用的是`署名内容`,而不是`署名ID`。
这里的署名"腾讯云"只是示例,真实的署名须要在短信掌握台申请 // 实例化 QcloudSms let qcloudsms = QcloudSms(appid, appkey); let ssender = qcloudsms.SmsSingleSender(); // 这里的params便是短信里面可以自定义的内容,也便是填入{1}{2}..的内容 let params = [dateStr, station, lineName, startTime, tickAmount, tradePrice]; //用promise来封装下异步操作 return new Promise((resolve, reject) => { ssender.sendWithParam(86, phoneNumber, templateId, params, smsSign, "", "", function (err, res, resData) { if (err) { reject(err) } else { resolve(resData) } }); }) } }

如果发信成功,返回result:0

定时任务

也声明一个类,这里我们用到的是schedule

// 定时任务class SetInter { constructor({ timer, fn }) { this.timer = timer // 每几秒实行 this.fn = fn //实行的回调 this.rule = new schedule.RecurrenceRule(); //实例化一个工具 this.rule.second = this.setRule() // 调用原型方法,schedule的语法而已 this.init() } setRule() { let rule = []; let i = 1; while (i < 60) { rule.push(i) i += this.timer } return rule //假设传入的timer为5,则表示定时任务每5秒实行一次 // [1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56] } init() { schedule.scheduleJob(this.rule, () => { this.fn() // 定时调用传入的回调方法 }); }}多个用户抢票

data: { //用户1 lineId: 111130, vehTime: 0722, startTime: 0751, onStationId: 564492, offStationId: 17990, onStationName: '宝安交通运输局③', offStationName: "深港产学研基地", tradePrice: 0, saleDates: '', beginDate: '', }, phoneNumber: 123123123, cookie: 'JSESSIONID=TESTCOOKIE', day: "17"}let obj2 = { //用户2 data: { lineId: 134423, vehTime: 1820, startTime: 1855, onStationId: 4322, offStationId: 53231, onStationName: '百度国际大厦', offStationName: "裕安路口", tradePrice: 0, saleDates: '', beginDate: '', }, phoneNumber: 175932123124, cookie: 'JSESSIONID=TESTCOOKIE', day: "" }let ticket = new QueryTicket(obj) //用户1let ticket2 = new QueryTicket(obj2) //用户2new SetInter({ timer: 1, //每秒实行一次,建议5秒,不然怕被ip拉黑,我这里只是为了方便下面截图 fn: function () { [ticket,ticket2].map(item => { //同时进行两个用户的抢票 if (!item.getStop()) { //调用实例的原型方法,判断是否停滞抢票,如果没有则连续抢 item.init() } else { // 如果抢到票了,则不连续抢票 console.log('stop') } }) }})

假设我们有两个用户要抢票,以是定义两个obj,实例化下QueryTicket类

node index.js 运行下,跑起来了

如果抢到票的话,我们就会收到短信关照:

打开手机,看下订单信息

原文链接:https://mp.weixin.qq.com/s/IqM2wW1CZB_JpG4neSSHGw作者:前端迷