插件实现12306网站“按预填信息”自动抢票

简介: 年底抢票回家过年,12306的“按预填信息购票”功能却隐藏按钮,导致抢票困难。程序员发现按钮被CSS隐藏后尝试手动修改,但遇到自动恢复和确认窗口的问题。最终决定开发Chrome插件,通过监听页面按钮自动点击,实现一键抢票。该插件结构简单,包含manifest.json、popup.html等文件,能有效节省抢票时间。代码已开源至GitHub,供有需要的人参考使用。

抢票那些事

年底了,为了回老家过年,大部分同学都需要抢票,12306提供了"按预填信息购票"的功能,先把购票信息填好,到点直接抢票,但是这个功能有点蛋疼,到点开票之前,页面上根本就看不到"按预填信息购票"按钮,只有页面的倒计时结束,用户才能看到这个按钮,检查源代码可以发现,开发人员把这个按钮给隐藏了。

  <div class="ticket-btn" style="user-select: text;">
      <a href="javascript:;" class="btn btn-hollow btn-sm w120 buy-ticket-button" data-index="0" data-seatno="3"
          style="display: none; user-select: text;">按预填信息购票</a>
      <span class="txt-lighter qishou-text" style="user-select: text;">1月9日09点
          起售</span>
  </div>

作为程序员的我,心里暗爽,“这不是小场面~”,把style="display: none; user-select: text;"改成style="display: inline-block; user-select: text;"不就行。说干就干,可是当我改成“display: inline-block”之后,发现立马又被改回了“display: none”,我猜应该是有一个脚本在维护这一块的隐藏。

当我以为等着页面上的倒计时结束,“按预填信息购票”按钮出现后,立马点击一下就能开始抢票,然而并不是,当时的我心中真的是一万匹**马在崩腾。点击“按预填信息购票”按钮后,竟然还会弹出购票信息确认窗口,需要我们再次点击“提交订单”按钮。

Snipaste_2025-01-10_22-52-24.png

好家伙,这还抢个der,无奈抢票时间就这样一点点被浪费,最终喜提的“已售罄”三个大字像是在屏幕前嘲笑我:“你个菜鸡,没有抢到票吧”。

这我哪能忍!所以决定研究一下怎么搞一个google chrome插件,实现一键自动化,节省两次点击按钮的时间,毕竟抢票的时间是很宝贵的,哪怕节约哪怕1毫秒,可能我们就已经排在很多人前面了。

实现一个chrome插件

凭借着以前学过的一点点前端知识和几个小弟(文心一言、豆包、chatGPT),从没有搞过chrome插件开发的我,似乎有那么一点点出生牛犊不怕虎的意思,这就开始干了。

明确需求

首先根据场景,明确一下我们的需求。希望达到的效果是,通过chrome插件,自动监听页面上的按钮控件,一是当出现“按预填信息购票”按钮时,自动触发点击;二是当出现“提交订单”按钮时,自动触发点击。

功能实现

秉承着面向小弟编程的原则,经过一通通高手之间“对话”,再加上验证调试,最终达到了效果。下面记录一下实现过程,具体的原理就不讲了,因为我也不懂哈哈。

模式12306的页面

写一个页面,模拟12306页面上的从“1月9日09点 起售”到“按预填信息购票”按钮出现的过程,再模拟当用户点击“按预填信息购票”按钮时,弹出“购票信息确认”窗口,显示“提交订单”按钮。代码放在github仓库里,这里就不贴了

编写插件

1、创建 Chrome 扩展目录结构

首先,创建一个包含以下文件的目录结构:

├── 12306 pre-filled buy
│   ├── icons //插件图标
│   │   ├── icon128.png
│   │   ├── icon16.png
│   │   └── icon48.png
│   ├── manifest.json //配置文件
│   ├── popup.html //弹出界面
│   └── popup.js

2、编写manifest.json

