当你同时维护多个代理节点时,真正麻烦的往往不是「有没有节点」,而是当前节点是否仍然可用。某个节点可能在上午延迟很低,到了晚高峰却出现丢包;也可能 TCP 连接尚在,但 HTTPS 握手、视频流或 API 请求已经频繁超时。如果每次都手动打开 Clash 客户端、逐个点击测速,再切换代理组,排障成本会随着节点数量快速增加。

Clash API提供了一个更适合自动化管理的入口:客户端启动后,通过 external-controller 暴露本机 HTTP API,脚本可以读取当前配置、查询代理组、执行指定 URL 的延迟测试,并把某个代理切换为代理组的当前选择。结合定时任务和运行日志,就能构建一套相对可靠的健康检查与故障转移流程。本文以 Clash Verge Rev、Mihomo 等常见客户端的通用能力为基础,重点说明 API 的工作方式、配置要点、脚本调用顺序,以及实际使用中容易忽略的安全和稳定性问题。

需要先说明:自动切换只能根据可观测指标做判断,不能保证某个节点在所有网站、所有时间段都表现良好。延迟测试成功,代表测试 URL 在当前时刻可以建立连接;它不等于视频解锁、下载速度或所有 API 服务都正常。因此,建议把自动切换设计成减少人工操作的辅助机制,同时保留日志、手动选择和异常告警。

Clash API 的工作原理:控制器、代理组与节点

在 Clash 或 Mihomo 配置中,external-controller用于指定外部控制器监听的地址和端口。例如,127.0.0.1:9090表示只允许本机访问 9090 端口;如果写成 0.0.0.0:9090,则可能让局域网内其他设备也能访问。后者只有在你明确需要远程管理、并且已经设置访问密钥和防火墙规则时才应使用。

API 的核心资源大致可以分为三类。第一类是运行状态,例如当前版本、模式和内核信息;第二类是代理资源,用于读取所有节点、代理组及当前选中的代理;第三类是延迟测试,通过指定代理访问一个测试 URL,返回耗时结果。不同内核版本的字段名称可能略有区别,因此脚本不应假定每个返回对象都绝对完整,最好对空值、错误码和超时进行处理。

常见的 API 路径包括:

  • GET /version:读取控制器版本信息,用于确认 API 是否可访问。
  • GET /proxies:读取代理节点和代理组。返回对象中的代理组通常带有 allnow 等字段。
  • GET /proxies/{name}/delay:对指定代理执行延迟测试,通常需要传入 urltimeout 参数。
  • PUT /proxies/{name}:修改代理组当前选中的节点,请求体通常为 {"name":"节点名称"}

这里的 {name}不是固定字符串,而是代理组名称经过 URL 编码后的结果。例如代理组名包含空格、斜杠或中文时,不能直接把原文字拼进 URL。使用 Python、Node.js 等语言的 HTTP 库时,应通过参数编码函数处理;如果使用 Shell,则至少要注意引号和特殊字符,否则脚本可能在某些节点名称下突然失效。

先确认 API 是否开启:在 Clash Verge Rev 或配置文件中检查控制器地址、端口和密钥。若浏览器访问 http://127.0.0.1:9090/version 返回拒绝连接,先不要修改脚本;这通常说明内核未启动、端口不同,或当前客户端使用了另一份配置。

配置 external-controller 与密钥:先把入口保护好

自动化之前,建议先确定一个仅供本机使用的控制器地址。桌面端通常可以在设置页面找到外部控制器、API 端口或控制器密钥选项;如果你直接编辑 YAML,也可以在顶层配置中写入类似以下内容:

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

示例中的密钥只是占位符,实际使用时应换成长度足够、难以猜测的随机字符串。API 请求需要在 HTTP 头中携带 Authorization: Bearer your-secret。如果控制器只监听回环地址,即使同一 Wi-Fi 下的其他设备知道端口,也通常无法直接连接;如果为了远程管理而监听局域网地址,则必须同时限制来源 IP、配置防火墙,并避免把控制器端口映射到公网。

安全问题经常被低估。控制器不仅能读取节点信息,还可能切换策略组、修改运行状态,某些内核还允许重新载入配置。不要把密钥硬编码进公开仓库、截图、Shell 历史记录或团队聊天。脚本可以从环境变量读取:

export CLASH_API=http://127.0.0.1:9090
export CLASH_SECRET='replace-with-your-secret'

如果脚本运行在 systemd、Docker、计划任务或 CI 环境中,还要确认这些环境真的继承了变量。很多「手动执行正常、定时执行失败」的问题,并不是 API 故障,而是任务进程没有读取用户 Shell 的环境配置。对于长期运行的脚本,建议使用权限受限的环境文件,并让文件只允许当前用户读取。

