LOCALE FARE LAB

Trip.com 語言比價室

同一份行程,Trip.com 的不同語言站常常開出不同報價。這裡把你的行程一鍵展開成 24 個語言站的深連結——統一台幣計價,逐站回填參考價,立刻看出哪個語言最省。

先看這裡:Trip.com 語言站是另一個網站(有人機驗證),本站的雲端查價(Google Flights)做不到——所以「自動輪查 24 站」需要電腦+自動查價腳本。手機/平板請改用外站多段區間掃描、區間掃描的雲端查價(免安裝),或在下方矩陣手動逐一點開各語言站、把總價填回該列。沒裝腳本時,下方的自動輪查會自動降級為「🔗 逐一開啟」。
新:登入後可用「📲 工作站」——手機/平板把行程派給你家常開的電腦,由它代跑 24 站,結果同步回來看(電腦那端仍需裝腳本)。

💡 為什麼換個語言,價格就不一樣?

Trip.com 的每個語言站其實是不同市場的銷售站點:各站的促銷活動、定價策略、庫存艙等釋出可能都不同,於是同一趟行程在台灣站、日本站、泰國站看到的價格經常有落差——有時差幾十元,有時差上千元。

本工具的玩法:行程只填一次 → 一鍵產生 24 個語言站的同行程連結,而且全部強制以台幣(TWD)顯示——不用管匯率、不用換算,回填後直接比大小,哪個語言站最省一目了然。

24 個語言站的行程帶入與台幣顯示皆已於 2026-07/08 逐站實測(阿拉伯文因 Trip.com 無獨立網域未列入);Trip.com 介面與參數可能改版,若開啟後行程沒帶入請回報。價格為你回填當下的參考價,隨時變動,實際以訂票網結帳頁為準。

🧭 三步驟上手

填行程多段 2–6 段,城市 3 碼+日期。來回填 2 段、外站四段票填 4 段。
逐站開啟並回填由上而下點 🔍,每站看到的總價填回該列。連結全部統一台幣顯示,可直接比。
挑最省的站下單最省列會亮起。切到該站訂票前,再次核對結帳頁總額與艙等行李規則。

💾 我的比價案(這台裝置)

同時比好幾趟行程?把每一趟存成一個比價案——行程、乘客艙等、24 站回填價格全部一起記住,下次回來點一下就接著比。免登入,資料存在這台裝置的瀏覽器,不會上傳。

目前正在編輯的比價案會自動存檔(改行程、回填價格都會即時記住)。清除瀏覽器資料會一併清掉——要留底或換電腦,按「📤 匯出存檔」把所有比價案(含 24 站回填價)存成一個 .json 檔,到另一台裝置按「📥 匯入存檔」讀回來(匯入是新增合併,不會蓋掉現有比價案)。只想傳行程給別人比,用「🔗 複製分享連結」(不含價格)。

🧳 行程編輯器

經濟/商務艙代碼已於 2026-07 逐站實測會帶入,Trip.com 若改版可能失效,請以開啟後頁面實際顯示為準;頭等與「只看直飛」同樣以頁面實際顯示為準。嬰兒票請在 Trip.com 頁面上自行調整;兒童票價多依年齡而異,連結只帶「兒童人數」,開啟後請在該站頁面確認兒童年齡設定再看價。

🌐 語言站比價矩陣

每列一個語言站(依 Trip.com 語言選單順序,共 24 站);單程、來回、多段行程都可以比。點 🔍 開啟該站的同行程搜尋(新分頁重複使用),把看到的總價回填,最省的列會亮起來。小技巧:Ctrl/Cmd+點 🔍 可各開一個分頁,多站並排對照。

💡 所有連結一律帶 curr=TWD:24 個語言站都直接以台幣顯示(2026-07/08 逐站實測有效;Trip.com 若改版,以開啟後頁面實際顯示為準),回填後直接比大小,不用換匯。

不用這台裝置自己開 24 站:送到常開的工作站電腦代跑,進度與價格在下方「📲 工作站」區即時顯示。
需先裝下方自動輪查腳本。同時開幾個分頁一起跑,比一站一站接力快;瀏覽器需允許本站彈出視窗(第一次會跳出封鎖提示,選「一律允許」)。建議 2–3 站,最多 6 站——短時間開太多、查太快,Trip.com 會判定為異常流量而跳出人機驗證(尤其某些語言站更敏感),而且瀏覽器也會凍結看不見的背景分頁。慢一點反而比較快。

