Clash API自动切换节点:进阶配置与脚本实践

为什么要用 Clash API 自动切换节点 详解

很多人第一次配置 Clash 自动选节点时,会直接把策略组类型改成 url-test,然后认为问题已经解决:节点会测速,延迟最高的线路会被淘汰,客户端也会自动选择响应最快的节点。但真实使用中,自动测速不等于可靠的故障恢复。测速地址可能能访问,目标网站却仍然超时;某个节点短时间延迟很低,实际下载速度却很慢;策略组刚完成切换,长连接又因为出口变化而中断。对于开发工具、远程桌面、API 请求、视频会议等场景,仅靠图形界面里的自动测速往往不够。

Mihomo 及兼容的 Clash 客户端通常会开放 external-controller,让本机脚本通过 HTTP API 读取代理组、查看当前选择、执行延迟测试并切换节点。Clash Verge、Clash Verge Rev、Mihomo Party 和部分其他客户端的界面名称可能不同,但核心思路基本一致:先在配置文件中启用控制器,再让脚本调用 /proxies、/group 或代理延迟测试接口。这样可以把「节点失效 → 检查候选节点 → 选择可用出口 → 验证恢复」变成可重复执行的流程。

本文重点讨论本机自动化,不涉及绕过服务商限制,也不建议把控制器直接暴露到公网。你将看到如何设计 API 安全参数、如何识别策略组名称、如何用 YAML 提供基础候选节点、如何用 Shell 快速切换,以及如何用 Python 写出带超时、阈值、重试和日志的自动切换脚本。即使你最终仍然使用手动选择,这套方法也能帮助你理解节点延迟升高、策略组频繁跳转和连接恢复缓慢的原因。

安全提醒:external-controller 本质上是控制 Clash 的管理接口。请优先绑定 127.0.0.1,并设置足够复杂的密钥;不要为了让手机或远程服务器调用,直接监听 0.0.0.0。如果确实需要跨设备访问,应放在可信内网、VPN 或带认证的反向代理后面。

先配置 external-controller 与可控的策略组 全面解析

自动切换的第一步不是写脚本,而是确认客户端真的提供了可用的控制器。打开当前使用的配置文件或覆写文件,在顶层加入类似下面的内容。具体字段是否被客户端覆盖,取决于内核版本;如果图形客户端的设置页已经提供「外部控制器」「External Controller」或「API 密钥」输入框,应优先使用界面配置,再通过 API 读取结果确认。

external-controller: 127.0.0.1:9090
secret: "change-this-to-a-long-random-secret"

mode: rule

proxy-groups:
  - name: Auto-Select
    type: url-test
    url: http://www.gstatic.com/generate_204
    interval: 300
    tolerance: 80
    proxies:
      - Node-A
      - Node-B
      - Node-C
      - DIRECT

external-controller 后面的端口可以按需修改,但要避开已经被其他程序占用的端口。secret 用于请求头 Authorization: Bearer 密钥,不能把真实密钥提交到 Git 仓库、公开脚本或截图中。若只在本机使用,绑定 127.0.0.1 可以明显缩小攻击面。配置修改后,重载配置或重启内核,再访问 http://127.0.0.1:9090/version 检查接口是否响应。

策略组名称必须完全匹配,包括大小写、空格和符号。很多自动切换脚本失败,并不是 API 地址错误,而是脚本写了 Proxy,配置里实际却叫 🚀 节点选择。可以用下面的请求读取所有代理和策略组名称:

curl -s \
  -H "Authorization: Bearer change-this-to-a-long-random-secret" \
  http://127.0.0.1:9090/proxies

返回结果中的 proxies 对象包含节点、策略组和当前选中项。策略组一般会有 all、now、type 等字段;其中 all 是候选列表,now 是当前出口。不要假设所有组都叫「自动选择」,也不要把 DIRECT、REJECT 或另一个策略组误当作真实节点。

url-test、fallback 与脚本控制怎么选(详解)

