我的 MyGO!!!!!上海演唱会抢票历程

当我得知我喜欢的乐队 MyGO!!!!!将于上海开演唱会时,我内心十分激动。然而,票务代理却是臭名昭著的 EUPD,他们以放票少,官牛多而出名,让无数抢不到票的粉丝伤透了心。

当抢票信息公布后,我并不打算直接用手机去抢票,因为这样成功率实在是太低了,感觉就跟原神无保底单抽出金的概率差不多吧。

作为一个还算懂点技术的人,我决定利用我平时所学,打造出几个抢票利器,来助我一臂之力。

自动化连点器

抢票开始时,我们一般都会拼了命地去点击抢票界面上的按钮。

但是呢,人点击按钮,是会有时间差的,你无法保证在开票时正正好好地点到按钮,总是会有个几十毫秒的误差,连点亦是如此,你 1 秒钟连点按钮的次数一般最多不会超过 10 次。尽管这对于一般的演唱会来说不算什么,但对于那种特别火热的演唱会,错过几毫秒,错过几次连点可能也会让你痛失机会。

是不是感觉自己太弱小了?

DIO 曾说过:人是有极限的,正因如此才要超越人类!

Jojoios-4.jpg

那我们如何在现实中获得这般“超越人类”的力量呢?答案是程序。

我们人做动作基本都是以秒为单位来计算的,然而,对于程序的运行速度来说,基本都是以毫秒为单位计算的,秒和毫秒可是相差了足足 1000 倍。

知道了这一点,那我们就可以让程序来帮我们连点,不仅能在操作速度上有巨大的提高,而且程序自带的定时器可比我们自己估算的时间要准的多,基本可以消灭开抢时时间上的误差。

接下来,我们将使用AutoX.js这个强大的程序来实现我们的抢票大业,注意这个程序目前来说只有安卓版的,并没有其他环境(如 ios)的版本。

安卓环境

首先,你需要一个安卓手机,用来提供程序运行所需要的环境。

有的人会说:emmmm 我是苹果机啊,总不见得为了抢票特地去买个安卓机吧?

没有安卓机怎么办?也行!我们可以下一个安卓模拟器,在电脑上就可以运行。模拟器的话个人推荐用网易的mumu 模拟器

安装程序

AutoX.js的 apk 可以在 Github 的Releases 页下载到。

对于手机而言,可以把 apk 先打包成压缩包,然后通过一些平台(如微信)进行传输,再下个解压缩的软件,解压缩后即可进行安装。

模拟器就简单了,直接把 apk 拖上去,就可以安装。

准备工作

安装完成后,我们直接进入 AutoX.js 这个软件。

打开无障碍服务

点击左上角的三道杠。

Snipaste_2024-09-10_14-18-47.jpg

找到“无障碍服务”,将它后面的开关打开,它会弹出一个提示,按提示去设置里打开应用的无障碍选项就行了。

Snipaste_2024-09-10_14-20-10.jpg

显示指针位置

我们写程序时,有时需要知道我们目前的手指点在手机的哪个位置。

找到“系统应用-设置-关于手机”,最底下有个版本号,连点几下,直到它提示你进入了开发者模式,就 OK 了。

Snipaste_2024-09-10_14-25-08.jpg

然后,我们回到“设置-系统”,里面会有一个“开发者选项”,进去,找到“指针位置”,打开它。(上面有个“显示点按操作反馈”建议也打开下)

Snipaste_2024-09-10_14-30-28.jpg

你会发现画面上方多了一些数据,其中 dX 和 dY 就是指针位置的数据了,当我们点击手机或模拟器上的一个位置时,它们的值会跟着改变。

拾取控件信息

当我们浏览和操作 app 时,会接触 app 的很多元素(如点击按钮等),这些都是 app 的控件。

想在脚本里操作 app 的控件的话,往往需要知道控件所包含的信息才能选中并操作它,最直观的是它所包含的文字。

除了文字外,我们还可以通过工具来拾取它的控件名称等信息。

回到 AutoX.js 软件,点击左上角的三道杠。

Snipaste_2024-09-10_14-18-47.jpg

找到“悬浮窗”,将它后面的开关打开。(如果有询问权限的话直接打开权限开关)

Snipaste_2024-09-10_14-56-46.jpg

这样我们就能在画面左侧发现一个悬浮菜单,点击菜单,它会自动展开,再点击它的第三个按钮。

Snipaste_2024-09-10_14-58-15.jpg

