const axios = require('axios') const querystring = require("querystring"); //序列化对象,用qs也行,都一样 var QcloudSms = require("qcloudsms_js"); var cheerio = require('cheerio'); var schedule = require('node-schedule');
然后我们先定义请求参数,来一个obj
var obj = { data: { lineId: 111130, //路线id vehTime: 0722, //发车时间, startTime: 0751, //预计上车时间 onStationId: 564492, //预定的站点id offStationId: 17990,//到站id onStationName: '宝安交通运输局③', //预定的站点名称 offStationName: "深港产学研基地",//预定到站名称 tradePrice: 0,//总金额 saleDates: '17',//车票日期 beginDate: '',//订票时间,滞空,用于抓取到余票后填入数据 }, phoneNumber: 123123123, //用户手机号,接收短信的手机号 cookie: 'JSESSIONID=TESTCOOKIE', // 抓取到的cookie day: "17" //定17号的票,这个主要是用于抢指定日期的票,滞空则为抢当月所有余票 }
接着声明一个名为 queryTicket 的类,为啥要用类呢,因为基于第五个需求点,多个用户抢票的时候,我们分别 new 一下就行了,
同时我们希望能够记录请求余票的次数,和当抢到票后自动停止查询余票得操作,所以给他加上个计数变量 times 和是否停止的变量,布尔值 stop
编写代码:
class QueryTicket{ /** *Creates an instance of QueryTicket. * @param {Object} { data, phoneNumber, cookie, day } * @param data {Object} 请求余票接口的requery参数 * @param phoneNumber {Number} 用户手机号,短信需要用到 * @param cookie {String} cookie信息 * @params day {String} 某日的票,如'18' * @memberof QueryTicket 请求余票接口 */ constructor({ data, phoneNumber, cookie, day }) { this.data = data this.cookie = cookie this.day = day this.phoneNumber = phoneNumber this.postData = querystring.stringify(data) this.times = 0; //记录次数 var stop = false //通过特定接口才能修改stop值,防止外部随意串改 this.getStop = function () { //获取是否停止 return stop } this.setStop = function (ifStop) { //设置是否停止 stop = ifStop } } }
下面开始定义原型方法,为了方便维护,我们把逻辑拆分成各个函数
class QueryTicket{ constructor({ data, phoneNumber, cookie, day }) { //constructor代码... } init(){}//初始化 handleQueryTicket(){}//查询余票的逻辑 requestTicket(){} //调用查询余票接口 handleBuyTicket(){} //购票相关逻辑 requestOrder(){}//调用购票接口 handleInfoUser(){}//通知用户的逻辑 sendMSg(){} //发短信接口 }
所有数据都是基于查询余票的操作,因此我们先开发这部分功能
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++ //计数器,记录请求查询多少次 var str = res.data.replace(/\\/g, "") //格式化返回值 var $ = cheerio.load(`<div>${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) => { var str = $(item).html() //str这时格式是<span>21</span><span>&$x4F59;0</span> //最后一个span 的内容其实"余0",也就是无票,只不过是被转码了而已 //因此要在下一步对其进行格式化 var 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
2-3开发购票功能
首先我们在 init 方法里做个判断,如果有余票才去购票,没有余票购个毛