url-test 适合让 Mihomo 按固定间隔测量候选节点,并在满足容差时自动选择延迟较低的节点。它的优点是配置简单,缺点是测试 URL 与实际业务不一定相同,而且网络抖动可能导致选择频繁变化。fallback 更偏向可用性优先:当前节点失败后才尝试下一个,但「失败」的判断仍取决于测试地址和间隔。若你需要根据多个域名、HTTP 状态码、业务响应时间或时间段切换,就应由外部脚本接管决策。

比较稳妥的做法是让策略组负责基础健康检查,让脚本负责异常时的二次处理。例如平时使用 url-test,当连续两次业务请求失败,脚本才调用 API 对候选节点逐个测速,并把第一个低于阈值的节点切换进去。这样既不会每几秒频繁改节点,也不会在当前出口已经失效时完全依赖客户端内部判断。

动手实践:读取节点、测速并切换策略组 一次讲透

下面的步骤适用于已经运行 Clash Verge Rev、Mihomo Party 或其他 Mihomo 客户端的本机环境。示例假设控制器地址是 127.0.0.1:9090,策略组名称是 Auto-Select。请先替换密钥和组名,再执行命令。

  1. 确认控制器可访问:用 curl http://127.0.0.1:9090/version 请求版本接口;如果返回连接拒绝,先检查客户端是否启动、端口是否一致,以及配置重载是否成功。
  2. 确认认证有效:为请求增加 Authorization: Bearer 你的密钥;若返回 401,重点检查密钥是否包含多余空格、引号或换行。
  3. 列出代理组:读取 /proxies,找到真实的策略组名称和候选节点,确认目标节点处于 all 列表中。
  4. 逐个执行延迟测试:调用 /proxies/节点名称/delay,为节点名称进行 URL 编码,并设置合理的超时时间,避免某个失效节点拖住整个脚本。
  5. 选择并切换:向 /proxies/策略组名称 发送 JSON 请求,例如 {"name":"Node-A"};切换完成后再次读取该策略组,确认 now 已更新。
  6. 验证业务恢复:不要只看 API 返回 204 或 200,还要访问实际使用的域名,检查 DNS、TLS、状态码和响应耗时是否符合预期。

Shell 适合做快速检查和定时任务。以下脚本先测试多个节点,再选择延迟低于阈值的第一个节点。节点名称含有空格或特殊字符时,使用 --data-urlencode 可避免路径解析错误;生产环境还应把密钥放入环境变量,而不是直接写进文件。

#!/usr/bin/env bash
set -u

API="http://127.0.0.1:9090"
SECRET="${CLASH_SECRET:?set CLASH_SECRET first}"
GROUP="Auto-Select"
TEST_URL="https://www.gstatic.com/generate_204"
MAX_DELAY=800

switch_node() {
  local node="$1"
  curl -fsS -X PUT \
    -H "Authorization: Bearer ${SECRET}" \
    -H "Content-Type: application/json" \
    --data "{\"name\":\"${node}\"}" \
    "${API}/proxies/${GROUP}"
}

for node in "Node-A" "Node-B" "Node-C"; do
  encoded=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$node")
  result=$(curl -sS -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer ${SECRET}" \
    "${API}/proxies/${encoded}/delay?timeout=5000&url=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("'${TEST_URL}'", safe=""))')")
  if [ "$result" -ge 200 ] && [ "$result" -lt 400 ]; then
    echo "candidate: ${node}"
    switch_node "$node" && exit 0
  fi
done

echo "no healthy node found" >&2
exit 1

Shell 版本的重点是流程而不是复杂的排序:先限制请求时间,再在成功后立即切换。如果要按延迟排序,应让 API 返回具体毫秒值并保存到数组中,而不是把 HTTP 状态码当成延迟。还要注意,节点测速成功只说明控制器能够完成测试,不代表应用程序的每一种请求都能成功,因此定时脚本最好增加日志和冷却时间。

用 Python 构建带阈值与恢复机制的脚本 深入理解

