20200816更新Olivia Wang:大麦脚本更新,辅助选座功能上线zhuanlan.zhihu.comOlivia Wang:我要认真写脚本教程啦zhuanlan.zhihu.com看完这个 Chat,人人都可以完成浏览器自动化gitbook.cn
我不知道为什么js脚本总是刷新一下就停了,干脆更新了脚本。Olivia Wang:我知道你们又在找抢周杰伦演唱会门票的脚本了zhuanlan.zhihu.com
热门演唱会门票不到一分钟就可能卖光,不知道要多快的手速才能一鼓作气点点点及时下单。走一下神可能就不得不选个差一点的区域,甚至只能高价求票了。
好朋友遇到了这样的烦恼,我一想这种比较机械的刷新其实很适合用脚本完成呀,所以就花了一个下午有了这个脚本。
目前还是比较有局限性的:稳定性没有100%保证,如果是很重要的票可能一边开着电脑用脚本,一边自己用手机刷新更保险一些,做好两手准备
需要手动在脚本中修改人数
只支持大麦演唱会,不支持歌剧话剧,比赛等
不支持选座
需要事先登录好,填好相关的地址,人员信息
UI太粗糙啦
根据自己和小伙伴的测试来看,如果是火到大麦会崩的演唱会(比如今天开票的张艺兴上海演唱会),那么有了脚本还是一样听天由命。没有这么夸张的使用起来暂时没问题。
如果你想直接使用现成的脚本 → 从安装Tampermonkey到成功抢到演唱会门票
如果你对怎么写脚本感兴趣 → 怎么写一个抢票脚本
从安装Tampermonkey到成功抢到演唱会门票
建议用Chrome浏览器使用这个脚本因为我就是在Chrome写的,其他浏览器没怎么测试,大家试用后有什么问题可以给我留言提意见反馈。
Tampermonkey可以很方便的开关脚本是否运行,抢完票后记得从Tampermonkey关掉脚本,不然浏览其他表演信息可能直接进入支付宝付账界面。
使用步骤
1. 用Chrome浏览器打开Tampermonkey官网,点击按钮下载安装
2. 关注公众号「伪装程序大佬」(wzcxdl_cs),发送dmtk取得脚本地址
3. 通过地址打开greasy fork的页面,点击安装按钮自动跳转到Tampermonkey
4. 点击安装按钮
5. 打开大麦网的页面,现在应该就可以看到按钮和提示了
6. 根据提示修改「大麦抢票-选场次票价人数」中people_num为相应人数
7. 刷新页面加载新版脚本
8. 点击想要的场次,票价
9. 点击“开始抢票”
10.等待提示音响起
11.支付宝付款
12.抢票成功~
如果报错
1. 可以打开开发者工具,如果报错一般是因为加载速度有点慢,可以适当放慢页面刷新速度或者换成更快的网络。
2. 如果出错建议运行前先清除localStorage中的isRunning。然后重新加载页面。
怎么写一个抢票脚本
开始前的准备:安装浏览器,比较推荐Chrome
安装Tampermonkey (详情见「从安装Tampermonkey到成功抢到演唱会门票」)
知道一点JavaScript
看看大麦网演唱会的网页是什么结构
观察大麦网购票流程
随便选一个演唱会页面看看。
观察大麦网演唱会购买网页我们可以发现当演唱会有票的时候,我们可以选择场次,选择票档,选择数量,点击「立即购买」(上图是预定票所以是「立即预订」),然后会跳转到确认页面,需要我们选择地址,观演人,支付方式然后按下按钮进行预订。如果只有一个默认地址,在确认页面实际需要我们做的只是点击选中观演人然后提交订单。
当票已经卖光或者还没有开始销售的时候按钮上的文字是「提交缺货登记」,「提交开售登记」,并且没有选择数量的地方。
思考抢票脚本流程用户登录,填写地址,观演人信息
用户手动点击选择场次,票档 (为什么没有数量?因为需要抢票的页面没有选择数量的地方),在脚本中修改数量
用户点击「开始抢票」,脚本读取当前选择的场次,票档,
刷新页面,脚本点击相应场次,票档
检查有没有调节数量的控件出现,如果没有,回到4
根据脚本中数量调成数量控件
检查购买按钮上是否是「立即购买」字样,如果不是,回到4
点击「立即购买」按钮,跳转到确认页面
选择观演人
点击「同意以上协议并提交订单」 (同时发出声音提醒用户)
支付宝付款
成功抢到票~
需要我们写的是2-10的部分。Tampermonkey会根据url判断执行什么脚本,所以我们可以写两个文件。
大麦抢票-选场次票价人数 2-8
大麦抢票-确认 9-10
具体写法
大麦抢票-确认
从逻辑上来讲应该先写「大麦抢票-选场次票价人数」,但是「大麦抢票-确认」简单很多。所以先写这部分吧。
点击新建之后会出现一个模版文件。不要删掉上方的注释,非常重要。
// ==UserScript==// @name New Userscript// @namespace http://tampermonkey.net/// @version 0.1// @description try to take over the world!// @author You// @match https://www.example.com// @grant none// ==/UserScript==
具体每个字段的含义可以查询Tampermonkey官网。
// ==UserScript==// @name 大麦抢票-确认// @namespace https://www.jwang0614.top/scripts// @version 0.1// @description 辅助购买大麦网演唱会门票// @author Olivia Wang// @match https://buy.damai.cn/orderConfirm*// @grant none// ==/UserScript==
对我们来说最重要的是@match。其他@name,@description之类怎么填对我们要写的这个脚本执行都没有太大影响。@namespace是用来在@name相同时区分脚本的,可以用任何独特字符串,不过一般用自己个人主页url的比较多。
@match的重点是最后的*号通配符,这样只要url前面包含https://buy.damai.cn/orderConfirm, 无论Confirm之后跟的是什么有多长都能匹配上。不同网页的脚本@match的字符所规定的匹配规则会有变化。
具体代码:
(function() {
'use strict';
console.log("confirm");
// 点击观演人 var person = document.querySelector('#confirmOrder_1 > div.dm-ticket-buyer > div.ticket-buyer-select > div.next-row.next-row-no-padding.buyer-list > div > label > span.next-checkbox.isFirefox > span');
if (person) {
person.click();
}
// 播放提示音 var audio = new Audio("http://audio.marsupialgurgle.com/audio/successtrumpet.mp3");
audio.play();
// 提交订单 document.querySelector('#confirmOrder_1 > div.submit-wrapper > button').click();
})();
虽然应该先成功提交再放庆祝提示音,但是点击提交后页面就跳转到支付宝了,所以这里是先播放提示音再点击提交订单。
有的确认页不需要选择观演人,所以要做个判断,不然在这一步会报错。
querySelector的那一长串直接用Chrome的开发者工具就可以获得:
点击红框中的按钮,然后在页面中点击选中你需要查看的元素,html中相关的元素会高亮显示。右键相应的html元素,选择复制selector,就可以得到那一长串字符了。
用document.querySelector就可以获取相应元素。
需要注意的是有些页面中元素会有变化,比如多一个少一个场次或者多一个少一个div什么的,直接复制的selector字符串可能含有类似:nth-child(6)的部分,这个数字可能会有变化,需要找到更加有普遍性的写法。比如根据有唯一classname的sibling节点定位之类的,大家可以多检查一下看看不同状态不同网页上selector是不是都能适用。
大麦抢票-选场次票价人数
我们可以把这个脚本分成几个部分UI开始抢票按钮
结束抢票按钮
提示
从页面获取用户输入场次票价
刷新
根据用户输入填入数据
判断能不能购买,如果可以点击按钮,如果不行再次刷新
LocalStorage
我们希望能刷新后保存用户场次票价人数信息,所以用到LocalStorage。
var people_num = 2;
var isRunning = false;
var storage = window.localStorage;
storage.setItem("people_num", people_num);
storage.setItem("isRunning", isRunning);
还没开票的页面也没有数量控件,所以通过脚本控制购票数量,储存到LocalStorage中。isRunning用来判断是不是在运行。
有的浏览器可能不太支持LocalStorage,可以在程序最开始判断一下.
if(!window.localStorage){
alert('不支持这个浏览器,请换成Chrome或者Safari。');
}
UI
添加两个固定在页面上的按钮和一个提示栏。比如「开始抢票」按钮
// 创建一个p标签var start = document.createElement("P");
// 添加文字start.appendChild(document.createTextNode("开始抢票"));
// 设置格式,位置start.style.lineheight="50px";
start.style.color="white";
start.style.fontSize="30px";
start.style.padding="10px 20px";
start.style.background="green";
start.style.position="fixed";
start.style.right="30px";
start.style.top="100px";
// 保持在最上方start.style.zIndex="10000";
// 获取dom中body元素var container = document.querySelector('body');
// 将start按钮添加到dom中container.appendChild(start);
从页面获取用户选择的场次,票价信息
function get_numbers_from_page() {
var event_selections = document.querySelectorAll('body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div.perform__order__select.perform__order__select__performs > div.select_right > div > div');
// 这里就用了sibling node定位 div.perform__desc__info + div // “+ div” 代表下一个div sibling var price_selections = document.querySelectorAll('body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div.perform__desc__info + div > div.select_right > div > div');
for (var i= 0;i < event_selections.length;i++) {
// 通过class中是否含有active判断用户选择的是第几个选项,将结果数字保存在本地存储中 if (event_selections[i].classList.contains("active")) {
storage.setItem("event_ele_num", i);
}
}
for (var j= 0;j < price_selections.length;j++) {
if (price_selections[j].classList.contains("active")) {
storage.setItem("price_ele_num", j);
}
}
}
一段时间后刷新页面
function timedRefresh(timeoutPeriod) {
window.setTimeout("location.reload(true);",timeoutPeriod);
}
填写数据,判断当前能不能购买
function set_up_check_page() {
// 从storage中获取场次,票价,数量信息 var event_ele_num = storage.getItem("event_ele_num");
var price_ele_num = storage.getItem("price_ele_num");
var people_num = storage.getItem("people_num");
// 为了更直观地表现出“程序正在运行”,我把网页背景换了一个颜色 if (storage.getItem("isRunning") == "true") {
document.querySelector('body > div.perform').style.background="darksalmon";
}
// 获取所有的场次元素,点击相应元素选择 var event_selections = document.querySelectorAll('body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div.perform__order__select.perform__order__select__performs > div.select_right > div > div');
event_selections[event_ele_num].click();
var price_selections = document.querySelectorAll('body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div.perform__desc__info + div > div.select_right > div > div');
price_selections[price_ele_num].click();
// 判断有没有数量控件,如果有设定数量,如果没有继续刷新 var people_selection = document.querySelector(".cafe-c-input-number-input");
if (people_selection) {
// 这里我用的是点击增加按钮,其实可以通过修改value的值实现 var people_inc_btn = document.querySelector('body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div:nth-child(6) > div.number_right > div > div > a.cafe-c-input-number-handler.cafe-c-input-number-handler-up');
for (var i =1; i < people_num; i++) {
people_inc_btn.click();
}
// 判断有没有“立即购买“按钮 var btn = document.querySelector("body > div.perform > div > div.flex1 > div.hd > div > div.order > div.perform__order__box > div:last-child > div");
if (btn) {
if (btn.innerText == "立即购买") {
// 点击立即购买按钮跳转到确认页 storage.removeItem("isRunning");
storage.removeItem("price_ele_num");
storage.removeItem("event_ele_num");
storage.removeItem("people_num");
storage.clear();
btn.click();
}
}
}
// 如果正在抢票,0.4秒后刷新页面 if (storage.getItem("isRunning") == "true") {
timedRefresh(400);
}
}
通过按钮控制脚本的开始和停止
// 开始抢票按钮start.onclick = function() {
console.log('开始抢票!');
document.querySelector('body > div.perform').style.background="darksalmon";
storage.setItem("isRunning", true);
get_numbers_from_page();
timedRefresh(600);
};
// 停止抢票按钮stop.onclick = function() {
alert('停止抢票!');
document.querySelector('body > div.perform').style.background="white";
//storage.setItem("isRunning", false); storage.removeItem("isRunning");
};
因为怕刷新太快来不及按「停止抢票」,我加入了快捷键。每个字母的keyCode可以在网上找到。
document.onkeydown = function() {
var oEvent = window.event;
if (oEvent.keyCode == 69 && oEvent.ctrlKey) {
//alert("你按下了ctrl+E"); // start start.click();
}else if (oEvent.keyCode == 84 && oEvent.ctrlKey) {
//alert("你按下了ctrl+T"); // stop stop.click();
}
}
刷新
function reload_page() {
// console.log("reload"); window.setTimeout(set_up_check_page,800);
}
组合
将以上内容补全完整组合到一起就可以啦,完整的代码请关注公众号「伪装程序大佬」(wzcxdl_cs),输入dmtk获取下载地址。
现在代码已经更新了一些,具体更新了什么请看下篇文章,不过答题思路是没什么变化的。
有什么问题和意见欢迎大家在下面给我留言~如果脚本运行出了什么问题也可以告诉我,我看看能不能改一改。