⚠️ 回填的是參考價:同一分鐘不同裝置/瀏覽器/登入狀態看到的價格都可能不同,顯示價與最終結帳價也可能有落差(稅費計算方式未必相同),下單前一律以該站結帳頁為準。多位乘客時,請確認每一站顯示的都是全部乘客合計價再回填——部分站可能預設顯示單人價,基準不同就比不出結果。

📲 工作站:手機派工,常開電腦代跑 24 站

登入後,把行程「送去工作站」——家裡/公司常開的電腦開著工作站模式、裝好自動輪查腳本,就會自動領單、一次多開跑完 24 個語言站,價格同步回來,手機直接看結果、一鍵套用進比價矩陣。沒登入完全照舊,純本機可用。

🖥️ 把這台電腦當工作站

任務與結果只存在你的私人雲端(僅本人讀寫,與區間掃描同一組帳號)。價格是工作站電腦上 Trip.com 頁面顯示的參考價,下單前一律以結帳頁為準。Trip.com 若跳出人機驗證,工作站會暫停並通知你(這裡會出現紅色提示)——要用遠端桌面到工作站自己完成驗證再按「繼續」;本工具不會、也不該繞過驗證。

工作站使用須知:
  • 這台電腦要已裝好「自動輪查腳本」(Tampermonkey,見安裝步驟),並允許本站彈出視窗(網址列右側,選「一律允許」)——否則分頁開不出來,任務會暫停。
  • 視窗要保持可見、不要最小化;Windows 請自行到「電源與睡眠」把睡眠設成「從不」(本頁不會、也無法替你改系統設定)。已嘗試向瀏覽器要螢幕喚醒鎖,僅在視窗可見時有效。
  • 建議用專用的 Chrome 設定檔當工作站:任務會暫時佔用本站的行程草稿(不影響已存的比價案)。
  • Trip.com 跳出人機驗證時,工作站會暫停並通知手機;請用遠端桌面連到這台電腦自己完成驗證後按「繼續」。工具不會、也不該繞過驗證。
  • 同一個帳號同一時間只有一個工作站;別台開工作站模式會接管,這台會顯示「已被另一個工作站取代」。
工作站紀錄

📈 價格變動紀錄(這台裝置)

每次回填價格都會自動留下時間戳。不同日期/時段查同一趟行程,這裡可以看出每一輪的最省價怎麼變,以及各站相對上一次是漲是跌。

各站最新 vs 上一次

紀錄以「行程設定指紋」分開存放:改了日期/艙等/人數會另計一組,不會把不同行程的價格混在一起比。資料只存在這台裝置(localStorage),最多保留 2000 筆,超過會自動刪最舊的。

⚠️ 下單前必讀

不同語言站=不同市場站點:客服語言、付款方式、可用優惠券、甚至改退票的處理動線都可能不同。用外語站下單前,確認自己能看懂訂單條款,或先截圖存證。
結帳幣別再確認一次:本頁連結已強制台幣顯示,但下單前仍請在結帳頁確認請款幣別是 TWD——若站點以外幣請款,發卡行可能收海外交易手續費(常見約 1.5%–3%,依發卡行而異),以發卡行公告為準。
價格會動、也會看人:促銷、庫存艙等變動、cookie 與會員等級都可能影響報價。建議同一時段內完成整輪回填,比價才有意義;回填價過了幾小時就只剩參考價值。
票務規則以各站顯示為準:行李額度、改退費、票價品牌(輕省/標準)在不同站的呈現可能不同,別只看總價——最便宜的那筆可能是無行李的輕省票。

⚡ 半自動查價:抓價書籤+逐站精靈(電腦進階選用)

這一區是電腦進階選用:不裝任何東西也能完整使用本頁(逐一開啟語言站、手動回填價格)。書籤與腳本只是幫你少打幾個數字;手機瀏覽器沒有書籤列、也裝不了 Tampermonkey,請直接略過本區。

不想手動打 11 次價格?用書籤讓價格自己飛回來:書籤會讀取你當下開著的 Trip.com 頁面上顯示的最低票價,自動辨識是哪個語言站並回填矩陣——不呼叫任何查價 API,讀的就是你眼睛看到的數字。

