批量检测mihomo节点里gemini和gpt可用性脚本
从 不科学手动测试节点质量的一点小思路 说起,当时我就在想,节点的质量到底怎么检测才是真实有效,从不科学手动测试里,我的想法就是,只有真实的去测试每个对应的项目,才能得出对应渠道数据库的黑ip,不黑的话,那就是好ip,但是手动太累了,就ai写了个py,批量检测下。
因为散热器炸了,换好新的散热,我就想,先把ai方面检测了吧,遂有了下面的python,恢复公益站 心尘公益 API 沉浸式翻译 第四批已领完 已恢复 的使用,以防其他没问题,被ip绊住了。
下午我自己测了下,基本没啥问题,但是有些前提哈,仅适合使用clash客户端或者mihomo(我自己用)这样的,然后有自己newapi聚合站有基础的打野gemini+论坛公益的gpt的佬们用。
我曾想过各种杂七杂八检测gemini和gpt的方式,论坛我看佬们也晒了蛮多,但是我用了下发现不尽人意,明明检测是ok的,实际使用就被墙,明明检测是无效节点,但是gemini和gpt跑的飞快,后来我想了下,还是最原始的操作吧,我直接对话批量检测,慢归慢,扫出来的节点有效能用才ok,对喽,因为有的节点延迟太高,对话时间超时,也会归类为无效节点,我想这类节点,哪怕能用,10秒都连不上,这么不稳定,无效就无效吧。
节点数量少,几个十几个,手动很方便,但是节点多了,真的太痛苦了,还是批量吧。
覆写js,保证auto分组
// AUTO测试组 function main(config) { const proxyCount = config?.proxies?.length ?? 0; try { if (config.proxies && config.proxies.length > 0) { console.log("原始代理节点列表:"); config.proxies.forEach(proxy => { console.log(`- ${proxy.name || '未知名称'}`); }); } //全局屏蔽 const specificNodesToBlock = [ //gemini gpt双不支持 "节点名字", ].sort(naturalSort); // 屏蔽包含特定关键字的节点 const blockedKeywords = ["氪金分割线", "????","中国", "群", "续费", "充值后可解锁更多节点哦", "分割", "充值", "剩余", "失联", "余额", "永久", "分享", "备用", "前往", "恢复", "webshare", "建议", "标准", "持续", "后台", "重置", "距离", "年付", "邀请", "返利", "循环", "官网", "客服", "网站", "网址", "获取", "订阅", "流量", "到期", "机场", "下次", "版本", "官址", "过期", "已用", "联系", "邮箱", "工单", "贩卖", "通知", "倒卖", "防止", "国内", "地址", "频道", "无法", "说明", "使用", "提示", "特别", "访问", "支持", "付费", "失联", "设置", "总计", "澳门", "升级", "解锁"]; if (config.proxies && config.proxies.length > 0) { console.log("开始过滤代理节点..."); config.proxies = config.proxies.filter(proxy => { // 增加一个检查,确保 proxy 是一个有效的对象 if (!proxy) { console.log("过滤掉无效代理: null/undefined"); return false; // 过滤掉 null 或 undefined 的代理 } const originalName = proxy.name || ''; const name = originalName.toLowerCase(); const server = proxy.server || ''; const port = proxy.port || 0; const password = proxy.password || ''; console.log(`正在处理代理: ${originalName}`); // 检查是否为需要精确屏蔽的节点 const isSpecificallyBlocked = specificNodesToBlock.includes(originalName); if (isSpecificallyBlocked) { console.log(` - ${originalName} 被精确屏蔽`); return false; // 如果匹配,则屏蔽该节点 } // 检查是否包含被屏蔽的关键字 const hasBlockedKeyword = blockedKeywords.some(keyword => { if (typeof keyword === 'string') { const result = name.includes(keyword); if (result) console.log(` - ${originalName} 匹配字符串关键字: ${keyword}`); return result; } else if (keyword instanceof RegExp) { const result = keyword.test(originalName); // 使用 originalName 进行正则匹配 if (result) console.log(` - ${originalName} 匹配正则表达式关键字: ${keyword}`); return result; } return false; }); const shouldKeep = !isSpecificallyBlocked && !hasBlockedKeyword; console.log(` - ${originalName} 最终保留状态: ${shouldKeep}`); return shouldKeep; }); console.log("代理节点过滤完成,剩余节点数:", config.proxies.length); // 处理重复节点名称,添加数字后缀 const nameCount = {}; config.proxies = config.proxies.map(proxy => { const originalName = proxy.name || ''; if (nameCount[originalName]) { nameCount[originalName]++; proxy.name = `${originalName}${nameCount[originalName]}`; } else { nameCount[originalName] = 1; } return proxy; }); console.log("节点重命名完成"); } } catch (error) { console.error("代理节点过滤过程中发生错误:", error); } if (proxyCount === 0) { throw new Error("配置文件中未找到任何代理"); } // 覆盖原配置中DNS配置 config["dns"] = dnsConfig; // 覆盖原配置中的代理组 config["proxy-groups"] = proxyGroupConfig; // 覆盖原配置中的规则 config["rule-providers"] = ruleProviders; config["rules"] = rules; //覆盖通用配置 config["mixed-port"] = 7890; config["allow-lan"] = true; config["bind-address"] = "*"; config["ipv6"] = true; config["unified-delay"] = true; // 返回修改后的配置 return config; } // DNS配置 const dnsConfig = { "enable": true, "ipv6": false, "listen": ":53", "prefer-h3": false, "respect-rules": true, "enhanced-mode": "fake-ip", "fake-ip-range": "198.18.0.1/16", "fake-ip-filter": [ // 本地主机/设备 "+.lan", "+.local", // Windows网络出现小地球图标 "+.msftconnecttest.com", "+.msftncsi.com", // QQ快速登录检测失败 "localhost.ptlogin2.qq.com", "localhost.sec.qq.com", // 微信快速登录检测失败 "localhost.work.weixin.qq.com" ], "use-hosts": false, "use-system-hosts": false, "nameserver": ["https://1.1.1.1/dns-query", "https://dns.google/dns-query"], // 国外默认的域名解析服务器 "default-nameserver": ["tls://223.5.5.5", "tls://119.29.29.29"], //国内默认DNS 用于解析 DNS服务器 的域名 "proxy-server-nameserver": ["https://doh.pub/dns-query"], "direct-nameserver": ['https://doh.pub/dns-query','https://dns.alidns.com/dns-query'], //用于 direct 出口域名解析的 DNS 服务器 "nameserver-policy": { "geosite:cn": ["https://doh.pub/dns-query"], "geolocation-!cn": ["https://1.1.1.1/dns-query", "https://dns.google/dns-query"] } } // 代理组通用配置 // 代理组通用配置 - 基础版 (用于 select 等不主动测速的组) const groupBaseOption = { "timeout": 10000, "url": "https://www.gstatic.com/generate_204", "max-failed-times": 3, "hidden": false }; // 健康检查组通用配置 (用于 url-test, fallback 等) const healthCheckGroupOption = { ...groupBaseOption, "interval": 600, // 每600秒检查一次 "lazy": false, // 启动时立即检查 }; // 代理组排除过滤器 // Helper function to create a regex filter from a node list const createExcludeFilter = (nodes) => { if (!nodes || nodes.length === 0) { return ""; } // Escape special regex characters in each node name const escapedNodes = nodes.map(name => name.replace(/[.*+?^${}()|[]]/g, '$&')); return `(?i)${escapedNodes.join('|')}`; }; const naturalSort = (a, b) => a.localeCompare(b, 'zh-CN', { numeric: true }); const excludeForAUTONodes = [ ]; // Gemini 排除的节点列表【不支持】 const excludeForGeminiNodes = [ //批量检查不能用 "节点名字", ].sort(naturalSort); // OpenAI 排除的节点列表【不支持】 const excludeForOpenAINodes = [ //批量检查不能用 "节点名字", ].sort(naturalSort); // 使用辅助函数生成最终的过滤器字符串 const excludeForAUTO = createExcludeFilter(excludeForAUTONodes); const excludeForGemini = createExcludeFilter(excludeForGeminiNodes); const excludeForOpenAI = createExcludeFilter(excludeForOpenAINodes); // 代理组规则 const proxyGroupConfig = [ { ...groupBaseOption, "name": "AUTO", "type": "select", "include-all": true, "tolerance": 50, "exclude-filter": excludeForAUTO, "icon": "https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Speedtest.png" } ]; // 规则集通用配置 const ruleProviderCommon = { "type": "http", "format": "yaml", "interval": 86400, "proxy": "AUTO" }; // 规则集配置 const ruleProviders = { "OpenAI": { ...ruleProviderCommon, "behavior": "classical", "url": "https://cdn.jsdelivr.net/gh/lianwusuoai/clash_rule@master/rule/Clash/OpenAI/OpenAI.yaml", "path": "./ruleset/OpenAI.yaml" }, "Gemini": { ...ruleProviderCommon, "behavior": "classical", "url": "https://cdn.jsdelivr.net/gh/lianwusuoai/clash_rule@master/rule/Clash/Gemini/Gemini.yaml", "path": "./ruleset/Gemini.yaml" }, }; // 规则 const rules = [ "DOMAIN-SUFFIX,openai.com,AUTO", "DOMAIN-SUFFIX,googleapis.com,AUTO", //规则组 "RULE-SET,Gemini,AUTO", "RULE-SET,OpenAI,AUTO", //自定义 大公司包含关键字--代理 "DOMAIN-KEYWORD,googleapis,AUTO", "DOMAIN-KEYWORD,openai.com,AUTO", //自定义 大公司包含关键字--直连 "DOMAIN-KEYWORD,12306,DIRECT", "DOMAIN-KEYWORD,alipay,DIRECT", "DOMAIN-KEYWORD,baidu,DIRECT", "DOMAIN-KEYWORD,douyin,DIRECT", "DOMAIN-KEYWORD,gov.cn,DIRECT", "DOMAIN-KEYWORD,immersivetranslate,DIRECT", "DOMAIN-KEYWORD,jd,DIRECT", "DOMAIN-KEYWORD,quark,DIRECT", "DOMAIN-KEYWORD,qq,DIRECT", "DOMAIN-KEYWORD,sogou,DIRECT", "DOMAIN-KEYWORD,taobao,DIRECT", "DOMAIN-KEYWORD,tencent,DIRECT", "DOMAIN-KEYWORD,Thunder,DIRECT", "DOMAIN-KEYWORD,xiaohongshu,DIRECT", "DOMAIN-KEYWORD,xiaomi,DIRECT", "DOMAIN-KEYWORD,xunlei,DIRECT", "DOMAIN-KEYWORD,youku,DIRECT", "DOMAIN-KEYWORD,zhihu,DIRECT", //自定义网站组-代理 "DOMAIN-SUFFIX,linux.do,AUTO", "DOMAIN-SUFFIX,linux.edu.com,AUTO", "DOMAIN-SUFFIX,linux.edu.com.lv,AUTO", "DOMAIN-SUFFIX,veloera.wei.bi,AUTO", //软件组 "PROCESS-NAME,douyin_tray.exe,DIRECT", // 自定义 IP-CIDR 规则 'IP-CIDR,127.0.0.0/8,DIRECT', 'IP-CIDR,192.168.0.0/16,DIRECT', 'GEOIP,CN,DIRECT', 'GEOIP,private,DIRECT', // 国内直连 "MATCH,AUTO" ];
使用真实的url+key进行检测真实使用环境
CHAT_COMPLETIONS_URL = 填写自己newapi的url
API_KEY =填写自己newapi的key
AI连通性检测.py import requests import time from datetime import datetime import sys import re import os import json # --- 全局配置 --- CONTROLLER_PORT = 7891 PROXY_PORT = 7890 API_KEY = "填写自己newapi的key" CHAT_COMPLETIONS_URL = "http://填写自己的/v1/chat/completions" MAIN_SELECTOR = "AUTO" OVERRIDE_JS_PATH = "data/override/198289f8d6f.js" SWITCH_DELAY = 2 API_TIMEOUT = 10 # API请求的超时时间(秒) # --- JS解析与API交互 --- def parse_js_exclusions(js_content, var_name): """从JS代码字符串中解析出指定的数组变量内容""" try: match = re.search(fr'consts+{var_name}s*=s*[([sS]*?)]', js_content) if not match: print(f" - 警告: 未在JS文件中找到变量 '{var_name}'。") return set() content_inside_brackets = match.group(1) nodes = re.findall(r'["'](.*?)["']', content_inside_brackets) cleaned_nodes = {node.strip() for node in nodes} print(f" - 成功解析变量 '{var_name}',找到 {len(cleaned_nodes)} 个排除节点。") return cleaned_nodes except Exception as e: print(f" - 错误: 解析JS变量 '{var_name}' 时发生错误: {e}") return set() def check_api_connection(port): print(f"正在尝试连接到 API 控制器,地址: http://127.0.0.1:{port}") try: response = requests.get(f"http://127.0.0.1:{port}/", timeout=3) if response.status_code in [200, 302]: print("✓ API 控制器连接成功!") return True except Exception: pass print(f"✗ 无法连接到 API 控制器,请检查端口 {port} 是否正确以及Mihomo/Clash是否正在运行。") return False def get_group_proxies(port, group_name): print(f"正在从策略组 '{group_name}' 获取全量节点列表...") try: url = f"http://127.0.0.1:{port}/proxies/{group_name}" response = requests.get(url, timeout=5) response.raise_for_status() proxies = response.json().get('all', []) cleaned_proxies = {p.strip() for p in proxies} print(f"✓ 成功获取到 {len(cleaned_proxies)} 个唯一节点。") return cleaned_proxies except Exception as e: print(f"✗ 获取策略组 '{group_name}' 节点时发生错误: {e}") return set() def get_nodes_delay(port, nodes_to_check): print("正在一次性获取所有节点的延迟数据...") try: url = f"http://127.0.0.1:{port}/proxies" response = requests.get(url, timeout=10) response.raise_for_status() proxies_data = response.json().get('proxies', {}) delay_info = {} for node in nodes_to_check: details = proxies_data.get(node, {}) history = details.get('history', []) delay = history[-1].get('delay', 0) if history else 0 delay_info[node] = delay print(f"✓ 成功获取到 {len(delay_info)} 个目标节点的延迟数据。") return delay_info except Exception as e: print(f"✗ 获取节点延迟信息时发生错误: {e}") return None def switch_proxy(port, selector_name, proxy_name): try: url = f"http://127.0.0.1:{port}/proxies/{selector_name}" payload = {"name": proxy_name} requests.put(url, json=payload, timeout=3).raise_for_status() return True except Exception as e: print(f" ✗ 切换到节点 '{proxy_name}' 失败: {e}") return False def get_current_proxy(port, selector_name): try: url = f"http://127.0.0.1:{port}/proxies/{selector_name}" response = requests.get(url, timeout=3) response.raise_for_status() return response.json().get('now') except Exception: return None def close_all_connections(port): """强制关闭所有活动的代理连接,确保切换后使用新节点。静默执行。""" try: requests.delete(f"http://127.0.0.1:{port}/connections", timeout=2) # 静默执行,不打印任何信息 except Exception: # 静默执行,忽略错误 pass # --- 连接测试函数 (V22 最终版) --- def test_gemini_connection(proxy_port): proxies = {'http': f'http://127.0.0.1:{proxy_port}', 'https': f'http://127.0.0.1:{proxy_port}'} headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"} data = {"model": "gemini-1.5-flash", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 10} try: # 直接使用 requests.post,确保每次都是独立连接,不受连接池影响 response = requests.post(CHAT_COMPLETIONS_URL, headers=headers, json=data, proxies=proxies, timeout=API_TIMEOUT) if response.status_code == 200: response_json = response.json() if response_json and 'choices' in response_json and len(response_json['choices']) > 0: return '支持' return '不支持' except Exception: return '超时' def test_openai_connection(proxy_port): proxies = {'http': f'http://127.0.0.1:{proxy_port}', 'https': f'http://127.0.0.1:{proxy_port}'} headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"} data = {"model": "gpt-4.1-nano", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 10} try: # 直接使用 requests.post,确保每次都是独立连接,不受连接池影响 response = requests.post(CHAT_COMPLETIONS_URL, headers=headers, json=data, proxies=proxies, timeout=API_TIMEOUT) if response.status_code == 200: response_json = response.json() if response_json and 'choices' in response_json and len(response_json['choices']) > 0: return '支持' return '不支持' except Exception: return '超时' # --- 结果处理与写入 --- def write_final_report(results, timestamp): filename = f"测速/AI问题节点报告_{timestamp}.yaml" report = {"全部不支持": [], "不支持 Gemini": [], "不支持 OpenAI": [], "超时节点": results.get("timeout_nodes", [])} for node, res in results.items(): if node == "timeout_nodes": continue gemini_ok = res.get("Gemini") == "支持" openai_ok = res.get("OpenAI") == "支持" if "Gemini" in res and "OpenAI" in res: if not gemini_ok and not openai_ok: report["全部不支持"].append(node) if "Gemini" in res and not gemini_ok: report["不支持 Gemini"].append(node) if "OpenAI" in res and not openai_ok: report["不支持 OpenAI"].append(node) yaml_content = [f"报告名称: AI服务问题节点报告", f"测试时间: {timestamp}"] for category, nodes in report.items(): yaml_content.append(f" # {category}") yaml_content.append(f"{category}:") if nodes: unique_nodes = sorted(list(set(nodes))) for node in unique_nodes: yaml_content.append(f' "{node}",') with open(filename, 'w', encoding='utf-8') as f: f.write(' '.join(yaml_content)) print(f" ✓ 问题节点报告已保存到: {filename}") # --- 主函数 (V22) --- def main(): print("=" * 50) print(" Mihomo/Clash AI 服务测试脚本 (V22 - 最终版)") print("=" * 50) if not check_api_connection(CONTROLLER_PORT): sys.exit(1) print(" --- 步骤 1: 解析JS获取排除列表 ---") if not os.path.exists(OVERRIDE_JS_PATH): print(f"✗ 错误: 找不到 override.js 文件,路径: {OVERRIDE_JS_PATH}") sys.exit(1) with open(OVERRIDE_JS_PATH, 'r', encoding='utf-8') as f: js_code = f.read() exclude_nodes = parse_js_exclusions(js_code, 'excludeForAUTONodes') print(" --- 步骤 2: 计算测试范围 ---") all_auto_nodes = get_group_proxies(CONTROLLER_PORT, MAIN_SELECTOR) if not all_auto_nodes: print("✗ 未能从'AUTO'组获取任何节点,测试无法继续。") sys.exit(1) total_nodes_to_check = all_auto_nodes - exclude_nodes print(f" - 总计待测节点 (去重后): {len(total_nodes_to_check)} 个节点") print(" --- 步骤 3: 预先筛选超时节点 ---") delays = get_nodes_delay(CONTROLLER_PORT, total_nodes_to_check) if delays is None: sys.exit(1) timeout_nodes = {name for name, delay in delays.items() if delay == 0} available_nodes = total_nodes_to_check - timeout_nodes print(f" - 发现 {len(timeout_nodes)} 个超时节点,将不参与后续测试。") print(" --- 步骤 4: 执行统一调度测试 ---") final_results = {"timeout_nodes": list(timeout_nodes)} sorted_available_nodes = sorted(list(available_nodes)) print(f" --- 步骤 4: 执行独立连接测试 ---") print(f"- 将对以下 {len(sorted_available_nodes)} 个可用节点进行测试...") total_node_count = len(sorted_available_nodes) for i, node in enumerate(sorted_available_nodes): current_progress = f"{i+1}/{total_node_count}" print(f" - {current_progress} 测试节点: {node}") if not switch_proxy(CONTROLLER_PORT, MAIN_SELECTOR, node): continue # 核心修复:强制关闭所有活动连接,确保新节点生效(静默) close_all_connections(CONTROLLER_PORT) time.sleep(SWITCH_DELAY) # 等待连接重置和路由刷新 final_results[node] = {} results_line = [] # 同时测试 Gemini 和 OpenAI gemini_result = test_gemini_connection(PROXY_PORT) final_results[node]["Gemini"] = gemini_result results_line.append(f"Gemini: {gemini_result}") openai_result = test_openai_connection(PROXY_PORT) final_results[node]["OpenAI"] = openai_result results_line.append(f"OpenAI: {openai_result}") print(f" → {' '.join(results_line)}") print(" - 测试完成,不恢复节点选择。") write_final_report(final_results, datetime.now().strftime("%Y-%m-%d_%H-%M")) print(" --- 所有测试完成 ---") if __name__ == "__main__": main()
然后你会得到不支持的节点名,丢到覆写js的gemini和openai分组单独屏蔽就好啦。
对于超时的节点,有空再针对测试下超时的节点就好啦
HISTORY_REPORT_PATH = “测速/AI问题节点报告_2025-08-08_16-11.yaml”
就是找当前文件夹“测速”下面刚测完的文件,文件里有超时的节点信息,针对这些超时的单独测
import requests import time from datetime import datetime import sys import re import os import json import yaml # --- 全局配置 --- CONTROLLER_PORT = 7891 PROXY_PORT = 7890 API_KEY = "同上 写自己的newapi的key" CHAT_COMPLETIONS_URL = "http://同上/v1/chat/completions" MAIN_SELECTOR = "AUTO" OVERRIDE_JS_PATH = "data/override/auto.js" HISTORY_REPORT_PATH = "测速/AI问题节点报告_2025-08-08_16-11.yaml" # 历史报告文件路径 SWITCH_DELAY = 2 API_TIMEOUT = 10 # API请求的超时时间(秒) # --- JS解析与API交互 --- def parse_js_exclusions(js_content, var_name): """从JS代码字符串中解析出指定的数组变量内容""" try: match = re.search(fr'consts+{var_name}s*=s*[([sS]*?)]', js_content) if not match: print(f" - 警告: 未在JS文件中找到变量 '{var_name}'。") return set() content_inside_brackets = match.group(1) nodes = re.findall(r'["'](.*?)["']', content_inside_brackets) cleaned_nodes = {node.strip() for node in nodes} print(f" - 成功解析变量 '{var_name}',找到 {len(cleaned_nodes)} 个排除节点。") return cleaned_nodes except Exception as e: print(f" - 错误: 解析JS变量 '{var_name}' 时发生错误: {e}") return set() def parse_timeout_nodes_from_report(report_path): """从历史报告文件中解析超时节点列表""" print(f"正在从历史报告文件中解析超时节点: {report_path}") try: if not os.path.exists(report_path): print(f" - 警告: 找不到历史报告文件,路径: {report_path}") return set() timeout_nodes = set() current_category = None with open(report_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() # 检查是否是分类标题 if line.startswith("# "): current_category = line[2:] # 去掉"# " # 检查是否是超时节点分类 elif current_category == "超时节点" and line.startswith('"'): # 提取节点名称,去掉引号和可能的逗号 &
用户必须遵守《计算机软件保护条例(2013修订)》第十七条:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。鉴于此条例,用户从本平台下载的全部源码(软件)教程仅限学习研究,未经版权归属者授权不得商用,若因商用引起的版权纠纷,一切责任均由使用者自行承担,本平台所属公司及其雇员不承担任何法律责任。
汇总网页前端与后端开发中遇到的高频疑难问题,附带详尽的技术解决文档和调试笔记,帮助开发者快速定位并解决实际问题。 » 批量检测mihomo节点里gemini和gpt可用性脚本