会出现一个弹窗,选择“布局范围分析”。

Snipaste_2024-09-10_14-59-51.jpg

然后它会把整个页面的结构都显示得清清楚楚。

Snipaste_2024-09-11_15-23-22

点击最下面的“缺货登记”按钮,再点击“查看控件信息”,我们就能看到这个按钮的所有控件信息了。

Snipaste_2024-09-11_15-27-03

AutoX.js 甚至还贴心地准备了一个自动生成代码的功能,再次点击按钮,选择“自动生成代码”,它就能直接生成供脚本使用的代码,而且各种场合都考虑到了。

我们试试生成这个脚本:找到“缺货登记”按钮并点击它。

Snipaste_2024-09-11_15-31-45.jpg

生成的代码如下:

1
className("android.widget.TextView").text("缺货登记").findOne().click();

连接电脑

我们先在自己的编辑器里安装一个扩展,以 vscode 为例,扩展名叫“Auto.js-Autox.js-VSCodeExt”,安装好后是这样的:

Snipaste_2024-09-11_16-06-55

按下“Ctrl+Shift+P”快捷键,搜索 autoxjs,找到“开启服务”这个命令并点击它:

Snipaste_2024-09-11_16-09-39

画面右下角会显示连接的服务器地址:

Snipaste_2024-09-11_16-10-55

回到 AutoX.js 软件,点击左上角的三道杠。

Snipaste_2024-09-10_14-18-47.jpg

找到“连接电脑”,将它后面的开关打开。

Snipaste_2024-09-11_16-01-47

它会提示你输入服务器地址,就输入刚刚右下角弹出来的服务器地址,再加一个ws://的前缀,因为服务器用的是WebSocket来连接的,输完后点击“确定”按钮,即可完成连接。

Snipaste_2024-09-11_16-13-43

注意:我用的是模拟器,可以直接连接,如果用真机的话,可能会有风控应用禁止连接,目前还没想到用什么办法来解决。

写脚本代码

所有的准备工作完成后,我们总算可以着手开始写代码了。

写代码前,建议先通读下 AutoX.js 的官方文档,了解它常用的 API 函数。

我们先大致理一下脚本所要做的逻辑:抢票开始前一直等待,当购票按钮出现时立刻点击它进入选票页面,然后在选票页面拼命地点“确认”按钮,直到“立即支付”按钮出现后,我们就算进入了最后一个关卡——确认支付页,这里就不停地点“立即支付”按钮就可以了。如果抢票人数过多,就可能会跳出“前方拥堵”的提示,我们再创建一个子线程,用来专门点掉这个提示的“刷新”按钮。

我写的脚本一定程度上参考了这个:https://github.com/Pactum7/ticket-grabbing/blob/main/MaoYan/MaoYanGoNew.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// 检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。
auto.waitFor();
//打开猫眼app
app.launchApp("猫眼");
openConsole();
console.setTitle("猫眼 go!", "#ff11ee00", 30);

//确认选票坐标,建议配置(不配置时仍会寻找“确认”按钮进行点击,但可能会出现点击失败的情况)
const ConfirmX = 734;
const ConfirmY = 1532;

//是否已进入订单页,如果进了前面的逻辑都不用管,直接一直点支付按钮就完事了
const havEnterOrderPage = false;

main();

function main() {
//注意:抢票前,观演人别忘了自己提前预约下
console.log("开始猫眼抢票!");

if (!havEnterOrderPage) {
//出现刷新按钮时点击刷新
threads.start(function () {
console.log("刷新按钮自动点击线程已启动");
while (true) {
textContains("刷新").waitFor();
textContains("刷新").findOne().click();
console.log("点击刷新...");
//避免点击过快
sleep(100);
}
});

console.log("等待开抢...");
while (true) {
//购票按钮有多种显示情况,都要考虑到
var but1 = textContains("立即预订").exists();
var but2 = textContains("立即购票").exists();
var but3 = textContains("特惠购票").exists();
var but4 = textContains("特惠预订").exists();
var result = but1 || but2 || but3 || but4;
if (result) {
var s;
if (but1) {
var s = textContains("立即预订").findOne().click();
} else if (but2) {
var s = textContains("立即购票").findOne().click();
} else if (but3) {
var s = textContains("特惠购票").findOne().click();
} else if (but4) {
var s = textContains("特惠预订").findOne().click();
}
break;
}
}
console.log("①准备确认购票");

//猛点,一直点到出现支付按钮为止
for (let cnt = 0; cnt >= 0; cnt++) {
//绝对坐标点击
click(ConfirmX, ConfirmY);
//文字查找按钮点击,避免未正确配置坐标导致的点击失败
if (text("确认").exists()) {
text("确认").click();
}
sleep(50);
if (classNameStartsWith("android.widget.").text("立即支付").exists()) {
console.log("支付按钮出现");
break;
}
if (cnt % 20 == 0) {
log("已点击确认次数:" + cnt);
}
}
console.log("②准备确认支付");
}

// 开冲!!!
for (
let cnt = 0;
classNameStartsWith("android.widget.").text("立即支付").exists();
cnt++
) {
//直接猛点就完事了
var c = classNameStartsWith("android.widget.")
.text("立即支付")
.findOne()
.click();
var sleepTime = havEnterOrderPage
? 400 + Math.floor(Math.random() * 250)
: 50;
sleep(sleepTime);
if (cnt % 20 == 0) {
log("已点击支付次数:" + cnt);
}
}

console.log("结束");
}