Python 更适合处理节点列表、异常分类和连续失败计数。一个实用脚本至少应包含四个保护:单节点请求超时、失败节点跳过、整体切换冷却时间,以及切换后业务验证。否则定时任务可能在每次运行时来回跳转,或者因为一个永远不响应的节点导致后续节点永远没有机会测试。

import os
import time
from urllib.parse import quote

import requests

API = os.getenv("CLASH_API", "http://127.0.0.1:9090")
SECRET = os.environ["CLASH_SECRET"]
GROUP = os.getenv("CLASH_GROUP", "Auto-Select")
TEST_URL = "https://www.gstatic.com/generate_204"
VERIFY_URL = "https://example.com/"
TIMEOUT_MS = 5000
MAX_DELAY = 800

session = requests.Session()
session.headers.update({"Authorization": f"Bearer {SECRET}"})

def node_delay(name):
    path = quote(name, safe="")
    response = session.get(
        f"{API}/proxies/{path}/delay",
        params={"timeout": TIMEOUT_MS, "url": TEST_URL},
        timeout=TIMEOUT_MS / 1000 + 2,
    )
    response.raise_for_status()
    return response.json().get("delay")

def select_node(name):
    response = session.put(
        f"{API}/proxies/{quote(GROUP, safe='')}",
        json={"name": name},
        timeout=5,
    )
    response.raise_for_status()

def verify():
    response = session.get(VERIFY_URL, timeout=10)
    return response.status_code < 500

def main():
    groups = session.get(f"{API}/proxies", timeout=5).json()["proxies"]
    group = groups.get(GROUP)
    if not group:
        raise RuntimeError(f"strategy group not found: {GROUP}")

    candidates = [
        name for name in group.get("all", [])
        if name not in {"DIRECT", "REJECT", GROUP}
    ]

    measured = []
    for name in candidates:
        try:
            delay = node_delay(name)
            if delay is not None and delay <= MAX_DELAY:
                measured.append((delay, name))
        except requests.RequestException as exc:
            print(f"skip {name}: {exc}")

    if not measured:
        raise RuntimeError("no node meets the delay threshold")

    measured.sort()
    for delay, name in measured:
        try:
            select_node(name)
            time.sleep(2)
            if verify():
                print(f"selected {name}, delay={delay}ms")
                return
        except requests.RequestException as exc:
            print(f"verification failed for {name}: {exc}")

    raise RuntimeError("all candidates failed verification")

if __name__ == "__main__":
    main()

这段代码使用 API 返回的延迟值排序,再通过独立的验证 URL 确认切换后仍可访问。实际部署时,VERIFY_URL 应替换为你的业务目标,例如代码仓库、接口服务或公司办公系统,而不是只测试一个公共页面。若业务需要登录、特定请求头或 POST 请求,建议用一个轻量健康检查接口,避免把完整业务请求放入频繁探测任务。

不要只追求最低延迟:延迟差距在几十毫秒以内时,稳定性、丢包率和连续失败次数往往比单次测速结果更重要。可以为节点记录最近五次结果,使用「至少三次成功」的条件,再决定是否切换;这比看到一个瞬时低延迟就立即跳转更适合长连接和 API 请求。

监控、日志与常见故障恢复 详解

自动切换最容易被忽略的是「切换之后」。如果策略组已经显示新节点,但浏览器、终端或开发工具仍然访问失败,可能是连接复用、DNS 缓存、系统代理状态或 TUN 路由没有同步。建议在脚本中记录时间、旧节点、新节点、测试延迟、HTTP 状态码和失败原因。客户端的连接页面也应同时观察:目标域名是否命中预期规则、连接是否走了目标策略组、是否有大量 TLS 错误或 DNS 请求超时。

