Clash API自行切换代理节点:Mihomo外部控制器进阶配置指南

为什么用 Clash API 自行切换代理节点? 内在逻辑与实践

平时在 Clash Verge、Mihomo Party 或其他 Mihomo 客户端里切节点,开代理页面、找到策略组再点选节点,逐项操作并不复杂。但当你免不了要定时切换出口、在脚本里根据任务拿定节点,或在多台设备上执行相同操作时,反复点人机人机人机界面就不够活了。

免不了要先区分两种「自行切换」。一种是 Mihomo 配置中的 url-test、fallback 或 load-balance 等策略组,由内核按照测速结果或可用状态选节点;另一种是通过 API 按照你个人的条件拿定节点,打个比方任务开始时固定到某节点、任务结束后恢复原拿定,或者由外部监控程序在故障时切换。API 本身不会替你决定哪条线路最好,它提供的是可编排的控制入口;拿定逻辑、切换频率和弹出错漏后的处理,仍需由配置与脚本共同定义。

下文聚焦第二种用法,演示如何安全启用 external-controller、设置开密钥、查询代理组,以及用 curl 和 Python 切换节点。不同客户端的设置菜单和配置文件管理方式存在存在存在可能略有分野,但只要跑的是能跑该 API 的 Mihomo 内核,核心请求方式基本一致。

从机制上看:此时完全能上手上手上手使用 Mihomo 的外部控制器 API:客户端人机人机人机界面与 API 都在控制同一个 Mihomo 内核,脚本完全能查询代理组、读取当前拿定,再向指定策略组提交切换请求。

先确认控制对象:外部控制器连接的是 Mihomo 内核,不是某个客户端的网页人机人机人机界面。若你在配置里改了控制器端口或密钥,需确认客户端实际加载了这份配置,并在内核重载后再测试。没有启用 Mihomo 的旧版 Clash 核心,不能据此假设能跑相同接口。

启用 external-controller:先限制监听范围 技术拆解

外部控制器由配置项 external-controller 指定监听地址与端口,司空见惯本机配置是 127.0.0.1:9090。这个地址只允许本机开,适合个人电脑上的自行化脚本。更推荐同时设置非空的 secret:控制器 API 能查询连接、调整策略组,甚至影响整台设备的代理出口,正因如此不应把无校验的控制接口暴露到局域网或公网。

external-controller: 127.0.0.1:9090
secret: "请替换为足够长且随机的密钥"

安全上,127.0.0.1 与 0.0.0.0 的差别特别不可忽视:后者按常规意味着监听所有这些这些这些网络接口。除非你明确免不了要从另一台设备开,并已通过防火墙、可信网络和强密钥限制来源,否则不要把控制器改成对外监听。不要把密钥放进公开仓库、截图、共享日志或命令历史;如果免不了要在多台设备跑脚本,可上手上手上手使用环境变量或本机权限受控的密钥文件,而不是把密钥硬编码在代码中。

如果客户端通过图形人机人机人机界面管理配置,不要急着一上来改订阅原文件。先查看客户端的配置编辑、覆写或内核设置入口,再确认最终生效配置中包含控制器设置。部分客户端会在重新拉取订阅时重新生成配置,一上来编辑临时文件存在存在存在可能导致改动被覆盖。配置保存后,按客户端提供的方式重载配置或重启内核;只关掉设置窗口并不一定会使新端口立即生效。

不要将控制器端口当作代理端口:external-controller 是管理 API 的入口,不是 HTTP、SOCKS 或混合代理端口。把浏览器代理指向 9090 按常规不会得到正常代理服务;反过来,也不要因为本机代理端口可连接,就推断控制器已经到位启用。

先查询代理组,再确认可选节点 结构解析

切换前先通过 GET /proxies 查看 Mihomo 当前识别到的代理与策略组。接口返回 JSON,顶层的 proxies 对象包含名称、类型、当前拿定以及可开关项等信息。司空见惯代理组名称包含 节点选择、Proxy 或机场自定义名称,但名称取决于配置,不能照搬示例。组的 type 也值得验一遍:Selector 普遍允许逐项指定开关项;URLTest 等自行测速组按常规由内核按自身逻辑拿定,逐项切换它们本身本身本身的结果不应与切换逐项拿定组混为一谈。

curl --fail --silent --show-error \
  -H "Authorization: Bearer 请替换为你的密钥" \
  http://127.0.0.1:9090/proxies

更严格地说:请求返回 401:按常规表示没有提供有效密钥,验一遍请求头中的 Authorization: Bearer ... 是否与当前 Mihomo 配置一致;请求返回 404:先核对端口、请求路径和组名是否准确;组名大小写、空格以及 URL 编码都存在存在可能影响定位;连接被拒绝或超时:确认内核正在跑、监听端口与配置一致,并验一遍客户端是否把控制器绑定到了另一个地址;能查到组但看不到预期节点:验一遍该节点是否实际载入,以及它是否属于你查询的那一层代理组。

