Skip to content

Commit

Permalink
修正wihle占用cpu问题,增加任务抢购时间限制 (#12)
Browse files Browse the repository at this point in the history
 Feat: 流程优化 & bug 修复
* 修正wihle占用cpu问题,增加任务抢购时间限制
* 增加获取eid,fp网页
* 增加时间可读性
* 定时器改动
* 更友好的时间格式
* 改进访问订单结算页面逻辑提高成功率
* 增加sleep
* 新增获取eid和fp的html文件
* 降低cpu占用率
*  删除废弃文件

Co-authored-by: superq <[email protected]>
Co-authored-by: noone <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2021
1 parent 0dfdfd6 commit 2b25505
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 67 deletions.
40 changes: 20 additions & 20 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

## 支持预约-抢购-提交订单流程的商品

狗东现有俩种抢购模式, 现在程序内部实现了自动区分俩种流程。区分依据参照下文`代码分析`处贴的俩个 js 文件
狗东预约抢购模式下又设置了如下俩种抢购流程, 现在程序内部实现了自动区分俩种流程。区分依据参照下文`代码分析`处贴的俩个 js 文件

1. 到时间直接抢购, 也叫秒杀(例如以前的口罩, 现在的茅台等)
1. 到时间直接抢购, 也叫秒杀(例如以前的口罩, 类似茅台(茅台现在限定App中抢购了😄))
2. 到时间先添加购物车 -> 到购物车提交订单(普通显卡预约抢购类)

写在前面

1. 模式 1 特别说明: 此模式商品展示无明显特征(console 控制台倒是可以打印对应参数区分).**`抢购时间有可能立即开始, 也有可能需要预约结束后再等一小段时间(5min左右)才可以抢购`**。需要不需要再等都遇到过。抢购时注意终端中会提示 `"当前流程是预约秒杀流程`, 这样的提示就是此模式.
2. 如果需要再等 1h 内, 程序会阻塞在此继续等待执行.超过一个小时会退出, 提示你修改时间后再重启
1. 模式 1 特别说明: 此模式商品展示无明显特征(console 控制台倒是可以打印对应参数区分).**`抢购时间有可能立即开始, 也有可能需要预约结束后再等一小段时间(几分钟到半个小时不等)才可以抢购`**。需要不需要再等都遇到过。抢购时注意终端中会提示 `"当前流程是预约秒杀流程`, 这样的提示就是此模式.
2. 如果需要再等, 提交会失败但是提示有货但是抢购失败.此时请手机或者浏览器查看状态.流程页处理一部分情况请注意打印文字提示
3. 模式 1 再说明: 区分流程的特征, 狗东最晚在开抢前几分钟内才设置, 程序现在已经支持自动区分了
4. 因为狗东有红包的话, 会自动勾选使用红包.为了抢购成功和快速, 配置文件中最好填写 6 位支付密码(保存密码的`config.js` **`不会从你本地上传到任何地方,请放心!!`**)
5. 已知 `windows` 系统自带终端打印出来的二维码错位, 请更换终端或者手动打开自动生成的本目录下`qrcode.png`文件扫码
6.`jd_by_mask` 改名 `jd_yuyue`.之前使用错别字防止搜索且买口罩也不符合现在仓库内容, 所以改名 yuyue 了
7.`jd_yuyue` 改名 `yuyue`, 被x东邮件通知下架. 避免出问题, 规避搜索

TG Group

Expand All @@ -38,11 +39,11 @@ const config = {
sckey: '', // string, messenger 是 true 则为必填, 不填写不影响抢购流程

// 必填,
// 方式1: 或者电脑打开 jd 登陆页面, devtools, 选择 `Elements`, 搜索 eid 和 sessionId(即fp), 在搜索到的input元素上面
// value 属性中的值复制过来
// 方式2: jd登录 pc 端, 随便提交一个订单, 利用谷歌浏览器devtools, 选择 `network`,
// 查看 submitOrder (注意要勾选 preserve log, 否则提交订单跳转后看不到该接口了) 接
// 口, 把接口提交的数据里面的 `eid`, 和 `fp` 俩个字段值分别粘贴过来
// 最新:用浏览器(最好chrome)打开本地文件夹下
// `get_eid_fp.html`文件, 将网页中`eid`,`fp` 填入配置中
// 其他方式: 或者电脑打开 jd 登陆页面, devtools, 选择
// `Elements`, 搜索 eid 和 sessionId(即fp),
// 在搜索到的input元素上面 value 属性中的值复制过来
eid: '', // string, 必填
fp: '', // string, 必填
// 6位支付密码如 '123456' 最好填上.如果当前账号有红包之类的则必填
Expand All @@ -61,39 +62,38 @@ module.exports = config;
4 配置`tasks-pool.js`(以前在*index.js* 中, 现在迁移出来了) 中设置日期 `date`, `forceKO`, `areadId`(如果已在 `config.js` 中设置了`areadId`, 这里无需再设置) 和要抢购的 `skuId`, 格式如下

```js
// **日期说明**
// 0是一月, 11是12月
// 年 月 日 时 分 秒 毫秒
// 2020, 0-11, 1-31, 0-24, 0-60 0-60 0-1000
//
// new Date(2020, 2, 4, 10, 0, 0, 400).getTime()
// 等于 2020-3-4 10:00:00.400

// 修改使用的时间
// **日期说明** 1-12月
// 0表示是一月, 11表示是12月
// 2020/3/3 10:00:00.400
const dd1 = new Date(2020, 2, 3, 10, 0, 0, 400).getTime();
// 2020/3/3 20:00:00.400
const dd2 = new Date(2020, 2, 3, 20, 0, 0, 400).getTime();
// 2020/3/3 21:00:00.400
const dd3 = new Date(2020, 2, 3, 21, 0, 0, 400).getTime();

// 修改这里, 添加skuId, 抢购时间 date, 强制使用秒杀流程 forceKO, areaId.
// 修改这里, 添加skuId, 抢购时间 date, 强制使用秒杀流程 forceKO(已经不推荐使用, 程序自动会判断), areaId.
// 需要更改 年/月/日 时:分:秒.毫秒
// skuId 获取方法, 打开任意一个商品详情页如 `https://item.jd.com/100011521400.html`, 则 `100011521400` 就是其skuId
exports.pool = [
// 1 *现在必须设置areaId*
// 如果上面`config.js`中已经配置过了, 此处可以不用再配置`areaId`, areaId 获取在第 5 步骤说明
// 2 forceKO: true/false
// 如果上面 `config.js`中已经配置过了, 此处可以不用再配置`areaId`, areaId 获取在第 5 步骤说明
// 2 forceKO: true/false(已经不推荐使用, 程序自动会判断)
// true 则为强制使用秒杀流程(自己100%确定流程是秒杀时开启.常见商品maotai)
// false 的话自动判断流程
// 3 注意有的商品在开抢前几分钟, 才把对应流程标志暴露出来,
// 所以想要使用程序自动判断流程准确
// 最好在开抢前很近的时候开启脚本, 这样就不会误判抢购模式
// 后续会改进

{ skuId: '100011621642', date: dd1, areaId: `2_2825_51936` },
// 新增同一时间抢购多个商品
// 不同时间, 像这样多项就可以了
// 同一时间的最好别多开哦, 容易触发频率限制导致提交全部失败
// 程序现在可以很好的支持此类多个商品的模式了
{ skuId: ['100011621642', '100011621643'], date: dd2, areaId: `2_2825_51936` },
// 不同时间, 复制上述项修改值, 填写在下方
];
// 设置要强制扫码登录(没搞懂使用场景的忽略此配置)
// 说明: 因为 x 东, 24小时就要重新登录, 防止运行时登录状态有效
Expand All @@ -111,7 +111,7 @@ exports.forceLogin = false;

## Todo
- [ ] 子进程推出前, dump cookie
- [ ] 合并抢购流程为添加购物车抢购流程的进程
- [x] 合并抢购流程为添加购物车抢购流程的进程
- [x] 狗东太恶心了, 区分流程的关键参数, 等到开抢前几分钟才获取到, 要想办法兼容
- [x] 如果预约时间结束, 还需要等待一小段时间后才抢购, 程序自动等待执行(1h 内)
- [x] 根据商品, 自动确定抢购流程
Expand Down
24 changes: 24 additions & 0 deletions get_eid_fp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<html>
<head></head>
<body>
<div id="info">获取中……</div>
<hr />
<div id="eid"></div>
<div id="fp"></div>
</body>
</html>

<script src="https://gias.jd.com/js/td.js"></script>

<script>
try {
getJdEid(function (eid, fp, udfp) {
document.getElementById('info').innerText =
'请把下面的内容复制到配置文件 config.js 中的对应位置';
document.getElementById('eid').innerText = 'eid = ' + eid;
document.getElementById('fp').innerText = 'fp = ' + fp;
});
} catch (e) {
document.getElementById('info').innerText = e;
}
</script>
11 changes: 4 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ try {
process.exit();
}

const {
login,
submitOrderProcess,
} = require('./jobs');
const { login, submitOrderProcess } = require('./jobs');
const { pool, forceLogin } = require('./tasks-pool');
const config = require('./config');
if (!config.eid || !config.fp) {
Expand All @@ -36,10 +33,10 @@ if (cluster.isWorker) {
const { date, skuId, areaId = config.areaId, forceKO = false } = item;
// 任务进程
console.log(
'progress.worker:',
'process.worker:',
process.pid,
'时间:',
item.date,
new Date(item.date).toLocaleTimeString('en-US', { hour12: false }),
'sku',
item.skuId
);
Expand Down Expand Up @@ -79,5 +76,5 @@ if (cluster.isWorker) {
}
});

console.log('progress.master:', process.pid);
console.log('process.master:', process.pid);
}
110 changes: 70 additions & 40 deletions jobs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const toolsClass = require('./tools');
const helper = new toolsClass();
const ToolsClass = require('./tools');
const helper = new ToolsClass();
const timer = require('./timer');
const sleep = helper.sleep;

// 登录
async function login(directly = false) {
Expand All @@ -20,9 +21,7 @@ async function login(directly = false) {
const res = await helper.checkLoginStatus();
if (res.code !== 200) {
console.log(`code: ${res.code}, message: ${res.msg}`);
await new Promise(r => {
setTimeout(r, 2000);
});
await sleep(2000);
} else {
console.info('已完成手机客户端确认');
ticket = res.ticket;
Expand Down Expand Up @@ -79,7 +78,7 @@ async function submitOrderFromItemDetailPage(
process.exit();
}
});
await new Promise(r => setTimeout(r, 100));
await sleep(100);
}
});
}
Expand Down Expand Up @@ -129,7 +128,7 @@ async function checkItemState(skuId, params, retry = 30) {
console.log('查询预约信息失败:', i, e);
}
if (i) {
await new Promise(r => setTimeout(r, 80));
await sleep(100);
}
}
// 不可用, 不是state ===4, 且有剩余时间
Expand Down Expand Up @@ -195,13 +194,10 @@ async function submitOrderFromShoppingCart(date, skuIds, params = {}, area) {
}

