本文简单介绍 Clash,通过 Clash 的 RESTful API,实现代码中切换指定节点代理。

名词

  • Clash:一个 Go 语言开发的多平台代理客户端,Github
  • ClashX:Clash 的 Mac 图形客户端,Github
  • ClashForAndroid:Clash 的 Android 图形客户端,Github
  • Clash for Windows:Clash 的 Windows/macOS/Linux 图形客户端,Github
  • Clash for Linux:基于 clash 、yacd 进行的配置整合。Github

Clash for Windows 下载:Releases · clash_for_windows_pkg
Windows 上 Clash 汉化版软件下载:Clash.for.Windows-0.20.24-win.7z - 蓝奏云

如无特殊说明,缩写CFW指代 Clash for Windows

Clash WebUI

官方面板:Clash
另一个好用的面板:yacd

Clash WebUI 和 Clash for Windows 一样是 Clash 的 GUI,同样基于 RESTful API,运行在 Web 端,Linux 上也可以使用。连接时需要填上 Host、Port 和秘钥,这些信息在 Clash 的配置文件主目录下 config.yaml 文件中可以看到。

RESTful API

基于 API,可以打造自己的可视化操作部分,也是实现 Clash GUI 的重要组成部分。

RESTful API 文档:RESTful API - Clash

Clash API 使用

python 脚本调用 RESTful API,实现在代码中切换想要使用的节点。

在使用 import yaml 之前,需要确保 pyyaml 模块已经安装,否则会报错 ModuleNotFoundError

1
pip3 install pyyaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# -*- coding: utf-8 -*-
# @Time : 2023/5/30 13:27
# @Author : flyrr
# @File : Auto_remind/proxy/toggle_clash_proxy.py
# @IDE : pycharm
import requests
import json
import random
import os
import yaml
import platform

if platform.system() == 'Windows':
# 获取用户Home目录的路径
home_dir = os.path.expanduser('~')
# 拼接config.yaml文件的完整路径
config_path = os.path.join(home_dir, '.config', 'clash', 'config.yaml')
# 读取config.yaml中的配置项
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
# 获取ip、端口号、secret
clash_host = config['external-controller'].split(':')[0]
clash_port = int(config['external-controller'].split(':')[1])
secret = config['secret']
else:
# Linux 自行修改用户目录,读取配置文件。或自定义 host port secret
# home_dir = os.path.expanduser('~')
# config_path = os.path.join(home_dir, '.config', 'clash', 'config.yaml')

clash_host = '127.0.0.1'
clash_port = '9090'
secret = 'Awehome666.'

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


def selector_global_chains(chains):
# 切换模式配置为全局
global clash_host, clash_port, headers
config_url = "http://%s:%s/configs" % (clash_host, clash_port)

payload = "{\"mode\":\"Global\"}"
requests.patch(config_url, headers=headers, data=payload)
config_json = requests.get(config_url, headers=headers).json()
assert config_json['mode'].lower() == 'global', '切换全局模式失败'
# 关闭系统代理,只保留7890端口

# 选择global节点
url = "http://%s:%s/proxies/GLOBAL" % (clash_host, clash_port)
payload = json.dumps({
"name": chains
})
headers.update({'Content-Type': 'application/json'})
r = requests.request("PUT", url, headers=headers, data=payload)
if r.status_code == 204:
print('切换节点成功')
return True
else:
print('切换节点失败', r.status_code, r.text)
assert 0


def toggle_proxies(area=None):
"""
全局模式7890端口的所有流量都会走代理。规则模式7890端口 国内的流量不走代理,国外的流量走代理。
"""
url = "http://%s:%s/proxies" % (clash_host, clash_port)
r = requests.request("GET", url, headers=headers, data={})
# print('所有代理信息:', r.text)
if area:
lst = json.loads(r.text)['proxies']['GLOBAL']['all']
chains_list = [i for i in lst if area in i]
else:
chains_list = json.loads(r.text)['proxies']['GLOBAL']['all']
# 切片选择需要的节点
# chains_list = chains_list[2:82]
# 过滤出开头是中文的节点,只适合少数人代理使用,其他代理商可以使用切片过滤
chains_list = [i for i in chains_list if i[0] > '\u4e00' and i[0] < '\u9fa5']
# print(chains_list)

assert chains_list, "没有获取到节点"
# 测速,获取正常的节点和延迟
chains_info_list = []
for chains_name in chains_list:
dic = {}
dic['name'] = chains_name
test_url = "http://%s:%s/proxies/%s/delay?timeout=5000&url=http://www.gstatic.com/generate_204" % (
clash_host, clash_port, chains_name)
delay_res = requests.get(test_url, headers=headers)
# 200 正常响应:{"delay":448} 504 响应超时:{"message":"Timeout"}
assert "Unauthorized" not in delay_res, f'节点 {chains_name} 延迟测试失败:{delay_res.text}'
if delay_res.status_code != 200:
# print('节点 %s 延迟测试失败' % chains_name, delay_res.text)
continue
delay_dic = delay_res.json()
dic.update(delay_dic)
chains_info_list.append(dic)

# 根据延迟排序
chains_list = sorted(chains_info_list, key=lambda x: x['delay'])
# # 随机选择一个节点
# chains = random.choice(chains_list)
# 选择延迟最小的节点
chains = chains_list[0]['name']
delay = chains_list[0]['delay']
print("本次选择的延迟最小的节点是: %s 延迟 %s" % (chains, delay))
selector_global_chains(chains)
# return chains_list


def test_proxy():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'}
proxies = {
"http": "http://127.0.0.1:7890/",
"https": "http://127.0.0.1:7890/"
}
res = requests.get('https://api.ip.sb/geoip', proxies=proxies, headers=headers).json()
res_dic = {'country': res['country'], 'city': res['city'], 'ip': res['ip']}
print('国外API测试', res_dic)

res = requests.get('https://qifu-api.baidubce.com/ip/local/geo/v1/district', proxies=proxies).json()
ip = res['ip']
data = res['data']
res_dic = {'country': data['country'], 'city': data['city'], 'ip': ip}
print('国内API测试', res_dic)


if __name__ == "__main__":
# 按照少数人代理写的,area为空时随机选择一个节点,但是所有节点测速时间会变长
# from toggle_clash_proxy import toggle_proxies
# toggle_proxies()
toggle_proxies(area='澳大利亚')
# toggle_proxies(area='香港')

test_proxy()