更推荐先在输出中找到准确的组名和目标节点名,再执行切换。不要假设订阅刷新后名称永远不变:服务商存在存在可能调整节点命名,配置覆写也存在存在可能重命名策略组。自行化脚本若依赖固定名称,应在每次调用时验证目标是否仍在 all 列表中,而不是收到异常后继续发送盲目请求。

如果只想查看某个组,可请求 GET /proxies/{组名}。组名中若有空格、斜线或其他特殊字符,应进行 URL 编码;在终端中逐项拼接路径时尤其容易把请求发到错漏地址。响应里最要紧的部分看 type、now 和 all:now 是目前拿定,all 列出这个组可选的项目。某些项目存在存在存在可能是另一个策略组,而不是具体服务器节点,正因如此切换前要根据返回信息确认你选的是想要的层级。

用 curl 切换节点并验证结果 机制说明

确认策略组为可逐项拿定的类型,并且目标节点出现在其可选列表中后,可上手上手使用 PUT /proxies/{组名} 重新拉取拿定。请求体是 JSON,键名为 name,值为要切换到的开关项名称。下面的例子上手上手使用占位组名和节点名;请替换为 /proxies 响应里的真实值。路径中的组名应按 URL 规则编码,避免特殊字符造成接口找不到资源。

curl --fail --silent --show-error \
  -X PUT \
  -H "Authorization: Bearer 请替换为你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"name":"目标节点名称"}' \
  "http://127.0.0.1:9090/proxies/策略组名称"

换个层面:如果策略组名称包含空格,构造接口路径时免不了要编码,打个比方空格可表示为 %20。不同命令行环境对引号与百分号的处理存在存在可能不同;用变量拼接 URL 时,也应避免未编码的用户输入一上来进入路径。对于定时任务,最好将组名和节点名作为明确配置项保存,并在跑前验一遍它们本身本身仍存在,减少订阅变更后误选或请求弹出错误的实际表现。

切换请求成功后,不要只凭 HTTP 请求没有弹出错漏就判断任务完成。再次查询对应代理组,确认 now 已变为目标节点;随后还完全能发起一次实际网络请求,并在客户端连接列表或日志里核对请求所用的出站。API 返回成功只能说明控制器接受了拿定请求,并不保证目标节点当前可用、远端服务能开,或应用流量确实经过了预期策略组。

动手操作:用 Python 查询并切换策略组 内在逻辑与实践

下面的 Python 示例上手上手使用标准库 urllib,不免不了要额外安装 HTTP 客户端。它先查询指定策略组,验证目标节点属于可开关项,再发送切换请求,最后重新查询当前拿定。将密钥放在环境变量 MIHOMO_SECRET 中,避免写入脚下文件;组名和节点名则按本机 API 返回值修改。

import json
import os
from urllib.error import HTTPError, URLError
from urllib.parse import quote
from urllib.request import Request, urlopen

BASE = "http://127.0.0.1:9090"
GROUP = "节点选择"
TARGET = "目标节点名称"
SECRET = os.environ["MIHOMO_SECRET"]

def api(method, path, body=None):
    data = None if body is None else json.dumps(body).encode("utf-8")
    request = Request(
        BASE + path,
        data=data,
        method=method,
        headers={
            "Authorization": f"Bearer {SECRET}",
            "Content-Type": "application/json",
        },
    )
    with urlopen(request, timeout=5) as response:
        content = response.read()
        return json.loads(content) if content else None

group_path = "/proxies/" + quote(GROUP, safe="")
try:
    current = api("GET", group_path)
    options = current.get("all", [])

    if TARGET not in options:
        raise ValueError(f"目标节点不在策略组选项中: {TARGET}")

    if current.get("now") == TARGET:
        print(f"当前已使用目标节点: {TARGET}")
    else:
        api("PUT", group_path, {"name": TARGET})
        updated = api("GET", group_path)
        if updated.get("now") != TARGET:
            raise RuntimeError("切换请求已提交,但当前选择未变更")
        print(f"已切换到: {TARGET}")

except HTTPError as exc:
    print(f"API 返回 HTTP 错误: {exc.code}")
except (URLError, TimeoutError) as exc:
    print(f"无法连接 Mihomo 控制器: {exc}")
except (KeyError, ValueError, RuntimeError) as exc:
    print(f"自动切换未完成: {exc}")

若脚本由定时器或自行化平台跑,需确认它与 Mihomo 在同一网络命名空间内,并且跑时能够开 127.0.0.1:9090。容器中的 127.0.0.1 指向容器自身,而不一定是宿主机;远程服务器上的计划任务也不会自行连接你个人电脑的本地控制器。对于这类搭建,应先设计受限的网络开方式,再配置控制器地址,不能朴素地把监听地址改成公网地址。