timer(date, async () => {
// let [isAvailable] = await checkItemState(skuId, params, 1);
// if (!isAvailable) {
// console.log('哈哈又被耍猴了!');
// process.exit();
// }
let isAvailable = false;
let i = 30;
// 不要更改 7, 超过七很容易触发京东频率限制
let i = 7;
let loopTime = 0;
while (i--) {
try {
await Promise.race([
Expand All @@ -222,23 +218,32 @@ async function submitOrderFromShoppingCart(date, skuIds, params = {}, area) {
console.log('访问结算页面成功, 准备提交订单');
break;
} catch (e) {
console.log('访问结算页面失败:', e.message);
console.log('访问结算页面失败:', e.message || message);
}
if (i === 0 && loopTime === 0) {
i = 7;
loopTime++;
await sleep(loopTime * 1000);
}
await new Promise(r => setTimeout(r, 100));
// loopTime 0: 50
// loopTime 1: 100
await sleep(loopTime * 50 + 50);
}
if (!isAvailable) {
console.log('访问结算页面彻底失败, 溜了');
process.exit();
}
i = 20;
i = 10;
while (i--) {
try {
const res = await helper.submitCartOrder();
if (res.success) {
const now = new Date();
const text = `订单提交成功!订单号:${
res.orderId
},时间:${now.toLocaleTimeString()}.${now.getMilliseconds()}`;
},时间:${now.toLocaleTimeString('en-US', {
hour12: false,
})}.${now.getMilliseconds()}`;
console.log(text);
await helper.sendToWechat(text);
process.exit();
Expand All @@ -257,7 +262,7 @@ async function submitOrderFromShoppingCart(date, skuIds, params = {}, area) {
} catch (e) {
console.log('抢购失败:', i, e);
}
await new Promise(r => setTimeout(r, 300));
await sleep(650);
}
});
}
Expand Down Expand Up @@ -335,6 +340,7 @@ async function submitOrderProcess(date, skuId, areaId, forceKO = false) {
const allSKUParam = {};
const skuIds = Array.isArray(skuId) ? skuId : [skuId];
const isKOSet = new Set();
const errorSet = new Set();
const beforeRunTaskMinues = Math.round((date - Date.now()) / 1000 / 60) - 2;
// < 0, 0
// > 6, 6
Expand All @@ -345,34 +351,58 @@ async function submitOrderProcess(date, skuId, areaId, forceKO = false) {
: beforeRunTaskMinues < 6
? beforeRunTaskMinues
: 6;
while (1) {
const now = Date.now();
if (now + m * 60 * 1000 >= date) {
for (let i = 0; i < skuIds.length; i++) {
const skuId = skuIds[i];

if (m === 2) {
await Promise.all(
skuIds.map(skuId => {
if (isKOSet.has(skuId)) return;
const [isKO, params] = await getPageConfig(skuId, areaId);
allSKUParam[skuId] = params;
if (isKO || forceKO) {
isKOSet.add(skuId);
return getPageConfig(skuId, areaId)
.then(([isKO, params]) => {
if (isKO || forceKO) {
isKOSet.add(skuId);
}
allSKUParam[skuId] = params;
})
.catch(_ => {
errorSet.add(skuId);
console.log(`${skuId},访问详情页出错`);
});
})
);
} else {
await new Promise(resolve => {
let id = setInterval(async () => {
const now = Date.now();
if (now + m * 60 * 1000 >= date) {
for (let i = 0; i < skuIds.length; i++) {
const skuId = skuIds[i];
if (isKOSet.has(skuId)) return;
const [isKO, params] = await getPageConfig(skuId, areaId);
allSKUParam[skuId] = params;
if (isKO || forceKO) {
isKOSet.add(skuId);
}
if (i < skuIds.length - 1) {
await new Promise(r => setTimeout(r, 1000));
}
m--;
}
}
if (i < skuIds.length - 1) {
await new Promise(r => setTimeout(r, 1000));
if (now >= date || forceKO) {
clearInterval(id);
resolve();
}
m--;
}
}
if (now >= date || forceKO) {
break;
}
if (isKOSet.size === skuIds.length || m <= 1) {
// 所有sku都是秒杀商品或者开抢前一分钟没变化
break;
}
if (isKOSet.size === skuIds.length || m <= 1) {
// 所有sku都是秒杀商品或者开抢前一分钟没变化
clearInterval(id);
resolve();
}
}, 10000);
});
}

// 购物车商品
const cartSkuIds = skuIds.filter(s => !isKOSet.has(s));
const cartSkuIds = skuIds.filter(s => !isKOSet.has(s) && !errorSet.has(s));
if (isKOSet.size > 0 || forceKO) {
console.log('当前流程是预约秒杀流程, 从详情页面直接提交订单的!');
console.log('请留意窗口打印信息');
Expand Down
4 changes: 4 additions & 0 deletions tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class Tools {
// this.reqTools.Cookie.fromJSON()
}

sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}

getLoginStatus = async () => {
if (this.isLogin) return true;
else {
Expand Down

0 comments on commit 2b25505

Please sign in to comment.