🚀

7. 万博自動化検証 ― ページ遷移に沿った処理フロー(3日前空き枠先着予約 後編)

に公開

7. 万博自動化検証 ― ページ遷移に沿った処理フロー(3日前空き枠先着予約 後編)

📌 前回の記事6. 万博自動化検証 ― ページ遷移に沿った処理フロー(3日前空き枠先着予約 前編))


実行 URL(@match)


[https://ticket.expo2025.or.jp/event_search/](https://ticket.expo2025.or.jp/event_search/)*
[https://ticket.expo2025.or.jp/event_time/](https://ticket.expo2025.or.jp/event_time/)*


具体実装処理概要

1. 時間帯選択(radioボタン操作)

  • パビリオンを選択した後に出てくる時間帯一覧を監視。
  • 指定した時間帯(例: 12:00〜15:00 の範囲)に合致した枠があれば、自動的にラジオボタンを選択する。
  • 選択成功時はログ保存や外部通知を実行(通知処理はここでは省略)。

2. 申込ボタンの監視とクリック

  • 「この内容で申込する」ボタンを MutationObserver で監視。
  • 有効化されたら一度だけクリック。
  • 属性変化を見逃した場合に備え、定期チェックも併用。
  • クリック履歴を localStorage に保存し、右下に UI 表示。

3. モーダルや「予約不可通知」への対応

  • 「定員を超えました」などのモーダルを検出したら閉じ、戻る処理へ移行。
  • 「予約可能な時間帯がありません」通知を検出した場合も同様に戻る処理へ。

4. 戻るボタン監視

  • 有効な戻るボタンが出現したら自動クリック。
  • 予約不可のケースからスムーズに検索ページへ復帰することを目的とする。

5. 復帰スクリプト

  • event_time ページで「検索」や「戻る」ボタンを監視し、自動クリックして再度 event_search ページへ戻す。
  • これにより「パビリオン選択前 → 空き枠探索 → 時間帯選択 → 戻る → 再検索」のループが成立する。

サンプルコード

// ==UserScript==
// @name         万博自動化検証 - 3日前空き枠先着(後編)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @match        https://ticket.expo2025.or.jp/event_search/*
// @match        https://ticket.expo2025.or.jp/event_time/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  // --- 履歴管理(localStorage + 画面表示) ---
  const history = JSON.parse(localStorage.getItem("reservationClickHistory") || "[]");
  function addHistory(type, name) {
    const now = new Date();
    const t = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
    history.unshift({ type, name, time: t });
    if (history.length > 50) history.pop();
    localStorage.setItem("reservationClickHistory", JSON.stringify(history));
    renderHistory();
  }
  function renderHistory() {
    let el = document.getElementById("historyPanel");
    if (!el) {
      el = document.createElement("div");
      el.id = "historyPanel";
      el.style.position = "fixed";
      el.style.bottom = "10px";
      el.style.left = "10px";
      el.style.background = "rgba(0,0,0,0.7)";
      el.style.color = "#fff";
      el.style.padding = "10px";
      el.style.zIndex = "9999";
      document.body.appendChild(el);
    }
    el.innerHTML = "<b>予約履歴:</b><br>" +
      history.slice(0,10).map((h,i)=>`${i+1}. [${h.time}] ${h.type}${h.name}`).join("<br>");
  }
  renderHistory();

  // --- 時間帯(radioボタン)のクリック ---
  function clickRadio() {
    const radios = document.querySelectorAll("input[type=radio].time-slot"); // セレクタ省略
    for (const radio of radios) {
      const text = radio.parentElement?.textContent?.trim() || "";
      // 例: "12:30-12:45" → 開始時刻を判定
      const match = text.match(/(\d{1,2}):(\d{2})/);
      if (match) {
        const h = parseInt(match[1],10);
        const m = parseInt(match[2],10);
        const total = h*60+m;
        if (total >= 12*60 && total <= 15*60 && !radio.disabled) {
          console.log("✅ 指定範囲の時間帯を選択:", text);
          radio.click();
          addHistory("時間選択", text);
          return true;
        }
      }
    }
    return false;
  }

  // --- 申込ボタンの監視 ---
  function observeSubmit() {
    const btn = document.querySelector("button.submit"); // セレクタ省略
    if (!btn) return;
    const obs = new MutationObserver(() => {
      if (!btn.disabled) {
        console.log("✅ 申込ボタン有効化 → クリック");
        btn.click();
        addHistory("申込", "この内容で申込する");
        obs.disconnect();
      }
    });
    obs.observe(btn, { attributes: true });
  }

  // --- モーダル監視 ---
  function observeModal() {
    const obs = new MutationObserver(() => {
      const modal = document.querySelector(".modal"); // セレクタ省略
      if (modal) {
        const close = modal.querySelector(".close-btn");
        if (close) {
          console.log("⚠️ モーダルを閉じて戻る処理へ");
          close.click();
          addHistory("モーダル", "定員超過/エラー");
        }
      }
    });
    obs.observe(document.body, { childList:true, subtree:true });
  }

  // --- 戻るボタン ---
  function observeBack() {
    const obs = new MutationObserver(() => {
      const back = document.querySelector("a.back-btn"); // セレクタ省略
      if (back) {
        console.log("↩ 戻るボタンをクリック");
        back.click();
        addHistory("戻る", "検索へ復帰");
      }
    });
    obs.observe(document.body, { childList:true, subtree:true });
  }

  // --- 検索/戻る 復帰スクリプト(event_time ページ用) ---
  function recoveryScript() {
    const obs = new MutationObserver(() => {
      const searchBtn = document.querySelector("button.search");
      const backBtn = document.querySelector("a.back-btn");
      if (searchBtn && !searchBtn.disabled) {
        console.log("🔄 検索ボタンをクリック");
        searchBtn.click();
      }
      if (backBtn && !backBtn.disabled) {
        console.log("↩ 戻るボタンをクリック");
        backBtn.click();
      }
    });
    obs.observe(document.body, { childList:true, subtree:true, attributes:true });
  }

  // --- 初期化 ---
  if (location.href.includes("/event_search/")) {
    if (clickRadio()) observeSubmit();
    observeModal();
    observeBack();
  }
  if (location.href.includes("/event_time/")) {
    recoveryScript();
  }

})();

補足

  • 上記コードでは、実際の セレクタ名外部通知処理 は省略しています。実装する際は対象サイトの DOM 構造に合わせて補ってください。
  • 「時間帯選択 → 申込 → モーダル処理 → 戻る → 再検索」というループを自動で繰り返す流れを実現しています。
  • ログ履歴を画面に出すことで、どの処理を実行したか後から確認可能です。
  • 実行間隔については、万博サイトへの負荷を抑えるためにも「人が手動で操作する程度の頻度」に設定することが重要です。過度に短い間隔での監視やリロードは、サーバー負荷の増大や不正利用と見なされるリスクがあります。

免責事項

本記事は Web 自動化の学習記録をまとめたものであり、Expo 2025 チケット予約システムの不正利用を助長する意図は一切ありません。
実際にスクリプトを利用する場合は、万博公式の規定や利用規約に従うことが必須です。
本記事を参考にしたことによって生じたいかなる不利益・損害についても、筆者は責任を負いません。


Discussion