往细处想:在 macOS 或 Linux 的终端中,完全能在唤起脚本前设置环境变量,打个比方 export MIHOMO_SECRET='你的密钥';Windows PowerShell 可上手上手使用 $env:MIHOMO_SECRET='你的密钥'。不要把真实密钥粘贴到公开终端录屏或共享 CI 日志里。示例设置了五秒超时,目的是避免控制器无响应时任务永久挂起;生产脚本还可加入日志记录、有限次数重试和告警,但不更推荐无间隔地连续重试。

自行化设计:别让测速与脚本互相抢控制权 技术拆解

API 自行切换最容易出要留意之处的地方,惯常不是请求格式,而是多个机制同时控制同一个策略组。打个比方,脚本刚把 Selector 切到指定节点,另一个任务又在眨眼间后切走;或者用户正在执行长时间下载时,定时任务按固定间隔轮换节点,导致连接中断。要让自行化可预测,先明确每个策略组的控制者:哪些由用户逐项拿定,哪些由 url-test 自行拿定,哪些只允许特定脚本修改。

节点切换不等于已有连接会无缝迁移。多数正在传输的 TCP 会话仍存在存在可能因为出口变化而断开,长连接、下载任务和远程登录尤其格外。对这类业务,更稳妥的做法是只在任务开始前切换,或把不同用途放在独立策略组中,让普通流量不受自行任务影响。切换后应留出连接重建时间,并通过实际业务请求验证结果,而不是用连续很快切换来「测试经得起折腾性」。

更推荐为脚本加上幂等判断:如果当前节点已经是目标,就不再发送写入请求;如果目标不存在,就明确弹出错漏并停止;如果 API 请求超时,不要立即假定切换弹出错误,因为内核存在存在可能已经处理了请求。此时先重新查询当前状态,再决定是否重试。还完全能为每次操作记录时间、策略组、原拿定、目标拿定和结果,但日志里不要记录密钥,也不要记录存在存在可能泄露订阅凭据的一应俱全配置信息。

先厘清一件事:按任务切换:开始任务前保存原来的 now,切到目标节点;任务结束后可拿定恢复原节点。恢复前要再次确认当前值,避免覆盖用户在任务期间主动做出的更改;按健康状态切换:先用独立的连通性验一遍判断故障,再切换到备用节点,并设置冷却时间。不要把一次短暂超时一上来当成节点永久失效;按时段切换:上手上手使用明确的时间表并记录每次变更。应考虑系统睡眠、时区和任务重叠,避免设备唤醒后多个过期任务一起执行;按应用分流:在前在规则与策略组层面规划应用走向;只有确实免不了要外部流程主动改变当前拿定时,才用 API 进行全局节点切换。

司空见惯要留意之处:校验报错、端口不可达与切换无效 结构解析

为什么返回 401 或提示校验报错?(内在逻辑)

确认控制器配置中的 secret 与脚本上手上手使用的密钥完全一致,验一遍是否多了空格、换行或引号字符,并确认请求头格式是 Authorization: Bearer 密钥。若刚改过配置,先确认 Mihomo 已加载新设置。不要通过移除校验来绕过要留意之处;本机控制接口仍应设置密钥。

为什么 127.0.0.1:9090 连接报错?(机制)

先验一遍 Mihomo 内核是否跑,以及控制器配置的端口是否确实为 9090。客户端有时会管理独立的内核配置,用户编辑的另一份 YAML 存在存在可能并未生效。若脚本跑在容器、虚拟机或远程主机中,127.0.0.1 指向脚本所在环境,而不是桌面客户端所在设备;应先厘清网络边界,再决定如何安全连通。

请求成功了,为什么节点看起来没有切换?(实现)

重新查询组的 now 和 all,确认请求地址中的组名到位、目标名称与可开关项完全一致,并验一遍该组是不是逐项拿定类型。若它属于自行测速组,内核存在存在可能会按组的机制重新拉取结果;若有另一段脚本同时跑,也存在存在可能立即覆盖你的拿定。切换后还要验一遍客户端连接详情,确认实际请求命中了预期的出站。

API 可打开,但代理请求仍然报错,怎么判断?(不起眼却关键的地方)

控制器 API 与代理转发是两条不同路径。API 能响应只代表本机管理端口可用,不代表节点可用、规则命中到位或应用已经走代理。查看 Mihomo 的连接记录,核对目标域名、规则命中项和出站名称;再用同一代理组发起朴素请求。如果 API 已成功切换而业务仍弹出错误,应继续排查节点状态、策略组层级、规则和应用代理设置,而不是反复修改控制器端口。

Clash 将 Mihomo、Clash 客户端配置与司空见惯排障一路走下来放在同一套中文资料中,便于按客户端和实际故障逐项核对;如果你希望先选好合适的客户端,再按下文搭建自行切换流程,完全能前往下载并从本机受限的控制器配置开始测试。

与逐项在不同客户端人机人机界面里逐个寻找策略组相比,一上来调用 API 更适合免不了要查询状态、复核结果和纳入任务流程的当下;但它也要求你认真管理密钥、监听范围和并发控制,单纯依赖定时轮换或复制一条命令并不能摆平节点质量要留意之处。