先理解 Clash API:控制器不是節點本身
所謂 Clash API 自動切換節點,並不是讓腳本直接接管每一條網路封包,而是透過核心提供的外部控制器,讀取目前代理、策略組與連線狀態,再依照健康檢查結果選擇一個較適合的節點。Clash、Clash Verge Rev、Mihomo 以及部分相容客戶端,通常都能透過外部控制器提供設定查詢、策略組切換、代理延遲測試與連線紀錄等能力;不過不同核心版本與客戶端對端點名稱、參數格式及權限行為可能略有差異,因此不要把某一套 GUI 的按鈕名稱直接當成所有核心都支援的 API 規格。
最重要的觀念是分清楚三個物件。第一是代理節點,例如某個 Shadowsocks、VMess、Trojan 或其他出站代理;第二是策略組,例如 select、url-test、fallback 或由多個代理組合而成的自動選擇池;第三是控制器 API,它只負責讀取和修改核心的運作狀態。腳本真正應該切換的,多半是策略組目前選中的成員,而不是頻繁改寫整份 YAML。直接重寫設定檔容易造成格式錯誤、訂閱更新覆蓋、核心重載中斷連線等額外問題。
在開始寫腳本前,請先在客戶端設定中確認外部控制器是否已啟用,以及它監聽的位址和連接埠。常見形式可能是本機的 127.0.0.1:9090,也可能由 Mihomo 配置成其他埠號;若控制器綁定在 0.0.0.0,代表區域網路甚至其他可達網段可能有機會連進來,這會把 API 令牌暴露風險放大。除非你確實需要遠端管理,否則優先使用本機回環位址,並為控制器設定密碼。不要把真實令牌寫入公開 Git 儲存庫、Shell 歷史或聊天截圖。
一般排查順序可以分成四步。先查詢策略組清單,確認腳本使用的是實際存在的組名;再查詢該組內有哪些成員,避免把介面顯示名稱誤當成 YAML 中的節點名稱;接著讀取目前選擇結果與延遲資料;最後才呼叫切換端點。節點名稱可能包含空格、斜線、地區符號或 emoji,放入 URL 時必須正確編碼,不能只靠字串拼接。若你在 Windows PowerShell、macOS 終端與 Linux Shell 之間移植腳本,也要留意引號、環境變數和 URL 編碼方式的差異。
健康檢查:不要只看一次延遲數字
自動切換最容易犯的錯,是把一次 HTTP 探測成功等同於節點長期健康。實際上,探測網址能否開啟只代表當下某一個目的地、某一個協定與某一個時間點可用;它不一定能代表你的工作流,例如 Git、影音、即時通訊或 API 請求都正常。選擇測試網址時,應優先使用穩定、回應內容簡單、對所在網路環境有意義的 HTTPS 端點。若網址本身被快取、遭到區域限制,或需要登入才能完成請求,結果就會混入服務端因素,不能單純歸因於代理節點。
對節點進行比較時,至少應同時觀察延遲、成功率與連續失敗次數。延遲低但偶爾逾時的節點,未必比延遲稍高但穩定的節點更適合作為長時間主節點。建議採用滑動視窗概念:在一段時間內重複測試數次,計算中位數或截尾平均值,並記錄失敗比例。當一次探測失敗時先保留現有節點,不要立即切換;只有在連續兩至三次失敗,或在指定時間窗內失敗率超過門檻時,才進入故障轉移。這能避免單次 DNS 抖動、遠端服務短暫忙碌或本機 Wi-Fi 干擾造成來回跳線。
Clash API 常見的延遲測試會要求核心透過指定策略或代理連線到測試 URL,再回傳毫秒值。不同核心可能使用不同查詢參數,例如測試網址、逾時上限或代理名稱的編碼方式;因此腳本不應把某一版本回應中的欄位結構視為永遠不變。開發時先把完整 HTTP 狀態碼、回應本文與時間戳記寫入日誌,等確認流程穩定後,再只保留必要欄位。遇到回傳 404,優先懷疑端點或組名;遇到 401、403,則檢查控制器密碼;若是連線逾時,才進一步判斷控制器是否正在運作。
以下是一個以 Python 標準函式庫為主的簡化範例。它示範如何讀取 API、取得策略組內的候選節點,再逐一發出延遲測試;實際端點名稱與回應欄位請依你使用的 Clash 或 Mihomo 版本文件調整。範例刻意沒有把令牌硬編碼在檔案裡,而是從環境變數取得。
import json
import os
import urllib.parse
import urllib.request
CONTROLLER = os.getenv("CLASH_CONTROLLER", "http://127.0.0.1:9090")
SECRET = os.getenv("CLASH_SECRET", "")
GROUP = os.getenv("CLASH_GROUP", "AUTO")
TEST_URL = "https://www.gstatic.com/generate_204"
def request_json(path):
req = urllib.request.Request(f"{CONTROLLER}{path}")
if SECRET:
req.add_header("Authorization", f"Bearer {SECRET}")
with urllib.request.urlopen(req, timeout=8) as response:
return json.load(response)
group = request_json("/proxies/" + urllib.parse.quote(GROUP, safe=""))
members = group.get("all", [])
for name in members:
encoded = urllib.parse.quote(name, safe="")
target = "/proxies/" + encoded + f"/delay?url={urllib.parse.quote(TEST_URL)}&timeout=5000"
try:
result = request_json(target)
print(name, result.get("delay"))
except Exception as error:
print(name, "failed", error)
這段程式只適合作為驗證 API 的起點,不能直接當成生產環境的故障轉移器。正式版本需要補上節點過濾、錯誤分類、重試退避、日誌輪替與切換鎖。也要明確排除特殊成員,例如 DIRECT、REJECT、另一個策略組或用來承載負載平衡的虛擬組,否則腳本可能把不可連線的保留項目當成普通節點。若策略組由訂閱自動生成,成員名稱也可能隨更新改變,腳本應先比對實際回傳清單,而不是永久依賴一份過期的節點名稱表。
建立可控的故障轉移腳本
一個可靠的腳本應把「檢查」與「切換」分成兩個階段。檢查階段先收集所有候選節點的結果,完成排序與門檻判斷;切換階段只在新節點明顯優於目前節點,或目前節點已確認失效時執行一次。這種設計能避免每個節點測完就切一次,造成使用者連線連續重置。當前節點仍然成功,而且新候選只快了十幾毫秒時,也不值得切換;可以設定最小改善幅度,例如新節點必須至少快二十至五十毫秒,或目前節點連續失敗兩輪,才允許改變策略。
下面是推薦的決策邏輯。先取得策略組目前的 now 值,再針對候選清單測試;將逾時、連線拒絕、HTTP 錯誤與 API 驗證錯誤分開記錄。若所有節點都失敗,不要把策略組切到一個猜測中的名稱,應保留目前狀態並發出通知。若找到可用節點,還要確認它與目前節點不同,並在切換前再次查詢一次狀態,避免另一個手動操作或第二個排程程序剛好已經完成切換。
def choose_node(results, current, minimum_gain=30):
usable = [
item for item in results
if item["delay"] is not None and item["delay"] < 5000
]
if not usable:
return None
usable.sort(key=lambda item: item["delay"])
best = usable[0]
current_delay = next(
(item["delay"] for item in usable if item["name"] == current),
None
)
if current_delay is not None and best["name"] != current:
if current_delay - best["delay"] < minimum_gain:
return current
return best["name"]
切換成功後不要只看 API 回應是否是 200。應該等待短暫時間,再重新讀取策略組的 now 欄位確認實際狀態已更新,必要時再發出一次輕量探測。部分應用程式會維持既有 TCP 連線,策略組改變後舊連線仍可能沿用原路徑,所以「切換完成」不等於所有現有連線立刻換線。若你的目標是讓新建立的請求走新節點,應在日誌中觀察新連線;若需要中斷舊連線,則必須評估對工作流的影響,不能把清空全部連線當成預設動作。
為避免兩個排程同時操作同一個控制器,可以加入檔案鎖或程序鎖。Windows 可用命名互斥或簡單的鎖檔,Linux 與 macOS 可透過 flock 包住執行命令。鎖檔必須有過期機制,否則腳本被強制終止後,下一次排程可能永遠以為仍有程序執行。更穩妥的做法是記錄程序識別碼、建立時間與最後心跳,只有在確認持有鎖的程序不存在時才清理殘留狀態。
故障轉移也需要冷卻時間。假設 A 節點短暫失敗,腳本切到 B;如果下一輪 A 恢復,卻因延遲略低而馬上切回去,兩個節點之間會不斷來回震盪。可以為剛被判定失敗的節點設定暫時隔離,例如五分鐘內不重新選用;對剛切換到的新節點則設定最短駐留時間,例如三至十分鐘。只有發生硬故障時才繞過冷卻時間。這種「失敗隔離+最短駐留」通常比單純降低檢查週期更能改善長時間穩定性。
排程、備援與長時間運作的維護方法
腳本測試穩定後,才適合交給作業系統排程。Linux 可以用 systemd timer 或 cron,macOS 可用 launchd,Windows 則可用工作排程器。排程間隔不宜只看節點切換速度:過於頻繁會增加控制器請求、測試站流量和日誌噪音,也會讓節點在尚未完成恢復時被重複判定。一般桌面使用可從每一至五分鐘一次開始,搭配連續失敗門檻;對即時服務或重要工作流,則應先在低頻模式觀察一段時間,再依實際失敗模式調整。
執行帳戶應採用最小權限原則。腳本通常只需要讀取自身設定、寫入日誌,以及連線到本機控制器,不需要系統管理員權限。Windows 工作排程器中可取消不必要的「使用最高權限執行」;Linux 可建立專用使用者與專用服務單元,限制其可讀目錄。令牌設定檔的權限也要收緊,避免同一台多人共用的電腦上被一般使用者讀取。若控制器必須接受區域網路請求,應在防火牆限制來源網段,並確認傳輸是否需要額外的 TLS 或反向代理保護。
監控內容至少包括:檢查開始與結束時間、目前節點、候選節點延遲、失敗原因、是否執行切換、切換後驗證結果,以及腳本版本。不要只寫「切換成功」四個字,因為日後很難知道它是從哪個節點切到哪個節點,也無法和使用者當時的斷線時間對照。日誌可以採用 JSON Lines 或固定欄位格式,並設定大小上限與保留天數。若服務涉及帳戶或內部網域,請遮蔽 URL 中的令牌、查詢參數與敏感主機名稱,避免排障資料本身造成外洩。
備援策略不應只準備一個「最快節點」。可以按用途建立不同的策略組:日常瀏覽使用自動測試組,工作 API 使用較穩定的主備組,下載或大量連線則交給符合核心語義的負載分散組。腳本切換時只操作指定組,不要用全域搜尋方式修改所有出站。若主要節點池全部失效,可以設定明確的最後備援,例如另一個策略組或直連,但必須根據工作流的安全需求決定;對需要代理才能安全連線的服務,盲目退回 DIRECT 反而可能造成資料直接外送。
你還需要定期驗證訂閱更新與核心升級後的相容性。訂閱可能重新命名節點、刪除策略組或改變 API 可見欄位;核心更新也可能調整延遲測試行為、授權標頭或錯誤回應。建議先在非主要設定檔上執行腳本,保留一份已知可用的 YAML 與腳本版本,並在更新後做一次手動切換與回滾測試。若客戶端提供「重載設定」或「重啟核心」選項,請確認腳本不會在重載期間誤判所有節點失敗,最好透過健康狀態或重試退避等待核心恢復。
常見的故障表現可以用幾個方向快速縮小範圍:API 回傳未授權,通常是令牌格式或控制器設定問題;查不到策略組,通常是組名大小寫、URL 編碼或目前載入的設定檔不一致;延遲全部逾時,可能是測試 URL、DNS、核心出站或本機防火牆問題;切換回應成功但流量沒有改變,則要檢查應用程式是否真的使用該策略組、既有連線是否仍存活,以及是否存在第二個代理程式覆蓋系統設定。將這些層次分開,遠比不停更換節點有效。
相較於只依賴 url-test 的固定週期測速,腳本化故障轉移能加入連續失敗、冷卻時間、最小改善幅度與通知等條件;相較於某些只提供手動下拉選單的輕量客戶端,Clash V.CORE 則能把控制器 API、策略組、日誌與多平台核心行為整合到同一套可觀察流程中。若你不想自行處理不同殼層的 API 路徑、節點名稱與排程細節,可以先從穩定的控制器設定與最小權限腳本開始,再使用 Clash V.CORE 測試本文的健康檢查和備援邏輯,前往下載 Clash V.CORE建立可長時間維護的自動切換環境。