拖書籤(只需一次)把下面的「⚡ 抓 Trip 價」按鈕拖到瀏覽器書籤列。手機瀏覽器多半不支援書籤列,此功能建議在電腦上用。
開一站,等價格跑完點矩陣任一列 🔍(或「🪄 下一站 ▶」)開啟 Trip.com,等票價列表載入完成。
按書籤→切語言→再按按一下書籤,最低價自動送回對應列;接著直接用 Trip.com 頁面右上角的語言選單切下一個語言(行程與篩選會保留),價格跑完再按一次書籤——24 站循環到填滿。

書籤讀取的是頁面上顯示的參考價(通常是該行程的最低票價,未含後續加購),與你手動回填一樣以結帳頁為準;Trip.com 版面改版時書籤可能抓不到,屆時仍可手動回填。書籤本體不含任何外部程式碼,只在你主動點擊時執行。

🤖 自動輪查(裝一次腳本,之後這一輪免按書籤)

想連書籤都不按?安裝下方使用者腳本(需 Tampermonkey 之類的腳本管理器,跟本站「抓價書籤/自動送回」同一套模式)。安裝後按「⚡ 自動輪查」:腳本會在每個 Trip.com 語言站等價格載入穩定後自動送回,並自動前往下一站——由本頁啟動的這一輪可以一路自動跑完 24 站,你只要看著矩陣被填滿。

⬆️ 「⚡ 自動輪查」「🚀 一次多開」按鈕已移到上方比價矩陣標題下,方便邊按邊看。

如果跳出「人機驗證 / 嘗試次數過多」:代表查詢頻率被 Trip.com 判定為異常。正確做法是——①在那個分頁自己完成驗證(這是網站的正常機制,工具不會、也不該幫你繞過);②把「一次同時開」調成 1–3 站;③隔十幾分鐘再繼續,或改用「⚡ 自動輪查」接力模式(一次只跑一個分頁,最不容易觸發);④真的急,就對還沒填的站手動開連結、按抓價書籤回填。
📜 安裝自動輪查腳本(⚡ 一鍵安裝・自動更新;點開看步驟與完整原始碼)
  1. 瀏覽器安裝 Tampermonkey 擴充功能(Chrome/Edge/Firefox 商店搜尋即可)。
  2. 按下方「⚡ 一鍵安裝 / 更新」→ Tampermonkey 跳出安裝畫面按「安裝」。v1.2.0 起會自動更新(Tampermonkey 預設每天檢查)。
    若點了只看到純文字(Tampermonkey 沒接手):Tampermonkey 管理面板 →「實用工具」→「從 URL 安裝」貼上 https://cuijian-home-tw.web.app/flights/cuijian-trip-autograb.user.js;或按「複製腳本」→「新增腳本」→ 全選貼上 → 儲存(Ctrl+S)。
  3. 回到本頁按「⚡ 自動輪查 24 站」(接力)或「🚀 一次多開」(並行)。若分頁停在 Trip.com 沒有自動送回,通常是腳本沒裝好——仍可改按書籤或手動回填。
  4. v1.3.x / v1.4.0 修正抓錯價:舊版會抓到頁首日期列(其他日期的價格)、價格提醒、加價金額,回報的價格常比實際低;新版只讀航班卡片上的票價,並等即時價載入穩定後才回報;有指定航空公司時,會自動選該航司去程、讀「去回程都是該航司」的價格。已裝 v1.2.0 的人會自動更新;v1.0 / v1.1 請重裝一次。

安全設計:腳本 @grant none(不要求任何特權)、只對 trip.com 網域生效,且只在網址帶著本頁發出的 _agrab=1 標記時才動作——平常自己逛 Trip.com 完全不會被讀取或跳轉;單輪有 20 分鐘與 60 站次上限,隨時可按「停止輪查」。抓到的一律是頁面顯示的參考價。