写好脚本后,我们就可以点击右上角的运行按钮,来运行这个脚本了。

Snipaste_2024-09-11_16-29-22

注意:在运行脚本前,你需要提前登录好,并且填完所有预约信息,再进入演出页面启动,才能让脚本正常地运行。

还有一点:像猫眼这类 app,是会不停地更新换代的,可能过了一段时间后,app 的 UI 界面会发生一定的变化,那么你之前写好的脚本就可能会失灵!这时你就要重新开始拾取新界面上的控件数据,调整点击的坐标,修改你的脚本代码了。

改包提前进抢票页

在抢票开始之前,界面是长这个样的:

IMG_6012

你点击下方的“已预约”按钮,只会进入预选择票档页,而不会进入正式的选票页,因为开票时间还没到嘛。

那么有没有一种办法,来“欺骗” app 以为已经到了开票时间,而显示出购票按钮呢?答案是有的。

我用的是 iphone,就以 ios 系统为例吧。

进入 App Store,搜索“Storm Sniffer”,安装这个 app,它的图标是一只“小螃蟹”,下文也这么称呼它。

IMG_6028

它的完整功能需要付费 18 元,我是觉得为了抢票这点小钱也无所谓了,当然如果你不愿意付费的话,可以直接跳过这一部分~

解锁小螃蟹后,我们会重点使用它的“抓包”和“重写”功能。

我们先要开启 MITM,用来解密 app 的 HTTPS 流量,这个只要根据它的说明一步步做就行,开启后,我们就可以开始抓包了。

抓包

小螃蟹里打开抓包开关,再点“启动”按钮。

我们在 app 上找一个已经开票的演唱会,进入页面,等页面加载完毕后,再回到小螃蟹,点击“停止”按钮。

点击抓包的“历史记录”,我们就能看到抓到的所有的包了。

找到演出详情的包,它的链接里包含“/my/odea/project/detail”,查看它的响应数据。

IMG_6031

然后进一个还没开票的演唱会,重复上面的步骤。

IMG_6034

将两者对比下,我们就能发现跟按钮显示相关的 2 个数据不一样:saleStatusticketStatus,未开票时,两者的值都是 1,开票后,两者的值变成了 3。

重写

我们的目的,就是要把未开票的界面变成已开票的界面。

从上面可以得知,为了做到这一点,我们可以把包里的数据进行“重写”,比如saleStatus原本是 1 的,将它改写为 3,不就行了吗。

回到小螃蟹,点击“重写规则”,再点击右上角的加号,即可添加新的规则。

IMG_6036

以上则是我个人的规则,主要就是对特定链接的响应进行文本替换。

添加完成后,我们返回规则列表,勾选好我们新增的规则,再返回主界面,打开重写开关,启动小螃蟹。

回到上次未开票的那个演出,我们就会惊讶地发现它的按钮真的变成了已开票时的按钮“立即购票”!

IMG_6037

不过呢,先别高兴的太早。我们点击购票按钮后,尽管会跳转到购票界面,但它会显示出这个结果:

IMG_6038

啊嘞?不是已经骗过了 app 吗?怎么它还会提示这个?

nonono,其实进入购票页面时,app 还是会请求购票的数据,刚刚我们修改的仅仅是演出的数据,跟它是两码事,由于演出实际上未开始,购票的数据肯定也会返回未开票时的数据,到这里重写就暂时无能为力了。

话虽如此,但我们已经可以比其他人提前进购票页面了不是吗?

