插件实现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,需要的自取哦。

相关文章
|
8月前
|
小程序
小程序一直未提审的原因及解决方案
小程序一直未提审的原因及解决方案
109 11
|
API 开发者
百度批量算路功能使用
百度批量算路功能使用
122 0
|
Python
一个12306自动抢票的脚本
一个12306自动抢票的脚本
1515 1
织梦dedecms会员发布文章内容自动过滤外部链接的方法
织梦会员中心发布文章自动过滤外部外部链接,保留本站站内链接。这个织梦默认后台本身带有这样的功能的,只是会员模块里没有而已。
|
安全 Java Devops
在线捉虫:1分钟代码自动检测体验
1分钟快速体验便宜云服务器云效提供的免费在线git代码托管和代码自动检测能力
百度统计:页面代码安装状态:代码未生效
百度统计:页面代码安装状态:代码未生效
179 0
|
JavaScript 前端开发
网站添加本站已稳定运行XX天的统计代码
网站添加本站已稳定运行XX天的统计代码
417 0
网站添加本站已稳定运行XX天的统计代码
|
JavaScript Unix Linux
PHP开发的网站,如何实现批量打印快递单的功能?
虽然市场中不断有新的编程语言诞生,但不得不承认PHP在web开发领域仍一直占有很大份额,高效开发是他一直以来最大的一个优点,而且他的学习成本也较低,入门门槛较低,和Python一样是解释性语言,很多人都说他是世界上最好的语言。其实语言并没有好坏之分,只有是否适用,每门语言都有自己的优缺点,毕竟想要获得一样东西,总要放弃点什么,有舍有得,看自己怎么权衡,只要能解决问题就是好的,不管黑猫白猫抓到老鼠就是好猫。自从swoole的加持以及和GO语言的结合,也弥补语言本身的很多不足,慢慢缩小和其他语言之间的差距,不过还是有很长的路要走。--题外话
391 0
|
Oracle 关系型数据库 Shell
[自制工具]批量后台更新统计信息
Oracle数据库有时需要批量收集数据库的统计信息,如在大量数据迁移或大量数据更新以后,但是收集的时间可能会较长,为了避免网络中断等意外情况可能引起的麻烦,今天调试了这个小脚本,可以分用户批量执行,同时记录执行时间等日志信息,比较实用。
229 0
http://www.vxiaotou.com