// ==UserScript==
// @name         外站票情報站:Trip.com 自動輪查
// @namespace    https://cuijian-home-tw.web.app/
// @version      1.4.3
// @description  只在從「Trip.com 語言比價室」按下自動輪查(網址帶 _agrab=1)時,讀取頁面顯示的最低 TWD 參考價送回工具;支援多分頁並行(背景分頁被瀏覽器節流時會耐心等待,不會誤判逾時)。平常瀏覽 Trip.com 完全不會動作。
// @match        https://*.trip.com/*
// @grant        none
// @run-at       document-idle
// @updateURL    https://cuijian-home-tw.web.app/flights/cuijian-trip-autograb.user.js
// @downloadURL  https://cuijian-home-tw.web.app/flights/cuijian-trip-autograb.user.js
// ==/UserScript==
// v1.2.0(2026-10-11):加上 @updateURL / @downloadURL —— 可一鍵安裝,之後 Tampermonkey 會自動更新(只從本站下載這一個檔,不連第三方)。抓價邏輯與 v1.1.0 相同。
// v1.3.0(2026-10-14):修正「抓到的價格比實際低很多」—— 舊版取「整頁所有 TWD 數字的最小值」,會抓到頁首日期列
//   (其他日期組合的價格,如 11/16–11/20 TWD4,572)、價格提醒框(推薦 TWD7,270)、加價金額(+TWD 632)。
//   改成只讀「航班卡片」上的價格:卡片 = 價格往上最近一層含 ≥2 個時刻(03:05 / 06:05)的區塊,且該區塊只含 1~2 種價格
//   (整串清單或提醒框會含很多價格,排除);「+」開頭的加價金額排除。實測 tw 站 TPE⇄CJU 11/18–11/22:舊版 4,572 → 新版 7,503(與頁面「最低價」一致)。
//   讀不到任何航班卡片價格時回報「沒抓到價」,寧可空白也不回報錯的價格。
// v1.3.1(2026-10-14):等「最低價」也穩定 6 秒、且首次出現價格後至少 12 秒才送 —— 避開 Trip.com 先顯示的暫時價(實測偏低約 6%)。
// v1.3.2(2026-10-15):卡片必須含 ≥2 個機場代碼(TPE/CJU…)—— 實測 nl 站左側篩選面板「Depart | 00:00 | 23:59 | TWD 7.836」會被當成卡片。
// v1.3.3(2026-10-15):①優先讀排序分頁「最低價」(清單逐步載入,最便宜的班次可能還沒出現;實測 tw TPE⇄OSA 15,571→15,496 與頁面一致)
//   ②送回時附上查詢日期,工具端行程不同就不收(避免改行程後舊分頁把別的行程的價格填進來)。
// v1.4.0(2026-10-15):有指定航空公司時,自動「選取」最便宜的去程、進到回程(多段則逐段),讀最後一步的價格
//   —— 確保去回程都是指定的航司(第 1 步的價格是「該航司去程 + 任何航司最便宜回程」,會偏低)。
//   換頁後同一個分頁用 sessionStorage 記住是工具開的(15 分鐘);沒指定航司時行為與 v1.3.3 相同(第 1 步就是全航司最低來回價)。
// v1.4.1(2026-10-16):從沒被看見過的背景分頁不因 10 分鐘牆鐘放棄(背景分頁清單不載入,輪到前景才會載),最多等 30 分鐘。
// v1.4.2(2026-10-16):遇到「搜尋結果已逾期」彈窗、或在背景等超過 8 分鐘才輪到前景 → 自動重新整理拿最新價(同頁最多 2 次)。
// v1.4.3(2026-10-16):逾時還沒完成(在前景 90 秒清單沒出來、或按不到「選取」)→ 先自動重新整理再試,兩次都不行才回報沒抓到。
(function () {
  "use strict";
  if (!/\/flights\//.test(location.pathname)) return;
  // v1.4.0:有指定航空公司(網址 airline=)時要「去回程都是該航司」—— 第 1 步顯示的是「該航司去程 + 任何航司最便宜回程」
  //   (業主實測 RMQ⇄ICN 只查 KE:第 1 步 16,903 = 大韓去程 + 廉航回程;選了大韓去程後第 2 步才是大韓來回 18,614)。
  //   所以有航司篩選時:第 1 步選最便宜那班 → 下一步 → … → 最後一步才讀價送回。換頁後網址沒有 _agrab,
  //   改用這個分頁的 sessionStorage 記住「這是工具開的查價分頁」(15 分鐘內有效,只在同一分頁)。
  var ARM_KEY = "cj_agrab_arm_v1", arm = null;
  try { arm = JSON.parse(sessionStorage.getItem(ARM_KEY) || "null"); } catch (e) { arm = null; }
  if (/[?&]_agrab=1/.test(location.search)) {
    var q0 = String(location.search);
    var legs0 = /[?&]triptype=rt\b/i.test(q0) ? 2 : /[?&]triptype=ow\b/i.test(q0) ? 1 : ((q0.match(/[?&]multddate\d+=/gi) || []).length || 1);
    arm = { t: Date.now(), legs: legs0, al: /[?&]airline=[A-Za-z0-9]/.test(q0), d: datesIn(q0), step: 1 };
    try { sessionStorage.setItem(ARM_KEY, JSON.stringify(arm)); } catch (e) {}
  } else if (!(arm && typeof arm === "object" && Date.now() - Number(arm.t) < 900000 && arm.al)) {
    return; // 非自動輪查模式(或不是工具開的分頁):什麼都不做
  }
  function datesIn(s) {
    var ds = String(s).match(/\d{4}-\d{2}-\d{2}/g) || [], out = [];
    for (var i = 0; i < ds.length && out.length < 8; i++) if (out.indexOf(ds[i]) < 0) out.push(ds[i]);
    return out;
  }
  function curStep() { var m = /[?&]currentseqno=(\d+)/i.exec(location.search); var n = m ? parseInt(m[1], 10) : 1; return n >= 1 && n <= 9 ? n : 1; }
  function disarm() { try { sessionStorage.removeItem(ARM_KEY); } catch (e) {} }
  var TOOL = "https://cuijian-home-tw.web.app/flights/triplang.html";
  var START = Date.now(), done = false;
  var visibleMs = 0, lastTick = Date.now(); // 只累計「分頁可見」的時間:背景分頁被瀏覽器節流時不算逾時
  function b64url(s) { return btoa(unescape(encodeURIComponent(s))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); }
  // v1.3.0:只讀「航班卡片」上的價格(見檔頭說明)。整數 TWD(含 7,503 / 7 503 / 7.503 千分位寫法)。
  var PRICE_RE = /(?:TWD|NT\$)\s?([0-9]{1,3}(?:[.,\u00a0\u202f ][0-9]{3})+|[0-9]{3,7})(?![0-9])|([0-9]{1,3}(?:[.,\u00a0\u202f ][0-9]{3})+|[0-9]{3,7})\s?TWD/;
  var PRICE_RE_G = new RegExp(PRICE_RE.source, "g");
  var TIME_RE_G = /(?:^|[^\d:\u2013-])\d{1,2}:\d{2}(?![\d:])(?!\s*[\u2013-]\s*\d)/g; // 單一時刻;「00:00–24:00」這種篩選範圍不算
  function priceOf(m) { var n = Number(String(m[1] || m[2] || "").replace(/[^0-9]/g, "")); return (isFinite(n) && n >= 500 && n <= 2000000000) ? n : null; }
  function scan() {
    var vals = [], cards = [], cheap = null, cheapN = Infinity;
    var els = document.body ? document.body.getElementsByTagName("*") : [];
    for (var i = 0; i < els.length; i++) {
      var el = els[i];
      if (el.children.length > 3) continue;
      var own = (el.innerText || "").trim();
      if (!own || own.length > 30) continue;
      var m = PRICE_RE.exec(own);
      if (!m || own.indexOf("+") >= 0) continue; // 「+TWD 632」是加價金額,不是票價
      var p = el, card = null;
      for (var k = 0; k < 8 && p; k++) {
        p = p.parentElement;
        if (!p) break;
        var tx = p.innerText || "";
        if (tx.length > 500) break;
        var tm = tx.match(TIME_RE_G);
        if (tm && tm.length >= 2) { card = p; break; }
      }
      if (!card || cards.indexOf(card) >= 0) continue;
      var uniq = {}, nu = 0, all = card.innerText.match(PRICE_RE_G) || [];
      for (var j = 0; j < all.length; j++) { var key = all[j].replace(/[^0-9]/g, ""); if (!uniq[key]) { uniq[key] = 1; nu++; } }
      if (nu > 2) continue; // 含很多價格的是整串清單/提醒框,不是單一航班卡
      // v1.3.2:航班卡一定有機場代碼(TPE / CJU);篩選面板的「出發 00:00 | 23:59 TWD…」沒有,排除
      var codes = (card.innerText.match(/\b[A-Z]{3}\b/g) || []).filter(function (c) { return c !== "TWD"; });
      if (codes.length < 2) continue;
      cards.push(card);
      // v1.4.0:一張卡只取「第一個」價格(班次票價),後面的價格不算 —— 只有一班時,價格提醒框
      //   (推薦:TWD11,507)會和航班卡被認成同一塊(實測 tw RMQ⇄ICN 只查 KE);「總額」也排在每人票價後面。
      var n = firstCardPrice(card);
      if (n != null) { vals.push(n); if (n < cheapN) { cheapN = n; cheap = card; } }
    }
    var cardMin = vals.length ? Math.min.apply(null, vals) : null;
    // v1.3.3:優先採用清單上方的排序分頁(推薦 / 直飛優先 / 最低價)—— 「最低價」那格是 Trip.com 對整份清單算的最低價;
    //   清單是逐步載入的,最便宜的那班可能還沒出現在畫面上(實測 tw TPE⇄OSA:卡片最低 15,571、分頁「最低價」15,496)。
    var bar = cards.length ? sortBarPrices(cards[0]) : null;
    var min = cardMin;
    if (bar) { var bm = Math.min.apply(null, bar); if (cardMin == null || bm >= cardMin * 0.7) min = bm; } // 離譜地低=認錯區塊,不採用
    return { count: vals.length, min: min, bar: bar ? bar.join(",") : "", cheapCard: cheap };
  }
  function firstCardPrice(card) {
    var tx = card.innerText || "", mm;
    PRICE_RE_G.lastIndex = 0;
    while ((mm = PRICE_RE_G.exec(tx))) {
      if (tx.charAt(mm.index - 1) === "+" || /\+\s*$/.test(tx.slice(Math.max(0, mm.index - 3), mm.index))) continue; // +TWD 632 加價
      PRICE_RE_G.lastIndex = 0;
      return priceOf(mm);
    }
    PRICE_RE_G.lastIndex = 0;
    return null;
  }
  // v1.4.0:按下航班卡上的「選取」(各語言字不同,用結構找:卡片內最後一個可點、短字、不含數字的元素)
  function clickSelect(card) {
    if (!card) return false;
    var pick = null, c, t, i;
    var btns = card.querySelectorAll("button,[role=button],a");
    for (i = 0; i < btns.length; i++) { t = (btns[i].innerText || "").trim(); if (t && t.length <= 20 && !/\d/.test(t)) pick = btns[i]; }
    if (!pick) {
      var leaves = card.getElementsByTagName("*");
      for (i = 0; i < leaves.length; i++) {
        c = leaves[i]; if (c.children.length) continue;
        t = (c.innerText || "").trim();
        if (!t || t.length > 15 || /\d/.test(t)) continue;
        try { if (getComputedStyle(c).cursor === "pointer" || getComputedStyle(c.parentElement).cursor === "pointer") pick = c; } catch (e) {}
      }
    }
    if (!pick) return false;
    try { pick.click(); return true; } catch (e) { return false; }
  }
  // 從第一張航班卡往「前面」找:含 2~4 個價格、每格只有「文字標籤+價格」(標籤不含數字)、沒有時刻的那一排 = 排序分頁
  function sortBarPrices(firstCard) {
    var all = document.body.getElementsByTagName("*"), idx = -1;
    for (var i = 0; i < all.length; i++) { if (all[i] === firstCard) { idx = i; break; } }
    for (var x = idx - 1; x >= 0 && x > idx - 400; x--) {
      var c = all[x], kids = c.children;
      if (kids.length < 3 || kids.length > 6) continue;
      var tx = c.innerText || "";
      if (tx.length > 200 || /\d{1,2}:\d{2}/.test(tx)) continue;
      var prices = [], bad = false;
      for (var j = 0; j < kids.length; j++) {
        var kt = (kids[j].innerText || "").trim(), m = PRICE_RE.exec(kt);
        if (!m) continue;
        if (kt.length > 40 || /\d/.test(kt.replace(m[0], ""))) { bad = true; break; }
        var n = priceOf(m);
        if (n != null) prices.push(n);
      }
      // 排序分頁固定 3 格(推薦 / 直飛優先 / 最低價;各語言同結構)—— 左側篩選「大型飛機 / 中型飛機」只有 2 格,排除
      if (!bad && prices.length === 3) return prices;
    }
    return null;
  }
  function go(url) { if (done) return; done = true; location.href = url; }
  // v1.3.1:不只「卡片數量」要穩定,「最低價」也要穩定才送 —— Trip.com 會先顯示一批暫時價格再換成即時價,
  //   卡片數不變但價格還在變;舊版數量穩定 5 秒就送,抓到偏低的暫時價(業主實測 nl 7,316 vs 實際 7,763)。
  var lastSig = "", stableAt = Date.now(), firstPriceAt = 0;
  var STABLE_MS = 6000, MIN_AFTER_FIRST_MS = 12000;
  // v1.3.3:送回時附上這個分頁查的日期(d)—— 工具端比對「目前行程」的日期,不同就不收
  //   (業主實測:改了行程後,舊行程還在跑的分頁把濟州的價格回填到大阪的矩陣上)。
  function tripDates() {
    var ds = String(location.search).match(/\d{4}-\d{2}-\d{2}/g) || [], out = [];
    for (var i = 0; i < ds.length && out.length < 8; i++) if (out.indexOf(ds[i]) < 0) out.push(ds[i]);
    return out;
  }
  function sendDates() { return (arm && arm.d && arm.d.length) ? arm.d : tripDates(); }
  function clearReloads() { try { sessionStorage.removeItem("cj_agrab_reload_" + location.pathname); } catch (e) {} }
  function send(r) { clearReloads(); disarm(); go(TOOL + "#px=" + b64url(JSON.stringify({ v: 1, host: location.hostname, price: r.min, t: Date.now(), d: sendDates() }))); }
  function fail() { clearReloads(); disarm(); go(TOOL + "#pxfail=" + b64url(JSON.stringify({ v: 1, host: location.hostname, t: Date.now() }))); }
  var advancing = 0, advFrom = 0;
  // 「搜尋結果已逾期」偵測:畫面上固定位置(彈窗)的區塊含各語言的「逾期/過期」字樣
  var EXPIRED_WORDS = ["逾期", "過期", "已失效", "过期", "expired", "abgelaufen", "expiré", "scadut", "caducad", "expirad", "verlopen",
    "期限切れ", "有効期限", "만료", "หมดอายุ", "hết hạn", "kedaluwarsa", "tamat tempoh", "wygas", "süresi dol", "истек", "закінч",
    "έληξ", "udløbet", "vanhentun", "utgått", "expirou"];
  function expiredDialog() {
    var els = document.body ? document.body.querySelectorAll("[role=dialog],[class*=modal],[class*=Modal],[class*=dialog],[class*=Dialog],[class*=popup],[class*=Popup]") : [];
    for (var i = 0; i < els.length; i++) {
      var e = els[i], t = (e.innerText || "").toLowerCase();
      if (!t || t.length > 600) continue;
      var rc = e.getBoundingClientRect(), cs = getComputedStyle(e);
      if (rc.width < 50 || rc.height < 30 || cs.display === "none" || cs.visibility === "hidden" || Number(cs.opacity) === 0) continue; // 只認真的顯示在畫面上的彈窗
      for (var j = 0; j < EXPIRED_WORDS.length; j++) if (t.indexOf(EXPIRED_WORDS[j].toLowerCase()) >= 0) return true;
    }
    return false;
  }
  function reloadOnce() {
    var key = "cj_agrab_reload_" + location.pathname, n = 0;
    try { n = parseInt(sessionStorage.getItem(key) || "0", 10) || 0; } catch (e) {}
    if (n >= 2) return false;
    try { sessionStorage.setItem(key, String(n + 1)); } catch (e) {}
    done = true; clearInterval(iv);
    location.reload();
    return true;
  }
  function tick() {
    if (done) return;
    var now = Date.now();
    if (!document.hidden) visibleMs += Math.min(now - lastTick, 3000); // 背景時間不計入
    lastTick = now;
    // v1.4.2:「搜尋結果已逾期」視窗(頁面放太久,價格可能已變)→ 自動重新整理拿最新價格(同一頁最多 2 次)
    //   另外:分頁在背景等了 8 分鐘以上才輪到前景,畫面上的價格也是舊的 → 先重新整理再讀,不送舊價。
    //   保險:就算沒認出彈窗,頁面在前景、開了 10 分鐘還沒完成,也重新整理一次(Trip.com 結果約 10 分鐘就會逾期)
    if (expiredDialog() || (!document.hidden && now - START > 480000 && (!firstPriceAt || now - START > 600000))) {
      if (reloadOnce()) return;
      if (expiredDialog()) { clearInterval(iv); fail(); return; } // 已重整 2 次仍逾期:放棄這站(寧可空白)
    }
    // 剛按了「選取」:等頁面換到下一步(換頁會重跑本腳本;同頁更新則看網址的 currentseqno 或清單變化)
    if (advancing) {
      if (curStep() === advFrom && now - advancing < 20000) return;
      advancing = 0; lastSig = ""; firstPriceAt = 0; stableAt = now;
    }
    var r = scan();
    var sig = r.count + "|" + r.min + "|" + r.bar;
    if (r.count > 0 && !firstPriceAt) firstPriceAt = now;
    if (r.count > 0 && sig === lastSig) {
      // 卡片數與最低價都 6 秒沒變,且第一次出現價格後至少等 12 秒 = 載完(即時價已換上)
      if (now - stableAt >= STABLE_MS && now - firstPriceAt >= MIN_AFTER_FIRST_MS) {
        // 有指定航司、還沒到最後一步:選最便宜那班、進下一步(去回程都要是該航司)
        if (arm && arm.al && curStep() < arm.legs) {
          if (clickSelect(r.cheapCard)) { advancing = now; advFrom = curStep(); return; }
          clearInterval(iv); if (!reloadOnce()) fail(); return; // 按不到「選取」:先重整再試;仍不行寧可空白,不回報「只有去程是該航司」的錯價
        }
        clearInterval(iv); send(r); return;
      }
    } else { lastSig = sig; stableAt = now; }
    // 逾時只看「可見時間」90 秒;背景分頁最多等 10 分鐘牆鐘,期間持續掃描
    // v1.4.1:背景分頁的航班清單常常不會載入(瀏覽器暫停背景分頁的畫面更新);舊版 10 分鐘牆鐘一到就回報「無價」,
    //   同時開好幾站時,最先開的幾個分頁(被後面的蓋到背景)幾乎都這樣失敗(業主實測 tw/hk/www 開了 9 分鐘才回報無價)。
    //   改成:從沒被看見過的分頁不因牆鐘放棄(前面的分頁抓完關掉後,它就會輪到前景),最多等 30 分鐘。
    if (visibleMs > 90000 || (visibleMs > 0 && now - START > 600000) || now - START > 1800000) {
      clearInterval(iv);
      if (r.count > 0 && !(arm && arm.al && curStep() < arm.legs)) send(r);
      // v1.4.3:逾時還沒完成(清單沒出來、或卡在第 1 步選不到下一步)→ 先自動重新整理再試(同頁最多 2 次),仍不行才回報沒抓到
      else if (!reloadOnce()) fail();
    }
  }
  var iv = setInterval(tick, 800);
  document.addEventListener("visibilitychange", function () { lastTick = Date.now(); if (!document.hidden) tick(); }); // 切回分頁立刻重掃
  window.addEventListener("focus", function () { lastTick = Date.now(); tick(); });
})();

💾 分享與列印

分享連結只帶行程與乘客/艙等設定(編在網址 # 後面,不經伺服器);你回填的參考價留在本機 localStorage,不會跟著連結走。免登入,清瀏覽器資料會清掉草稿。

❓ 常見問題

價格是自動抓的嗎?
不是。這頁完全免登入、純前端,不呼叫任何查價 API;連結幫你把行程帶進各語言站,價格由你看到後手動回填,只作比價參考。
換了語言,幣別會跟著變嗎?需要自己換匯嗎?
不需要。Trip.com 的幣別參數與語言站互相獨立——日本站、泰國站也能直接顯示台幣。本頁所有連結都已強制 curr=TWD,24 站的數字直接比大小就好;若你自己在 Trip.com 頁面上切了幣別,回填前記得切回台幣,或自行心算匯率。
開啟後行程沒帶入怎麼辦?
24 個語言站在 2026-07/08 都實測過會帶入;若遇到改版失效,先確認城市代碼是 3 碼英文字母、日期在未來,仍不行請回報,我們會更新參數。
可以配合外站四段票一起用嗎?
可以,這正是設計初衷之一:先在四段票規劃頁驗證行程規則、決定外站與日期,再把同一組四段搬過來,看哪個語言站開的價最漂亮。