趁着双11,写个京东商品自动下单
项目地址 求个 star
在现在,商家一年不卖货,双11卖出一年的货是大家都知道的事实了,总得来说调一调蚊子腿的价格,聊胜于无,但是也会有些神价格会出现,这时候买到就是赚到
本来是想趁着双11组台电脑,买个 Z370 的板U套装,没想到京东的 8700k 一直是无货的状态,这几天有货了,价格涨到了3999,简直不能忍,看了下板U套装比较划算,但是有些板U套装是不支持自动下单的,所以 gayhub 搜搜看有没有爬虫可以监听到货自动下单的,正好有了这哥们的 jd-autobuy Python 脚本,还有 Go 的,看了下接口已经很齐全了,来个 node 版本的助助兴
这次用到的 http 库是 axios,支持客户端和服务端,总得来说语法还是很简洁的,在这之前还有个 superagent 库,看了下也差不多,只不过 superagent 在 response 上多处理了下
因为在 vue 中使用了 axios,这次想试试服务端的能力咋样,还是一如既往的好,滋次一波
先写个 request header ,毕竟是服务端,没有浏览器帮你处理 User-Agent,所以自己去浏览器请求下然后把 header 拿到
const defaultInfo = {header: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36','Content-Type': 'text/plain;charset=utf-8','Accept-Encoding': 'gzip, deflate, br','Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.6,en;q=0.4,en-US;q=0.2','Connection': 'keep-alive',},
}
header 拿到我们就可以伪装成浏览器去请求二维码图片了,京东的扫码图片地址 https://qr.m.jd.com/show
,没有多余的技巧,直接用 axios 来个get请求即可
async function requestScan() {const result = await request({method: 'get',url: 'https://qr.m.jd.com/show',headers: defaultInfo.header,params: {appid: 133,size: 147,t: new Date().getTime()},responseType: 'arraybuffer'})
}
参数 appid
size
和 t
可以通过抓包拿到的,这里注意我 responseType 用的 arraybuffer
,默认值是 json
,buffer 主要是方便我们来像本地写入图片,我们来处理下 res
defaultInfo.cookies = cookieParser(result.headers['set-cookie'])
defaultInfo.cookieData = result.headers['set-cookie'];
const image_file = result.data;
await writeFile('qr.png', image_file)
async function writeFile(fileName, file) {return await new Promise((resolve, reject) => {fs.writeFile(fileName, file, 'binary', err => {opn('qr.png')resolve()})})
}
这一步 cookie 已经拿到了,这里我做了两步处理,一步是自己写的 cookieParser
把参数进行解析,主要是拿到其中的 wlfstk_smdl
,接下来会用到,然后直接 writeFile 写入图片就行了,写好了之后利用 opn 打开图片,sindresorhus 大神的 opn 库还是蛮好用的,可以指定程序打开图片,文件等
在扫码之前我们要监听扫码的状态
async function listenScan() {let flag = truelet ticketwhile (flag) {const callback = {}let name;callback[name = ('jQuery' + getRandomInt(100000, 999999))] = data => {console.log(` ${data.msg || '扫码成功,正在登录'}`)if (data.code === 200) {flag = false;ticket = data.ticket}}const result = await request({method: 'get',url: 'https://qr.m.jd.com/check',headers: Object.assign({Host: 'qr.m.jd.com',Referer: 'https://passport.jd.com/new/login.aspx',Cookie: defaultInfo.cookieData.join(';')}, defaultInfo.header),params: {callback: name,appid: 133,token: defaultInfo.cookies['wlfstk_smdl'],_: new Date().getTime()},})eval('callback.' + result.data);await sleep(1000)}return ticket
}
一开始的想法是开个定时器来轮询下:"好没好呀",没有我1秒后再来问下,这里使用 async/await
的强大功能实现个 sleep,比 setTimeout 的使用更优雅而且对于异步的处理也能够操控自如
function sleep(ms) {return new Promise((resolve, reject) => {setTimeout(() => {resolve()}, ms)})
}
这里我们把 header 组合一下,刚刚拿到的 cookie 带上,并加上 host
和 referer
来表明我们从哪里来要到哪里去,参数里面的 token 就是之前解析 cookie 拿到的 wlfstk_smdl ,这个接口应该约定的 jQuery jsonp(京东看了下 jsonp 还是蛮多的),所以我这里使用一个 callback 来模拟一个 jsonp 的执行,看返回的 code 和 msg,code 为 200 的时候说明扫码成功了,这时候 msg 是没有的,所以自定义下,其他状态是有 msg 的,直接输出就 OK 了,扫码成功我们要拿到 ticket
,这个从字面上理解就知道了,大兄弟你拿到入场券了,并且 ticket 下单的时候也是需要的,存起来
这时候用你的手机打开京东扫一扫打开的二维码图片,确认后扫码成功,用入场券登录去
async function login(ticket) {const result = await request({method: 'get',url: 'https://passport.jd.com/uc/qrCodeTicketValidation',headers: Object.assign({Host: 'passport.jd.com',Referer: 'https://passport.jd.com/uc/login?ltype=logout',Cookie: defaultInfo.cookieData.join('')}, defaultInfo.header),params: {t: ticket},})defaultInfo.header['p3p'] = result.headers['p3p']return defaultInfo.cookieData = result.headers['set-cookie']
}
这一步没什么说的,入场券有了,理所应当登录成功了,拿到 p3p 参数并且更新下 cookie 这样一个合法的身份就诞生了
有了身份后就可以去 get 商品页面,这一步需要拿三个请求的信息拼一下
拿到商品页面的 html
function goodInfo(goodId) {const stockLink = `http://item.jd.com/${goodId}.html`return request({method: 'get',url: stockLink,headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),responseType: 'arraybuffer'})
}
拿到商品的价格
async function goodPrice(stockId) {const callback = {}let name;let price;callback[name = ('jQuery' + getRandomInt(100000, 999999))] = data => {price = data}const result = await request({method: 'get',url: 'http://p.3.cn/prices/mgets',headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),params: {type: 1,pduid: new Date().getTime(),skuIds: 'J_' + stockId,callback: name,},})eval('callback.' + result.data)return price
}
拿到商品的状态
async function goodStatus(goodId, areaId) {const callback = {}let name;let statuscallback[name = ('jQuery' + getRandomInt(100000, 999999))] = data => {status = data[goodId]}const result = await request({method: 'get',url: 'http://c0.3.cn/stocks',headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),params: {type: 'getstocks',area: areaId,skuIds: goodId,callback: name,},responseType: 'arraybuffer'})const data = iconv.decode(result.data, 'gb2312')eval('callback.' + data)return status
}
最后 Promise.all 一波带走
async function runGoodSearch() {let flag = truewhile (flag) {const all = await Promise.all([goodPrice(defaultInfo.goodId), goodStatus(defaultInfo.goodId, defaultInfo.areaId), goodInfo(defaultInfo.goodId)])const body = $.load(iconv.decode(all[2].data, 'gb2312'))outData.name = body('div.sku-name').text().trim()const cartLink = body('a#InitCartUrl').attr('href')outData.cartLink = cartLink ? 'http:' + cartLink : '无购买链接'outData.price = all[0][0].poutData.stockStatus = all[1]['StockStateName']outData.time = formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss')console.log()console.log(` 商品详情------------------------------`)console.log(` 时间:${outData.time}`)console.log(` 商品名:${outData.name}`)console.log(` 价格:${outData.price}`)console.log(` 状态:${outData.stockStatus}`)console.log(` 商品连接:${outData.link}`)console.log(` 购买连接:${outData.cartLink}`)const statusCode = all[1]['StockState']// 如果有货就下单// 33 有货 34 无货if (+statusCode === 33) {flag = false} else {await sleep(defaultInfo.time)}}
}
这里要解析 dom,$
就是有着 Node 版 jQuery 之称的 cheerio,但是如果直接解析会乱码,先转码,转码神器出场 iconv-lite,剩下的就是 jQuery 操作了,很久没写 jQuery 了,写起来还是这么的顺溜
defaultInfo 中的 goodId 是商品的 id,下面会说到,解析命令行的参数获得的,在哪里能看到呢,来图
areaId 是对应着区域的信息,毕竟每个城市的库存都是不一样的
京东购物的流程购物车先走一波,然后开始下单付款,有货了我们加入购物车
async function addCart() {console.log()console.log(' 开始加入购物车')const result = await request({method: 'get',url: outData.cartLink,headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),})const body = $.load(result.data)const addCartResult = body('h3.ftx-02')if (addCartResult) {console.log(` ${addCartResult.text()}`)} else {console.log(' 添加购物车失败')}
}
没什么可说的,加入后开始下单
async function buy() {const orderInfo = await request({method: 'get',url: 'http://trade.jd.com/shopping/order/getOrderInfo.action',headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),params: {rid: new Date().getTime(),},responseType: 'arraybuffer'})const body = $.load(orderInfo.data)const payment = body('span#sumPayPriceId').text().trim()const sendAddr = body('span#sendAddr').text().trim()const sendMobile = body('span#sendMobile').text().trim()console.log()console.log(` 订单详情------------------------------`)console.log(` 订单总金额:${payment}`)console.log(` ${sendAddr}`)console.log(` ${sendMobile}`)console.log()console.log(' 开始下单')const result = await request({method: 'post',url: 'http://trade.jd.com/shopping/order/submitOrder.action',headers: Object.assign(defaultInfo.header, {cookie: defaultInfo.cookieData.join('')}),params: {'overseaPurchaseCookies': '','submitOrderParam.btSupport': '1','submitOrderParam.ignorePriceChange': '0','submitOrderParam.sopNotPutInvoice': 'false','submitOrderParam.trackID': defaultInfo.ticket,'submitOrderParam.eid': defaultInfo.eid,'submitOrderParam.fp': defaultInfo.fp,},})if (result.data.success) {console.log(` 下单成功,订单号${result.data.orderId}`)console.log('请前往京东商城及时付款,以免订单超时取消')} else {console.log(` 下单失败,${result.data.message}`)}
}
其实这里 post http://trade.jd.com/shopping/... 这个就可以了,前面的一个请求是下单页面拿一下订单的信息展示下,这里会有两个注意的点
- 商品的数量
京东下单是把购物车这个商品全部下单,不管数量的,比如你购物车已经有一件这个商品了,那么前面的流程走完后购物车现在有两件这个商品,下单后是下单了两件,当然了这里是可以更改数量的,但是我没写 - 订单的参数
上面下单的请求可以注意到三个陌生的参数submitOrderParam.trackID
submitOrderParam.eid
submitOrderParam.fp
,trackID 前面有拿到过,这里直接用就行了,那么 eid 和 fp 是从哪来的呢?答案是登录页面,但是这里有个坑是 request 返回的页面拿到的 dom 元素是不行的,只能通过浏览器来,这也很好办,Node 有 phantomjs,但是这里我用了 Chrome 出品的 puppeteer
puppeteer 使用也很简单,它是基于 Node 的 headless Chrome 工具
puppeteer.launch().then(async browser => {console.log(' 初始化完成,开始抓取页面')const page = await browser.newPage();await page.goto('https://passport.jd.com/new/login.aspx');await sleep(1000)console.log(' 页面抓取完成,开始分析页面')const inputs = await page.evaluate(res => {const result = document.querySelectorAll('input')const data = {}for (let v of result) {switch (v.getAttribute('id')) {case 'token':data.token = v.valuebreakcase 'uuid':data.uuid = v.valuebreakcase 'eid':data.eid = v.valuebreakcase 'sessionId':data.fp = v.valuebreak}}return data})Object.assign(defaultInfo, inputs)await browser.close();console.log(' 页面参数到手,关闭浏览器')console.log()console.log(' ------------------------------------- ')console.log(' 请求扫码')console.log(' ------------------------------------- ')console.log()})
puppeteer 首先要 launch 后来生成一个 browser 的实例,我们用 browser 来新建一个页面运行我们的网址,并且我们可以在它提供的 evaluate 方法中操作 DOM,上面的代码也是很简单的,一目了然
至此基本上一个自动下单的功能就完成了,再扩展下命令行参数
const args = require('yargs').alias('h', 'help').option('a', {alias: 'area',demand: true,describe: '地区编号',}).option('g', {alias: 'good',demand: true,describe: '商品编号',}).option('t', {alias: 'time',describe: '查询间隔ms',default: '10000'}).option('b', {alias: 'buy',describe: '是否下单',default: true}).usage('Usage: node index.js -a 地区编号 -g 商品编号').example('node index.js -a 2_2830_51810_0 -g 5008395').argv;
这里我给了两个必需的参数和两个可选的参数,-a
必须要的,地区编号,-g
必要要的,商品编号,-t
商品查询的间隔时间,默认是10s,-b
是否自动购买,默认是购买的,这里是 boolean,yargs 还是蛮好用的,也可以用 TJ 大神的 commander,都是一样的
完整的代码可以去下面的项目地址中查看
项目地址 求个 star
趁着双11,写个京东商品自动下单相关推荐
- 今年你的双11包裹,也是自动驾驶卡车送来的吗?
允中 发自 副驾寺 量子位 报道 | 公众号 QbitAI 你的双11包裹,到货了吗? 家住上海虹口区场中路的谢先生,已在今日上午完成收货. 谢先生购买的是最新款无人机,为的是体验最新的黑科技. 但令 ...
- 吉利2019年将推出首款飞行汽车;双11数据引京东阿里互怼|ServiceHot一周热闻
这几天关于京东和阿里的新闻可真不少~ >>>> 京东发了份"漂亮得不像话"的Q3财报 京东(Nasdaq:JD)11月3日发布了2017财年第三季度财报:净 ...
- 管窥“双11”:看京东阿里怎样下零售创新棋局
眼瞅着"11·11"的脚步越来越近,两大电商巨头阿里与京东早已做好准备,各自出招,试图铺设一盘更大的零售创新棋局. "无人"物流成加分项 随着订单量骤然上升,物 ...
- 未封装的扩展程序是什么意思_伊能静双11买900件商品,张萌发文去年快递未收到,什么意思?...
昨天凌晨,伊能静发文报告双11购物战况,整个双十一大约购入900件,11月11日单日用了三个号码购入397件商品. 双十一期间,小编专门挑有需要的.便宜的买,狠心狠心再狠心最后才买了不足100件. 妈 ...
- 未封装的扩展程序是什么意思_伊能静双11买900件商品,张萌发文去年快递未收到什么意思?...
昨天凌晨,伊能静发文报告双11购物战况,整个双十一大约购入900件,11月11日单日用了三个号码购入397件商品. 双十一期间,小编专门挑有需要的.便宜的买,狠心狠心再狠心最后才买了不足100件. 妈 ...
- 2021年双十一活动,淘宝/天猫喵糖/京东双11任务自动助手软件+淘宝/京东/拼多多/抖音/直播抢购软件,分享代码
软件下载地址https://www.lanzoui.com/b01cfbrbihttps://www.lanzoui.com/b01cfbrbi 功能 1.支持天猫喵糖/京东双11任务自动完成,领取奖 ...
- 【备战】双11未到战火先燃,天猫京东苏宁各打各的牌
很快,一年一度的双11就要来临.去年双11,天猫已经连续第七年创造新的记录,交易额达到912亿元.今年突破千亿,似乎也毫无悬念. 事实上,如今的双11,已远不止于一次购物狂欢,它已经演变成一种商业奇迹 ...
- “动员商家”策略相继而出,天猫京东吹响“双11”号角
双11跨过十二个年头,现在正处于预热前奏. 各大电商平台"摩拳擦掌",吹响"商家动员"冲锋号.今年,天猫和京东的招商举措已然发布,对比两者的差异,这个双11能否 ...
- 双11背后,再看京东云的「底色」
京东的新底色是什么?在京东云身上,一个关于产业的答案正在逐渐成型. 作者|皮爷 出品|产业家 今年双11,用户体验更丝滑的同时,京东集团副总裁.京东云事业群总裁高礼强也觉得更轻松了. " ...
最新文章
- php签名是做什么用的,这个签名在PHP中意味着什么()?
- MS Reporting Services 报表开发
- vm ububtu突然没网
- Angular 内容投影 content projection 关于选择器问题的单步调试
- Android数据的存储方式简介
- 浅谈FOF场外投资交易流程
- Python 3——xlsxwriter生成图表
- R语言连续变量正态性检验
- php水印文字方向,ppt文本框文字方向为所有文字旋转的设置方法
- dna计算机ppt模板,七、DNA与蛋白质序列同源分析(进化树构建).ppt
- php onblur=,onblur
- Linux使用cp命令报cp:omitting directory错误
- 计算机类英文参考文献,计算机英文参考文献.doc
- 三星手机动态修改分辨率信息
- Python_美多商城(验证码)_3
- [jzoj 4249] 【五校联考7day1】游戏 {贪心/斜率优化}
- ipfs星际文件系统初体验
- 信用卡违约预测模型的开发思路
- 灵魂有香气的女子李筱懿:充实自己,学会把自己变成奢侈品
- 【笔记】openwrt - 【一文解决】ipv6设置、DDNS、端口转发