{
   
  "manifest_version": 3,
  "name": "12306 按预填信息购票",
  "version": "1.0",
  "description": "12306 按预填信息购票",
  "permissions": [
    "activeTab",
    "alarms",
    "scripting"
  ],
  "action": {
   
    "default_popup": "popup.html",
    "default_icon": {
   
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
   
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

3、编写popup.html

<!DOCTYPE html>
<html>
<head>
  <title>12306 按预填信息购票</title>
  <style>
    body {
    
      width: 200px;
      height: 50px;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #f1f1f1;
    }
    button {
    
      padding: 10px 20px;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <button id="goHome">I Must Go Home</button>
  <script src="popup.js"></script>
</body>
</html>

4、编写popup.js

document.getElementById('goHome').addEventListener('click', function() {
   
  // 首先获取当前活动标签页的ID
  chrome.tabs.query({
   active: true, currentWindow: true}, function(tabs) {
   
    var tabId = tabs[0].id;

    // 使用获取到的tabId来执行脚本
    chrome.scripting.executeScript({
   
      target: {
   tabId: tabId},
      func: buttonlistener
    }, function() {
   
      console.log('button listener enabled in tab:', tabId);
    });
  });
});

function buttonlistener() {
   
  console.log('listening start...');
  const intervalId0 = setInterval(() => {
   
    const targetButtonSelector = 'a.buy-ticket-button[style*="display: inline-block;"]';
    const targetButton = document.querySelector(targetButtonSelector);
    if (targetButton && window.getComputedStyle(targetButton).display !== 'none') {
   
      targetButton.click();
      console.log('yushou, clicking...');
      clearInterval(intervalId0); // 停止检查
    }else{
   
      console.log('Not found the yushou button, waiting...');
    }
  }, 3); // 每隔3毫秒检查一次


  const intervalId = setInterval(() => {
   
    const submitButton = document.querySelector('a[href="javascript:;"][class="btn btn-primary ok"]');
    if (submitButton) {
   
      submitButton.click();
      console.log('Found the submit button, clicking...');
      clearInterval(intervalId); // 停止检查
    }else{
   
      console.log('Not found the submit button, waiting...');
    }
  }, 5); // 每隔5毫秒检查一次

}

5、加载扩展

  • 打开Chrome浏览器,进入 chrome://extensions/。
  • 打开右上角的“开发者模式”
  • 点击“加载已解压的扩展程序”按钮,选择你的12306 pre-filled buy文件夹。

经过以上步骤后,应该就能在Chrome工具栏中看到你的扩展图标。点击图标,然后点击“I Must Go Home”按钮,脚本就会在当前活动页面上执行,可以打开浏览器控制台看日志。

演示使用

利用我们前面写的模拟页面,来演示一下插件的使用:

show2.gif

显然,我们开发的这个插件非常的简陋,还有报错信息没有处理,另外每隔5毫秒检查一次DOM元素对浏览器性能有一定影响,不过足以满足我们的自动化需求。

另外,在使用插件之前,我们需要注意几点:

  • 更新主机的系统时间,12306上面的倒计时貌似是根据系统时间来的,如果你的系统时间慢了,结果可想而知

    mac上可以执行命令:sudo ntpdate -u time.apple.com

  • 在倒计时还有两分钟的时候开启我们这个插件功能,避免长时间高频检查DOM元素导致对浏览器性能产生影响

结束语

平时我都是拿来主义,想要什么插件就在chrome的插件市场或者tampermonkey里找,大部分都是能找到的。第一次硬着头皮自己捣鼓了一个,虽然过程中遇到了好几个坎,功能也很简陋,但最终到达了目标,节约了抢票时间,心里还是美滋滋的。所以把这次经历记录下来,希望对大家有一丢丢帮助。

上述所有代码都已经放在了github,需要的自取哦。

相关文章
|
缓存 网络协议 算法
Netty的基础入门(上)
Netty的基础入门(上)
777 1
|
Shell 网络安全 开发工具
项目快速导入git
本文介绍了如何在本地初始化 Git 仓库并将代码提交到远程仓库(如 GitHub 或 Gitee)的基本流程。内容包括安装 Git、创建仓库、添加文件、提交更改以及推送代码到远程仓库的详细步骤,适合初学者快速掌握 Git 的基本使用方法。
2952 1
|
Linux 虚拟化 iOS开发
macOS Tahoe 26 beta 3 (25A5306g) Boot ISO 原版可引导镜像下载
macOS Tahoe 26 beta 3 (25A5306g) Boot ISO 原版可引导镜像下载
1614 0
|
分布式计算 Java 大数据
Java 大视界 —— 基于 Java 的大数据分布式计算在气象数据处理与天气预报中的应用进展(176)
本文围绕基于 Java 的大数据分布式计算在气象数据处理与天气预报中的应用展开,剖析行业现状与挑战,阐释技术原理,介绍其在数据处理及天气预报中的具体应用,并结合实际案例展示实施效果。
|
安全 Linux 网络安全
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
152859 0
|
SQL XML JavaScript
【若依Java】15分钟玩转若依二次开发,新手小白半小时实现前后端分离项目,springboot+vue3+Element Plus+vite实现Java项目和管理后台网站功能
摘要: 本文档详细介绍了如何使用若依框架快速搭建一个基于SpringBoot和Vue3的前后端分离的Java管理后台。教程涵盖了技术点、准备工作、启动项目、自动生成代码、数据库配置、菜单管理、代码下载和导入、自定义主题样式、代码生成、启动Vue3项目、修改代码、以及对代码进行自定义和扩展,例如单表和主子表的代码生成、树形表的实现、商品列表和分类列表的改造等。整个过程详细地指导了如何从下载项目到配置数据库,再到生成Java和Vue3代码,最后实现前后端的运行和功能定制。此外,还提供了关于软件安装、环境变量配置和代码自动生成的注意事项。
32676 73