动手操作:读取代理组并执行一次健康检查

第一次不要直接启用自动切换,先用手动请求确认控制器、密钥和代理组名称都正确。下面的命令读取版本信息:

curl -sS \
  -H "Authorization: Bearer ${CLASH_SECRET}" \
  "${CLASH_API}/version"

如果返回 JSON,说明基本连接已经建立。接着读取全部代理资源:

curl -sS \
  -H "Authorization: Bearer ${CLASH_SECRET}" \
  "${CLASH_API}/proxies"

在返回结果中找到你实际使用的策略组。例如配置里可能叫「🚀 节点选择」「Proxy」或「自动选择」,不要直接照抄示例名称。记下策略组的准确名称,以及其中确实能使用的节点名称。部分代理组是 url-testfallbackload-balance 类型,内核本身已经具备自动选择逻辑;脚本不应无条件覆盖它们的选择结果,否则可能与内核的定时检测互相干扰。

对单个节点做测试时,需要提供测试地址和超时时间。例如:

curl -sS -G \
  -H "Authorization: Bearer ${CLASH_SECRET}" \
  --data-urlencode "timeout=5000" \
  --data-urlencode "url=https://www.gstatic.com/generate_204" \
  "${CLASH_API}/proxies/节点名称/delay"

测试 URL 应尽量稳定、响应体小、不会触发复杂登录流程。generate_204类地址适合测连通性,但不一定代表你的实际业务质量。若主要用途是访问某个 API、代码仓库或视频服务,可以选择一个合法且稳定的目标作为补充测试。不过不要把检查频率设得过高,也不要同时对几十个节点每分钟发起请求,这会增加本地 CPU、代理服务和目标站点的负担。

确认节点测试结果后,再向代理组发送切换请求:

curl -sS -X PUT \
  -H "Authorization: Bearer ${CLASH_SECRET}" \
  -H "Content-Type: application/json" \
  --data '{"name":"节点名称"}' \
  "${CLASH_API}/proxies/代理组名称"

如果名称包含中文或特殊符号,建议改用 Python 处理 URL 编码,而不是在 Shell 中手工拼接。切换后再次请求 /proxies,检查代理组的 now 字段是否已经变成目标节点。随后打开客户端连接面板,确认新请求实际命中了该策略组;API 返回成功只代表控制器接受了修改,不代表业务连接已经验证完成。

脚本化故障转移:阈值、候选顺序与冷却时间

一个可用的故障转移脚本至少要完成四件事:读取候选节点、逐个执行延迟测试、根据阈值筛选可用节点、在必要时切换策略组。不要简单地把「延迟最低」等同于「最优」。延迟结果会受测试 URL、线路拥塞和瞬时调度影响;如果两个节点分别返回 180 毫秒和 220 毫秒,但前者连续失败、后者稳定可用,稳定性显然更值得优先考虑。

建议为脚本设置以下参数:

  • 超时时间:例如 3–8 秒。过短容易把短暂抖动误判为故障,过长则会让切换等待太久。
  • 可接受延迟:例如 800 毫秒。阈值应根据实际用途调整,视频会议和普通网页的标准并不相同。
  • 失败次数:连续两次或三次失败后才切换,避免一次偶发丢包导致频繁跳节点。
  • 冷却时间:切换后至少等待数分钟再做下一次自动切换,防止两个节点之间来回震荡。
  • 最小保持时间:当前节点刚切换成功时,即使另一个节点稍快,也不要马上再次替换。

下面是一个简化的 Python 逻辑示例。它展示调用顺序,实际使用时应补充日志轮转、异常分类和节点名称过滤:

import os
import time
import requests

API = os.environ["CLASH_API"]
SECRET = os.environ["CLASH_SECRET"]
GROUP = "Proxy"
TEST_URL = "https://www.gstatic.com/generate_204"

headers = {"Authorization": f"Bearer {SECRET}"}

def api_get(path, **kwargs):
    response = requests.get(API + path, headers=headers, timeout=8, **kwargs)
    response.raise_for_status()
    return response.json()

def test_proxy(name):
    response = requests.get(
        API + "/proxies/" + requests.utils.quote(name, safe=""),
        params={"url": TEST_URL, "timeout": 5000},
        headers=headers,
        timeout=8,
    )
    if response.status_code != 200:
        return None
    return response.json().get("delay")

proxies = api_get("/proxies")
members = proxies["proxies"][GROUP]["all"]

results = []
for name in members:
    delay = test_proxy(name)
    if delay is not None and delay < 800:
        results.append((delay, name))
    time.sleep(0.2)