现象 常见原因 处理方向
返回 401 密钥错误或请求头格式不对 确认使用 Bearer 密钥,并检查控制器配置是否已重载。
返回 404 节点名或策略组名未编码、名称不存在 先读取 /proxies,再对路径参数执行 URL 编码。
测速成功但业务失败 测速地址与目标业务网络条件不同 增加业务域名验证,并检查规则是否把目标域名送入正确策略组。
节点频繁来回切换 阈值过宽、间隔太短或网络抖动 加入冷却时间、连续失败计数和最小切换间隔。
切换后终端仍不通 进程未使用系统代理,或 TUN 未接管流量 检查 HTTP_PROXY、混合端口、TUN 状态和连接日志。

如果希望定时执行,可以使用 Linux 的 systemd timer、macOS 的 launchd 或 Windows 任务计划程序。建议先以五到十分钟为间隔观察日志,不要一开始就设置成每十秒运行一次。对于长连接服务,更应该采用「失败触发」而不是固定频率切换:正常情况下保持当前节点,只有连续请求失败、延迟超过阈值或 TCP 重置达到次数后才启动候选节点测试。

还要给脚本设置明确的失败出口。当所有候选节点都不可用时,不要悄悄切换到未知节点,也不要自动关闭整个 Clash。可以保留当前节点并发送通知,或者切到一个明确标记的备用策略组。若脚本由服务器运行,应把退出码接入监控系统;若脚本运行在个人电脑,则至少将错误写入带日期的日志文件,方便对照客户端连接记录复盘。

常见问题 全面解析

Clash API 返回 404,是否代表客户端不支持 API?(要点)

不一定。首先访问 /version,确认控制器地址和端口正确;然后读取 /proxies,确认你使用的是当前内核运行的控制器,而不是另一个旧进程。部分客户端会在启动后覆盖配置中的控制器端口,也可能把接口放在带路径前缀的地址下。若版本接口可用而具体节点接口 404,通常是节点名称没有进行 URL 编码,或把策略组名称当成了节点名称。

为什么配置里的 url-test 已经自动选节点,还需要 Python 脚本?(说明)

url-test 适合基础健康检查,会按照固定 URL 和周期测量节点,但它不了解你的真实业务,也不会判断登录接口、下载端点或 WebSocket 是否正常。Python 脚本可以加入多个测试目标、连续失败计数、业务验证、切换冷却和通知逻辑。因此两者并不冲突:让策略组处理常规选择,脚本只处理客户端内部判断无法覆盖的异常。

把 external-controller 绑定到局域网,方便手机控制安全吗?(实践)

直接绑定到局域网并不安全,尤其是在公共 Wi-Fi、访客网络或路由器端口转发存在的环境中。即使设置了密钥,也可能遭遇暴力尝试、配置泄露或客户端实现漏洞。更稳妥的方式是继续监听本机回环地址,通过安全 VPN 或受控跳板访问;如果必须监听局域网,应限制防火墙来源地址,并使用长随机密钥和独立管理端口。

自动切换后只有浏览器恢复,终端程序仍然超时,怎么办?(详解)

这通常不是 API 切换失败,而是终端进程没有经过同一条代理链路。检查当前 Shell 是否设置了 HTTP_PROXY 和 HTTPS_PROXY,端口是否对应 Clash 的 HTTP 混合端口;不支持环境变量的程序则需要 TUN 或应用自身的代理设置。随后查看 Clash 连接日志,确认目标域名确实出现,并且命中了预期策略组,而不是被直连规则或错误的 DNS 结果提前处理。

相比手动在多个客户端窗口之间切换节点,或依赖只能按单一网址测速的同类方案,Clash API 自动化的优势在于判断条件透明、日志可追踪、恢复动作可以按业务需求定制;它的局限也很明确:需要理解策略组名称、端口、认证和脚本权限,不能把一次测速结果当成绝对可靠的线路结论。Clash 提供了更集中、易检索的 Clash 配置与排障资料,方便你把 YAML、控制器 API、TUN 和终端代理放在同一套思路中维护;如果你准备按本文实践,不妨先前往下载合适的客户端,再从本机回环控制器和最小化脚本开始逐步验证。