Clash 外部控制器自動換節點:故障切換與腳本實作
節點失效或延遲飆高時,靠手動更換代理既慢又容易中斷工作。本篇以外部控制器為核心,建立可持續執行的節點健康檢查與自動換線流程,並說明策略組更新、API 驗證和服務化部署的實務細節。
外部控制器能做什麼
Clash 的外部控制器是一組供面板與自動化程式使用的 HTTP API。它不負責建立代理節點,而是把目前已載入的設定、代理列表、策略組狀態與連線資訊暴露出來,讓外部程式可以讀取狀態或發出切換指令。Clash Verge、Clash Verge Rev、Mihomo Party 與其他基於 mihomo 的用戶端,通常都能在設定頁看到外部控制器位址與密鑰。
節點自動切換的核心流程可以拆成四個動作:取得策略組中的節點名稱,逐一對節點執行延遲或連線測試,依結果選出可用節點,最後透過 API 把策略組切換到新節點。這個流程和用戶端畫面上的手動點選相同,只是把判斷條件與執行頻率固定下來。
要先分清楚「節點」與「策略組」不是同一個物件。節點是實際的代理出口,例如某個伺服器或某條代理設定;策略組則是把多個節點包在一起的選擇器,名稱可能是 PROXY、🚀 節點選擇 或訂閱服務商自訂的名稱。API 切換時,請求的目標通常是策略組,請求內容才是要選中的節點。
先保護外部控制器
外部控制器等同於代理核心的管理入口。不要把它綁定在公網位址,也不要把密鑰放進公開腳本、聊天紀錄或版本庫。單機腳本優先使用 127.0.0.1:9090;只有確實需要區域網路控制時,才設定區域網路位址並搭配防火牆限制來源。
開啟 API 與確認策略組
在 mihomo 設定中,外部控制器由 external-controller 指定監聽位址,密鑰由 secret 指定。下面是一份只供本機腳本使用的示例設定,其中密鑰是刻意編寫的假值,實際使用時請換成長度足夠的隨機字串。
external-controller: 127.0.0.1:9090
secret: "demo-local-secret-change-me"
修改設定後要重新載入設定檔或重啟核心,再確認埠是否真的在監聽。Windows 可以用 netstat -ano | findstr 9090,macOS 與 Linux 可以用 lsof -iTCP:9090 -sTCP:LISTEN。若用戶端是 Clash Verge 或 Clash Verge Rev,也要注意圖形介面顯示的 API 埠可能由當前核心設定檔覆蓋,不能只看全域偏好設定。
API 驗證使用 HTTP 標頭 Authorization: Bearer 密鑰。先呼叫根端點確認控制器可連線,再查詢全部代理。查詢結果中的 proxies 是物件,鍵名就是節點或策略組名稱;策略組則會包含 all、now 與 type 等欄位。
curl -H "Authorization: Bearer demo-local-secret-change-me" \
http://127.0.0.1:9090/proxies
不要把策略組名稱寫死成英文 PROXY 就直接部署。訂閱設定經常使用表情符號、空格或全形標點,例如 🚀 節點選擇;名稱只要有一個字元不同,API 就會回傳找不到代理。最穩定的做法是先列出所有名稱,再透過設定檔中的固定名稱、環境變數或明確關鍵字選定目標。
| 端點 | 用途 | 常見方法 |
|---|---|---|
/ | 確認控制器是否可連線 | GET |
/proxies | 取得節點、策略組與目前選擇 | GET |
/proxies/{name} | 取得單一節點或策略組詳情 | GET |
/proxies/{group} | 切換策略組目前選中的節點 | PUT |
/proxies/{name}/delay | 測試節點延遲 | GET |
健康檢查與自動選線邏輯
延遲測試不能只看數字最小的節點。單次測試可能受到瞬間擁塞、DNS 解析或目標網址不可達影響,所以腳本至少要同時考慮測試成功、延遲上限、連續失敗次數與切換冷卻時間。若每次輪詢都切換到當下最快的節點,網路稍有抖動就會頻繁換線,反而造成連線中斷與登入狀態失效。
測試網址應選擇穩定、回應內容不重要的 HTTPS 目標,並設定合理的逾時時間。這裡使用 https://www.example.com/generate_204 作為格式示例;實際部署時請換成你能穩定存取的測試網址。若策略組中混有不支援該網址協定的節點,測試失敗只能代表「這個節點無法完成本次測試」,不能直接推論節點永久失效。
- 讀取策略組的
all清單,排除策略組、DIRECT、REJECT與不希望自動選用的名稱。 - 對每個候選節點呼叫延遲端點,傳入經 URL 編碼的測試網址與逾時毫秒數。
- 捨棄逾時、HTTP 錯誤或回傳格式不完整的結果,再套用延遲上限,例如 800 毫秒。
- 保留目前節點的黏著時間,只有目前節點失敗或候選節點明顯快很多時才切換。
- 切換後等待下一輪確認,不要在同一輪連續 PUT 多次,避免核心與上游連線同時重建。
下面的 Python 範例使用標準函式庫,不依賴第三方套件。它假設策略組名稱由環境變數提供,每輪先讀取策略組,再逐一測試候選節點。requests 等第三方套件雖然寫法較短,但在服務化部署時需要額外管理虛擬環境;標準函式庫更容易搬到 Windows 工作排程或 Linux systemd。
#!/usr/bin/env python3
import json
import os
import time
import urllib.parse
import urllib.request
import urllib.error
API = os.getenv("CLASH_API", "http://127.0.0.1:9090")
SECRET = os.environ["CLASH_SECRET"]
GROUP = os.getenv("CLASH_GROUP", "PROXY")
TEST_URL = os.getenv("TEST_URL", "https://www.example.com/generate_204")
TIMEOUT_MS = int(os.getenv("TEST_TIMEOUT_MS", "5000"))
MAX_DELAY = int(os.getenv("MAX_DELAY_MS", "800"))
INTERVAL = int(os.getenv("CHECK_INTERVAL_SEC", "60"))
SWITCH_MARGIN = int(os.getenv("SWITCH_MARGIN_MS", "120"))
def request(path, method="GET", body=None):
data = None if body is None else json.dumps(body).encode()
req = urllib.request.Request(API + path, data=data, method=method)
req.add_header("Authorization", "Bearer " + SECRET)
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=TIMEOUT_MS / 1000 + 2) as res:
return json.loads(res.read().decode())
def delay(node):
name = urllib.parse.quote(node, safe="")
target = urllib.parse.quote(TEST_URL, safe="")
path = f"/proxies/{name}/delay?timeout={TIMEOUT_MS}&url={target}"
try:
result = request(path)
value = int(result.get("delay", 0))
return value if value > 0 else None
except (urllib.error.HTTPError, urllib.error.URLError,
TimeoutError, ValueError, json.JSONDecodeError):
return None
def check_once():
group_name = urllib.parse.quote(GROUP, safe="")
info = request("/proxies/" + group_name)
candidates = info.get("all", [])
current = info.get("now")
results = {}
for node in candidates:
if node in ("DIRECT", "REJECT") or node == GROUP:
continue
value = delay(node)
if value is not None and value <= MAX_DELAY:
results[node] = value
if not results:
print("沒有可用節點,保留目前選擇:", current, flush=True)
return
best, best_delay = min(results.items(), key=lambda item: item[1])
current_delay = results.get(current)
should_switch = current not in results
if current_delay is not None:
should_switch = best_delay + SWITCH_MARGIN < current_delay
if should_switch and best != current:
request("/proxies/" + group_name, "PUT", {"name": best})
print(f"已切換: {current} -> {best}, {best_delay} ms", flush=True)
else:
print(f"維持: {current}, 最佳 {best} {best_delay} ms", flush=True)
while True:
try:
check_once()
except Exception as exc:
print("本輪檢查失敗:", exc, flush=True)
time.sleep(INTERVAL)
避免誤切換的實務細節
API 可以很快完成切換,但「能切換」不代表「應該立刻切換」。最常見的錯誤是把一次超時當成節點死亡,下一輪又把節點切回來,最後形成來回震盪。建議設定至少兩輪失敗才標記為不可用,並讓成功節點維持一段最短使用時間。例如檢查週期設為 60 秒,冷卻時間設為 5 分鐘,就能避開短暫封包遺失造成的頻繁換線。
- 設定延遲門檻:只接受小於 800 或 1000 毫秒的結果。門檻應依所在地網路與服務用途調整,串流與互動服務不適合沿用同一個數值。
- 設定切換幅度:最佳節點只比目前節點快 10 毫秒時不要切換。使用 100 至 200 毫秒的差值,能減少延遲測量誤差帶來的抖動。
- 保留目前節點:若目前節點仍能通過測試,優先維持連線。自動化的目標是恢復可用性,不是每分鐘追逐最低數字。
- 限制候選範圍:可用名稱前綴、地區標籤或固定清單排除高流量、限時與特殊用途節點,避免腳本選到不適合日常使用的出口。
- 記錄每輪結果:至少記錄時間、策略組、目前節點、最佳節點、延遲與錯誤原因。沒有紀錄時,很難分辨是 API 失效、節點逾時還是測試網址不可達。
不要覆寫訂閱設定
PUT /proxies/{group} 通常只改變目前執行中的策略組選擇,不會把選擇永久寫回遠端訂閱。用戶端重新載入設定或訂閱更新後,策略組可能恢復預設值,因此腳本必須能在核心重載後重新判斷,不能把一次切換當成永久修改。
若需要驗證整條代理鏈,可以把測試網址與實際使用的協定分開設計。延遲端點主要反映核心經該節點建立請求的時間,不等同於影片播放、檔案下載或長連線穩定度。對重要工作環境,可在切換後另外執行一次應用層健康檢查,但不要把過多探測流量送進節點。
服務化部署與故障排查
手動在終端機執行腳本適合初次驗證,長期使用則應交給作業系統的服務管理器。Linux 上可用 systemd,Windows 可用工作排程器或把腳本包成服務。無論平台為何,密鑰都不應直接寫在腳本裡,建議透過環境檔、系統密鑰儲存區或只有服務帳號可讀的檔案注入。
Linux 的 systemd 服務可以使用以下形式。路徑請按照實際安裝位置修改,並確保執行帳號有權讀取腳本與環境檔。
[Unit]
Description=Clash node auto switch
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=clashbot
WorkingDirectory=/opt/clash-autoswitch
EnvironmentFile=/etc/clash-autoswitch.env
ExecStart=/usr/bin/python3 /opt/clash-autoswitch/autoswitch.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
環境檔可以只保留必要值,例如 CLASH_API=http://127.0.0.1:9090、CLASH_GROUP=PROXY、CLASH_SECRET=demo-local-secret-change-me。建立後將權限限制為服務帳號或 root 可讀,再執行 systemctl daemon-reload、systemctl enable --now clash-autoswitch。查看 journalctl -u clash-autoswitch -f 能即時確認每輪檢查與切換結果。
排查時按照「核心、認證、名稱、節點」的順序最有效。先用瀏覽器或 curl 確認 API 埠可達;接著確認 Authorization 標頭與密鑰完全一致;再查 /proxies 中是否真的存在策略組與候選節點;最後才分析延遲測試失敗。若回傳 401,通常是密鑰或標頭格式錯誤;若回傳 404,常見原因是策略組名稱未經 URL 編碼或名稱已因訂閱更新而改變;若所有節點都超時,應先用用戶端手動測試,排除測試網址、系統防火牆與核心本身的問題。
完成部署後,不要只驗證「腳本有在執行」。可以暫時把延遲門檻調低,或手動讓目前節點不可用,觀察腳本是否只切換一次、日誌是否記錄原因、用戶端畫面是否同步更新。測試結束後恢復正常門檻,再確認重啟核心、更新訂閱與重啟腳本三種情況都能自動恢復。這樣建立的流程,才是可長期維持的故障切換,而不是一個只在終端機裡偶爾成功的示範程式。
外部控制器故障切換自動化腳本
先確認核心與控制器
開始寫腳本前,先安裝仍在維護的 Clash 用戶端,確認 mihomo 核心、外部控制器埠與策略組名稱,再依本文流程逐步測試。
下載 Clash 用戶端
規則分流的前提是用戶端先接管流量。前往下載中心依平台選擇用戶端,再回到教學文章完成系統代理或 TUN 接管設定。