if results:
    results.sort()
    selected = results[0][1]
    requests.put(
        API + "/proxies/" + requests.utils.quote(GROUP, safe=""),
        headers={**headers, "Content-Type": "application/json"},
        json={"name": selected},
        timeout=8,
    )

这段逻辑不应直接放进高频循环。更稳妥的做法是由 cron、Windows 任务计划程序或其他调度器每隔几分钟执行一次,并在脚本外部保存上次切换时间。脚本还应排除「剩余流量不足」「维护中」或明显属于特殊用途的节点,避免把不可比较的节点与普通线路放在同一排序列表里。

减少误切换:先测试当前节点,再测试候选节点。只有当前节点连续失败,或候选节点的延迟明显低于设定阈值时才切换;如果当前请求正在下载文件、进行视频会议或运行长连接任务,最好把切换动作延后到业务空闲时段。

日志分析与常见故障:不要只看一次延迟

自动化脚本最有价值的产物不是「换到了哪个节点」,而是能够解释为什么换。建议每次检查至少记录时间、策略组、当前节点、测试 URL、延迟、HTTP 状态、错误类型和最终动作。日志中不要写入订阅链接、API 密钥或完整的敏感请求头。可以把节点名称做部分脱敏,例如只保留地区和编号,降低日志泄露风险。

遇到请求失败时,先区分以下几类问题。返回 401 或 403,通常与控制器密钥、权限或请求头有关;返回 404,常见于 API 路径、内核版本或资源名称不正确;连接被拒绝,通常是控制器没有监听、端口写错或客户端尚未启动;请求超时,则可能是节点本身、测试地址、DNS、系统防火墙或本地代理链路的问题。不要看到超时就立刻切换,因为控制器请求和代理节点测试请求是两条不同的链路。

如果策略组是自动类型,API 切换后又很快恢复成原节点,先检查配置中的 url-testfallback 或定时测试设置。此时应该选择一种控制方式:要么让内核负责自动选择,脚本只负责告警;要么关闭该组的自动决策,由脚本统一控制。两套机制同时运行,最容易出现日志显示「脚本已切换」,界面却在数秒后被内核改回去的情况。

DNS 也值得单独检查。延迟测试使用域名时,解析可能受到当前 DNS 模式影响;某些节点 TCP 可达,但目标域名被解析到不可用地址,结果仍会表现为失败。可以分别使用稳定域名和 IP 进行对照,但不要因为 IP 测试成功就忽略 TLS 的主机名校验。对于 HTTPS 服务,证书验证、SNI 和真实域名都很重要,单纯 ping 通 IP 没有足够参考价值。

常见问题

是否可以把 external-controller 暴露到公网?

不建议。控制器具备读取和修改代理状态的能力,暴露公网会增加扫描、撞库和密钥泄露风险。如果确实需要远程操作,优先通过 WireGuard、Tailscale 等经过认证的私有网络访问,并使用强密钥、防火墙白名单和 HTTPS 反向代理。不要仅仅依靠一个简单端口号隐藏服务。

延迟最低的节点一定是最稳定的吗?

不一定。延迟测试只反映某个时间点、某个 URL 的结果,无法覆盖丢包、带宽、长连接和高峰拥塞。实际脚本应结合连续成功率、最近失败次数和冷却时间判断,必要时对多个测试地址取综合结果,而不是只按单次延迟排序。

为什么 API 切换成功,客户端却没有变化?

常见原因是代理组名称写错、切换到了非当前配置中的组,或者该组由 url-testfallback 等自动策略接管。切换请求完成后,应重新读取 /proxies,检查组的当前节点,并在连接面板观察新请求的实际策略。

多久执行一次健康检查比较合适?

普通桌面使用可以从 5 到 15 分钟一次开始;长时间运行的服务则应根据业务容忍度调整。检查过于频繁会产生大量请求和日志,也可能造成节点反复切换。更好的做法是失败时加快复查,恢复后进入较长冷却周期。

相比只依赖手动测速的客户端流程,Clash API 的优势在于能把延迟检测、策略组切换、定时巡检和日志记录组合成可重复的工作流;而一些功能封闭的同类工具往往只能点击按钮,无法接入计划任务,也很难解释节点为何被替换。只要控制器限制在本机、阈值设置不过于激进,并保留人工确认入口,这套方式就能把「网络突然变慢后四处排查」变成有记录、可回滚的自动化运维流程。如果你希望在不同平台上使用统一的 Clash 客户端和 API 能力,可以前往下载页面选择适合自己的版本。

立即免费下载 Clash,开启流畅上网新体验 →