1. 自動切換的原理:控制器 API 負責操作,核心負責測試
代理節點失效時,手動開啟用戶端、找到策略群組,再挑選另一個節點,既耗時也不容易持續監控。Clash 相容核心提供的控制器 API,可以讓外部程式讀取代理群組與目前選取的節點、測試指定節點的延遲,並更新可選策略群組的選擇結果。把這幾個動作串起來,就能建立一套簡單的故障切換流程。
本文以支援控制器 API 的 Clash 核心為例,並以 mihomo 的常見 API 路徑說明。不同核心與版本可能有實作差異;若使用舊版核心或特定用戶端內建核心,請先確認它支援外部控制器及相應的 API。用戶端介面可能把控制器設定放在核心設定、服務設定或設定檔覆寫中,重點是確認最後交給核心的生效設定,而不是只看訂閱原文。
故障切換的流程可分為三步:先讀取策略群組與目前節點,再以核心提供的延遲測試檢查節點,最後只在目前節點失敗且有其他節點通過測試時,才透過 API 更新選擇結果。這與修改 YAML 或重新載入整份設定不同;API 操作通常只變更執行中的群組選擇,不會替換訂閱內容。
- API 讀取:確認控制器可連線、驗證方式正確,並讀取群組的
now和all欄位。 - 節點測試:使用核心的延遲測試端點,測量節點對指定測試網址的連線結果。
- 故障切換:只有符合失敗條件時,才呼叫群組選擇端點指定另一個節點。
先確認控制器設定與策略群組
以下是 mihomo 常見的控制器設定示意,僅供合併參考。若設定中已經存在 external-controller 或 secret,請修改原有欄位,不要在同一層級重複新增。埠號 9090 是範例值,也可能與其他本機服務衝突;請以目前核心實際使用的埠號為準。
external-controller: 127.0.0.1:9090
secret: "請替換為專用且不重複的長隨機字串"
要讓 API 切換指定群組,該群組必須是可由使用者選取的 select 類型,例如名稱為「自動切換」。若使用 url-test 或 fallback,核心本身已會依相應群組邏輯測試與選擇;再用外部程式介入,可能形成兩套切換機制互相影響。自動化腳本範例採用 select 群組,且只允許切換預先列出的節點,避免把巢狀群組、直連或拒絕策略當成候選節點。
proxy-groups:
- name: 自動切換
type: select
proxies:
- 節點A
- 節點B
- 節點C
2. 動手操作:測試節點並在故障時切換
以下流程使用 Python 3 與 requests 套件,透過本機控制器 API 操作群組。先將設定儲存到用戶端實際使用的設定來源,再確認核心已成功載入群組與節點。若用戶端會在更新訂閱時重建設定,請把群組放在能持續生效的覆寫位置,避免後續更新將其移除。
- 確認控制器位址,例如
http://127.0.0.1:9090,並從核心設定取得secret。 - 確認控制器服務已啟動、群組名稱完全一致,且候選項目都是可直接測試的代理節點。
- 在執行腳本的環境設定控制器金鑰與允許切換的節點清單,不要把真實金鑰寫進程式碼或提交到共用儲存庫。
- 先手動執行一次,檢查目前節點、測試結果與切換結果,再安排定時執行。
安裝相依套件可使用 python -m pip install requests。以下範例只在目前節點測試失敗後才尋找替代節點,並從成功的候選節點中選擇測試延遲最低者。把範例中的群組名稱與節點名稱換成核心 API 回傳的實際名稱;含有空格或特殊字元的名稱也會由程式進行路徑編碼。
import os
from urllib.parse import quote
import requests
API = "http://127.0.0.1:9090"
GROUP = "自動切換"
TEST_URL = "https://www.gstatic.com/generate_204"
TIMEOUT_MS = 5000
ALLOWED_NODES = ["節點A", "節點B", "節點C"]
secret = os.environ.get("CLASH_SECRET")
if not secret:
raise SystemExit("請先設定 CLASH_SECRET")
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {secret}"})
def get_json(path, params=None):
response = session.get(
API + path, params=params, timeout=8
)
response.raise_for_status()
return response.json()
def test_delay(name):
path = "/proxies/" + quote(name, safe="") + "/delay"
result = get_json(
path,
params={"url": TEST_URL, "timeout": TIMEOUT_MS},
)
delay = result.get("delay")
if not isinstance(delay, int) or delay <= 0:
raise RuntimeError(f"{name}: API 未回傳有效延遲")
return delay
group_path = "/proxies/" + quote(GROUP, safe="")
group = get_json(group_path)
current = group.get("now")
members = set(group.get("all", []))
candidates = [
name for name in ALLOWED_NODES
if name in members and name not in {"DIRECT", "REJECT"}
]
if not current or not candidates:
raise SystemExit("群組目前節點或允許的候選節點清單不正確")
try:
current_delay = test_delay(current)
print(f"目前節點正常:{current},{current_delay} ms")
except (requests.RequestException, RuntimeError) as error:
print(f"目前節點測試失敗:{current},{error}")
healthy = []
for name in candidates:
if name == current:
continue
try:
delay = test_delay(name)
healthy.append((delay, name))
print(f"候選節點可用:{name},{delay} ms")
except (requests.RequestException, RuntimeError) as candidate_error:
print(f"候選節點不可用:{name},{candidate_error}")
if not healthy:
raise SystemExit("沒有通過測試的替代節點,保留目前選擇")
delay, selected = min(healthy)
response = session.put(
group_path,
json={"name": selected},
timeout=8,
)
response.raise_for_status()
print(f"已切換至:{selected},測試延遲 {delay} ms")
請依實際環境設定環境變數 CLASH_SECRET 與 ALLOWED_NODES 對應的程式清單。程式先讀取群組的 now 與 all,再逐一測試預先允許的節點;只有群組目前節點無法通過測試時,才呼叫 PUT /proxies/{群組名稱},以 JSON 內的 name 欄位指定選擇結果。若控制器回應未授權、找不到資源或其他 HTTP 錯誤,應先檢查金鑰、埠號、群組名稱及核心版本,不要直接忽略錯誤後持續重試。
3. 監測頻率、切換條件與權限保護
自動化能否穩定運作,除了 API 呼叫正確,也取決於測試方式與切換策略。控制器的延遲測試是在核心中透過指定代理向測試網址發出請求;它不是對所有網站、所有協定或所有目的地的全面健康檢查。測試網址若無法從目前網路存取,即使代理節點本身正常,也可能被判斷為失敗。請選擇可穩定回應的網址,並在手動切換後確認測試結果與實際使用情境一致。
- 監測間隔:可先從每 60 秒檢查一次開始。過於頻繁的測試會增加請求量,也可能讓短暫網路抖動觸發連續切換。
- 逾時設定:
TIMEOUT_MS = 5000表示單次測試使用 5000 毫秒作為核心測試逾時參數。外層 HTTP 請求的逾時也應大於核心測試時間,讓 API 有機會完成並回傳結果。 - 錯誤門檻:正式使用時,可要求連續兩至三次失敗才切換,避免一次短暫丟包就改變節點。恢復條件也可要求目前節點連續成功數次,降低來回切換。
- 切換節制:若只希望在故障時恢復連線,不要因候選節點偶爾快幾十毫秒就反覆更換。範例只有在目前節點測試失敗時才比較候選節點,適合以可用性為優先的情境。
- 記錄結果:保留時間、群組、目前節點、測試延遲、失敗原因及切換結果。日誌不要包含金鑰、訂閱網址或節點驗證資訊。
控制器金鑰應透過作業系統環境變數、權限受限的祕密管理機制或其他安全方式提供。若腳本以排程工作執行,請確認它使用的系統帳號能讀取金鑰,且一般使用者無法任意讀取該排程設定。API 僅綁定迴圈介面並不代表可省略驗證;啟用 secret,並避免將控制器埠透過路由器、容器或防火牆轉送到外部網路。
初次測試時,建議先在終端機手動執行,不要一開始就設定每分鐘執行的排程。確認正常後,再使用 Windows 工作排程器、macOS 的 launchd 或 Linux 的 systemd timer 等工具安排週期工作;排程方式應配合實際作業系統及用戶端是否在背景持續執行。若用戶端關閉時核心也會停止,排程腳本無法替它完成節點測試。
4. API 故障排查與方案取捨
遇到腳本無法切換時,先把問題拆成控制器連線、API 驗證、群組讀取、節點測試與選擇更新五個階段。每個階段的回應不同,逐段檢查比直接重啟整個用戶端更容易找出原因。
- 連線被拒絕或逾時:確認核心仍在執行、控制器監聽位址與埠號正確,並檢查用戶端是否使用另一份設定或另一個核心。
- HTTP 401 或 403:確認請求標頭為
Authorization: Bearer ...,金鑰與生效設定一致,且環境變數沒有多餘空白或換行。 - 找不到群組:從
GET /proxies或群組 API 的實際回應核對名稱。中文名稱、空格及特殊字元在 URL 路徑中必須正確編碼。 - 延遲測試失敗:確認候選節點仍在群組內,測試網址可用,且核心能透過該節點連線。測試失敗不等於節點的所有流量必然失效,應結合用戶端日誌與實際連線檢查。
- 切換請求失敗:確認群組類型可由使用者選擇、JSON 欄位名稱為
name,且指定節點是該群組的直接成員。
若想要的是「由核心自動選擇延遲最低的節點」,通常優先使用核心支援的 url-test 或 fallback 群組,減少額外服務與金鑰管理。外部 API 腳本較適合需要自訂候選清單、連續失敗門檻、企業監控記錄或通知流程的進階情境。兩者各有用途,不必為了自動化而額外維護一支長期無人檢查的程式。
無論選擇哪種方式,節點切換都只處理出口選擇,不會修正錯誤規則、DNS 故障、本機網路中斷或訂閱失效。切換後若仍無法連線,請查看核心日誌中的實際出站與錯誤訊息,並確認連線是否經過預期的策略群組。
常見問題
控制器 API 可以不設定 secret 嗎?
不建議。即使控制器只監聽 127.0.0.1,本機其他程式或使用者仍可能嘗試連線。設定專用金鑰並避免公開控制器埠,可降低未授權讀取或變更代理選擇的風險。
API 回傳延遲就代表節點完全正常嗎?
不代表。延遲測試只表示該節點在當下對指定測試網址完成了測試。不同網站、DNS 路徑、TCP 或 UDP 流量仍可能有不同結果;請把測試結果視為一個監測訊號,而不是對所有服務的保證。
為什麼節點會在幾個選項間反覆切換?
常見原因包括測試網址不穩定、監測過於頻繁、單次失敗就切換,或核心群組與外部腳本同時管理節點。增加連續失敗門檻、放寬檢查間隔,並確認只有一套機制負責切換,通常能減少抖動。
API 切換後,設定檔中的節點順序會改變嗎?
通常不會。API 更新的是執行中的策略群組選擇,不等於編輯或重寫 YAML 設定檔。用戶端重載設定或重新啟動核心後,選擇結果是否保留取決於核心與用戶端行為,請在自己的版本中驗證。