我自己用的。
import subprocess
import sys
import os
# ANSI 颜色转义码
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
CYAN = "\033[36m"
BOLD = "\033[1m"
RESET = "\033[0m"
def get_display_width(s):
"""计算字符串实际显示宽度(中文字符算2个宽度)"""
width = 0
for char in s:
if '\u4e00' <= char <= '\u9fff' or '\u3000' <= char <= '\u303f' or '\uff00' <= char <= '\uffef':
width += 2
else:
width += 1
return width
def pad_string(s, width):
"""中英文混排字符串填充到指定宽度(中文字符算2个宽度)"""
display_width = get_display_width(s)
padding = width - display_width
if padding > 0:
return s + ' ' * padding
else:
return s
def test_proxy(tasks):
# curl 格式化参数
fmt = "%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}"
print("") # 开头空行
# 收集所有结果用于最后汇总
summary = []
for port, url, desc in tasks:
# 核心逻辑:没写协议则强制补全 https,写了则完全保留原样(支持自定义协议和端口)
target_url = url if "://" in url else f"https://{url}"
if desc:
print(f"{GREEN}描述 {desc}{RESET}")
print(f"{CYAN}代理 {port}{RESET}")
print(f"{CYAN}目标 {target_url}{RESET}")
try:
cmd = ["curl.exe", "-x", f"socks5h://127.0.0.1:{port}", "-o", "NUL", "-s", "-w", fmt, "--connect-timeout", "5", target_url]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=12)
if result.returncode != 0:
print(f"{RED}❌ 失败: 代理 {port} 连不通或目标超时 (Code: {result.returncode}){RESET}\n")
summary.append([
desc if desc else "-",
port,
target_url,
"失败",
999999,
"-",
999999,
"-",
999999,
"-",
999999,
"-",
999999
])
continue
# 转换毫秒并保留两位小数
t = [round(float(x) * 1000, 2) for x in result.stdout.strip().split(" ")]
pure_response = round(t[3] - t[2], 2)
dns_time = t[0]
tcp_time = t[1]
tls_time = t[2]
total_time = t[4]
print(f"DNS 解析 {dns_time} ms")
print(f"TCP 建立 {tcp_time} ms")
print(f"TLS 握手 {tls_time} ms")
# 红色标记纯响应时间
print(f"纯响应 {RED}{pure_response} ms{RESET} (减去握手耗时)")
print(f"首字节 {t[3]} ms (TTFB)")
print(f"总计耗时 {total_time} ms")
print("")
summary.append([
desc if desc else "-",
port,
target_url,
f"{total_time} ms",
total_time,
f"{pure_response} ms",
pure_response,
f"{dns_time} ms",
dns_time,
f"{tcp_time} ms",
tcp_time,
f"{tls_time} ms",
tls_time
])
except Exception as e:
print(f"{RED}❌ 异常: {e}{RESET}\n")
summary.append([
desc if desc else "-",
port,
target_url,
"异常",
999999,
"-",
999999,
"-",
999999,
"-",
999999,
"-",
999999
])
# 输出汇总排名(只在共用模式下,即所有任务共用一个 URL)
urls = set(task[1] for task in tasks)
if len(urls) == 1 and len(summary) > 1:
# 按总计耗时排序(数值从小到大,失败/异常的999999会排到最后)
summary.sort(key=lambda x: x[4])
# 定义表头
headers = ["描述", "端口", "网址", "总计耗时", "纯响应(类ping)", "DNS", "TCP", "TLS"]
# 动态计算每列最大显示宽度
col_widths = []
for i, header in enumerate(headers):
max_width = get_display_width(header)
for item in summary:
if i == 0: # 描述
width = get_display_width(item[0])
elif i == 1: # 端口
width = get_display_width(item[1])
elif i == 2: # 网址
width = get_display_width(item[2])
elif i == 3: # 总计耗时
width = get_display_width(item[3])
elif i == 4: # 纯响应(类ping)
width = get_display_width(item[5])
elif i == 5: # DNS
width = get_display_width(item[7])
elif i == 6: # TCP
width = get_display_width(item[9])
elif i == 7: # TLS
width = get_display_width(item[11])
max_width = max(max_width, width)
# 最小宽度限制
min_widths = [12, 8, 20, 10, 14, 8, 8, 8]
col_widths.append(max(max_width, min_widths[i]))
# 列间距2个空格
col_gap = 2
# 计算总宽度
total_width = sum(col_widths) + col_gap * (len(headers) - 1)
print(f"{'='*total_width}")
print(f"📊 总计耗时排名(由快到慢)")
print(f"{'='*total_width}")
# 打印表头
header_line = ""
for i, header in enumerate(headers):
if i > 0:
header_line += ' ' * col_gap
header_line += pad_string(header, col_widths[i])
print(header_line)
print(f"{'-'*total_width}")
# 打印数据行
rank = 1
for item in summary:
desc = item[0]
port = item[1]
url = item[2]
total_time_str = item[3]
total_time_val = item[4]
pure_response_str = item[5]
dns_str = item[7]
tcp_str = item[9]
tls_str = item[11]
# 构建每列数据
cols = [
desc,
port,
url,
total_time_str,
pure_response_str,
dns_str,
tcp_str,
tls_str
]
# 拼接行
line = ""
for i, col in enumerate(cols):
if i > 0:
line += ' ' * col_gap
line += pad_string(col, col_widths[i])
# 失败/异常用红色显示
if total_time_val == 999999:
print(f"{RED}{rank}. {line}{RESET}")
elif rank == 1:
# 第一名用绿色
print(f"{GREEN}{rank}. {line}{RESET}")
else:
print(f"{rank}. {line}")
rank += 1
print(f"{'='*total_width}\n")
if __name__ == "__main__":
if sys.platform == "win32":
os.system("")
if len(sys.argv) < 2:
print(f"\n{BOLD}{YELLOW}[用法1]{RESET} {GREEN}独立模式{RESET}: python url-Test.py {CYAN}代理端口1 网址1{RESET},{CYAN}代理端口2 网址2{RESET}")
print(f" {BOLD}{YELLOW}注意:{RESET} 独立模式仅支持英文逗号分隔多组任务,空格为端口与网址的分隔符")
print(f"\n{BOLD}{YELLOW}[用法2]{RESET} {GREEN}共用模式{RESET}: python url-Test.py {CYAN}端口1{RESET} -w {CYAN}网址{RESET}")
print(f"{BOLD}{YELLOW}[用法2]{RESET} {GREEN}共用模式{RESET}: python url-Test.py -w {CYAN}网址{RESET} {CYAN}端口1 端口2 端口3{RESET}")
print(f" {BOLD}{YELLOW}注意:{RESET} {BOLD}{RED}仅 -w 共用模式{RESET}支持端口用英文逗号、中文逗号、空格混用分隔")
print(f" {BOLD}{YELLOW}端口描述:{RESET} -w 模式下支持 {CYAN}端口-描述{RESET} 格式,如 {CYAN}907-香港 800-日本{RESET}")
print(f"\n{BOLD}{YELLOW}[分隔符支持]:{RESET}")
print(f" {GREEN}-w 共用模式:{RESET} 英文逗号({CYAN},{RESET})、中文逗号({CYAN},{RESET})、空格 混用")
print(f" 示例: {CYAN}700,8000,600 8001{RESET} 均可正确解析")
print(f" {GREEN}独立模式:{RESET} 仅支持英文逗号({CYAN},{RESET})分隔多组任务")
print(f"\n{BOLD}{YELLOW}[自动补全逻辑]:{RESET}")
print(f" 1. 纯域名 ({CYAN}a.com{RESET}) -> 自动补全为 {CYAN}https://{RESET} (443端口)")
print(f" 2. 带协议 ({CYAN}http://a.com:1688{RESET}) -> 保持原样 (使用指定协议和端口)")
print(f"\n{BOLD}{YELLOW}[独立模式示例]:{RESET}")
print(f" python url-Test.py {CYAN}1205 wap.nkcc.com{RESET},{CYAN}8501 gg.nkcc.com{RESET},{CYAN}1203 alice.nkcc.com{RESET}")
print(f"\n{BOLD}{YELLOW}[共用模式示例]:{RESET}")
print(f" python url-Test.py {CYAN}700,8000,600{RESET} -w {CYAN}alice.nkcc.com{RESET}")
print(f" python url-Test.py -w {CYAN}alice.nkcc.com{RESET} {CYAN}700 8000 600{RESET}")
print(f" python url-Test.py -w {CYAN}http://www.nodeseek.com{RESET} {CYAN}907-halo 800-zouter 4000-光帆 700-菠萝云 {RESET}")
sys.exit(0)
# 解析参数,找 -w 位置
full_input = " ".join(sys.argv[1:])
args = sys.argv[1:]
# 检查是否使用共用模式(有 -w 参数)
url_index = -1
try:
url_index = args.index("-w")
except ValueError:
pass
if url_index != -1:
# 共用模式:-w 参数存在
if url_index + 1 >= len(args):
print(f"\n{RED}错误: -w 后缺少网址参数{RESET}\n")
sys.exit(1)
common_url = args[url_index + 1]
# 端口列表在 -w 的前面或后面
ports_str = ""
if url_index > 0:
# -w 在后面,端口在前面
ports_str = " ".join(args[:url_index])
elif url_index + 2 < len(args):
# -w 在前面,端口在后面
ports_str = " ".join(args[url_index + 2:])
if not ports_str:
print(f"\n{RED}错误: 未提供端口列表{RESET}\n")
sys.exit(1)
# 解析端口(支持英文逗号、中文逗号、空格混用)
# 先把中文逗号替换成英文逗号,再统一处理
ports_str = ports_str.replace(",", ",")
# 按逗号或空格分割成独立项
items = []
for item in ports_str.replace(",", " ").split():
item = item.strip()
if item:
items.append(item)
# 构建任务列表,支持端口-描述格式
all_tasks = []
for item in items:
if "-" in item:
parts = item.split("-", 1) # 只分割第一个"-"
port = parts[0]
desc = parts[1]
else:
port = item
desc = ""
all_tasks.append([port, common_url, desc])
if not all_tasks:
print(f"\n{RED}错误: 端口解析失败{RESET}\n")
sys.exit(1)
else:
# 独立模式:原逻辑
groups = [g.strip() for g in full_input.split(",") if g.strip()]
all_tasks = []
for group in groups:
parts = group.split()
if len(parts) >= 2:
all_tasks.append([parts[0], parts[1], ""]) # 独立模式无描述
if not all_tasks:
print(f"\n{RED}错误: 参数解析失败。{RESET}\n")
sys.exit(1)
test_proxy(all_tasks)