只要抓准时机,在开票前的 1 秒内提前点“立即购票”按钮,我们就能提前几百毫秒进入购票页。可别小看这点时间,在抢票面前,时间就是金钱!

那么如何把握住时间呢?我们需要一个精确到 0.1 秒的悬浮时钟,App Store 上搜索“ZK 助手”并安装它。

打开“ZK 助手”,点击“开启画中画”按钮。

如果你是 iPhone14Pro 以上的机型,也可以打开“显示到灵动岛”这个开关。

IMG_6041

返回 app,你就能看到悬浮时钟已经出现在某个角落了,同时小螃蟹开着的话,下方的按钮也是“立即购票”。

IMG_6042

当抢票即将开始前,你就可以等时钟走到最后 1 秒的某一刻,点击“立即购票”按钮,比别人抢先进入购票页了。

注意万万不能提前点,否则会出现“演出信息发生变化”的提示,返回会拖延几秒时间,直接导致你的抢票 GG。

但就算提前进了,也不排除有人也会用这招,因此不能松懈,战斗才刚刚开始!

监控回流票

当你完成抢票这场激烈的战斗后,你可能赢了,也可能输了。

技术,只能大大提高你成功的概率,然而,并不能确保你百分百能抢到你想要的票。

倘若票秒空了,你依旧没能抢到票,不要放弃,因为可能还会有回流票。

也不排除回流票很快就被其他人抢掉的情况,这时就得看你的运气了。

运气好,你就能蹲到并抢到回流票;运气不好,你和回流票会不停地失之交臂。

不论结果如何,你得知道啥时候有回流票。

我在网上找了个 python 写的回流票监控脚本,地址:https://github.com/ThinkerWen/TicketMonitoring

用起来也很简单,你只需在 config.json 中配置好演出的 id(可通过 app 抓包获得)、名称、平台(猫眼是 1)以及监控截止时间,就能直接运行脚本进行监控了。

Snipaste_2024-09-11_17-13-56

如果你想在有票的时候收到手机上的通知,你需要再装一个发通知的 app,App Store 上搜索 Bark 并安装它。

IMG_6043

打开 Bark app 后,将链接https://api.day.app/后面的一串代码复制到电脑上,替换掉 Monitor.py 的”BARK_PUSH_KEY”,再开启脚本,就能在检测到回流票的时候给手机发通知了。

保持脚本与接口同步

实在不巧,我找的回流脚本其实已经失灵了,因为猫眼已经更新了演出的相关接口,当我得知这一点后顿时感觉之前都白监控了。

既然如此,就只好把脚本也更新一下了,让它和新接口相同步。

Snipaste_2024-09-16_11-01-58

确保所有设备时间同步

有一个很容易被忽视的一个点:所有抢票设备的时间是否和当前的时间同步?

如果同步,OK 没什么问题。

如果不同步,我们要手动同步一下最新的时间。

同步电脑时间

以 win10 系统为例,打开“设置-时间与语言”,点击“同步时钟”,即可完成同步。

Snipaste_2024-09-14_20-44-47

然后再重启下模拟器,看下抢票底部的倒计时是否跟手机一致,一致的话就表明同步成功了。

同步手机时间

手机上,我们通过 ZK 助手来看时间的,但这个 app 的时间往往会跟实际的时间有一定的误差,只要对比下电脑就可以得知。

我们可以把时间源锁定在“北京时间”。

IMG_6055

我个人的抢票经历

如你所见,我用了上文写到的所有技术,来抢 MyGO!!!!!的这次 live。

第一轮,我很有自信,直接瞄准 1380 档,结果令我万万没想到的是这一档竟然是最先被抢完的,而且 1 秒钟都不到,我的脚本和小螃蟹全都被卡在了支付页,提示“库存不足”。

第二轮,我决定退一步,选择少难(?)一点的 1180 档,为了确保抢到甚至还找了个代抢,结果还是梅开二度,我和代抢双双阵亡……

第三轮,就放几张票,有抢到的可能吗?别想了。

至于回流票?说实话我都没意识到当时用的是过时的监控脚本,直到两轮都抢完后才发现,唉。

花了这么大的功夫抢票,最终依旧是一场空。错的不是我,是扣弄塞该哒!

最后

祝愿大家都能抢到自己想抢的票,就算没抢到,也绝不要向黄牛屈服!

Author: alphardex
Link: https://alphardex.github.io/mygo/posts/59816/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.