Airport subscriptions are designed for human use — pick a node, browse the web.
sub2node repurposes them: every node becomes its own 127.0.0.1:<port> endpoint (HTTP + SOCKS5), queryable by index like an array. Scripts and crawlers get a clean, rotating IP pool without buying expensive commercial proxy services.
Subscription URL
│ gen_config.py — fetch · decode · filter · parse (6 protocols)
▼
config.yaml ──► mihomo — one mixed-port per node (33001, 33002 …)
│
▼
probe.py — concurrent reachability + egress-IP check
│
▼
proxies.json ── alive nodes sorted by latency ◄── proxypool.py (import API)
| Protocol | URI scheme |
|---|---|
| VMess | vmess:// |
| Trojan | trojan:// |
| VLESS (incl. Reality) | vless:// |
| Shadowsocks | ss:// (SIP002 + legacy) |
| Hysteria2 | hysteria2:// · hy2:// |
| TUIC v5 | tuic:// |
| Dependency | Install |
|---|---|
| mihomo | brew install mihomo (macOS) · releases (Linux/Windows) |
| Python 3.10+ | stdlib only — no pip install needed |
git clone https://github.com/Soth912/sub2node
cd sub2node
# One command: fetch subscription → start mihomo → probe alive nodes
python3 sub2node.py run "https://your-airport/your-token"
# Or use an env var (recommended — keeps the URL out of shell history)
export PROXY_SUB="https://your-airport/your-token"
python3 sub2node.py runmacOS / Shadowrocket note
Shadowrocket's TUN mode intercepts all outbound traffic including mihomo. Quit Shadowrocket (or disable TUN/system-proxy) before running.
python3 sub2node.py run [URL] [--workers N] # fetch + start + probe ← start here
python3 sub2node.py start [URL] # fetch sub & start mihomo only
python3 sub2node.py probe [--workers N] # re-probe without restarting
python3 sub2node.py status # show process + pool summary
python3 sub2node.py stop # stop mihomo
import sys
sys.path.insert(0, "/path/to/sub2node")
from proxypool import POOL, get, proxies_for, rotator, unique_ipv4
import requests
# Fastest node (index 0 = lowest latency)
requests.get(url, proxies=proxies_for(0))
# Round-robin — rotate every 5 requests
nxt = rotator(every=5)
for _ in range(100):
p = nxt()
requests.get(url, proxies={"http": p, "https": p})
# One identity → one unique IPv4 egress IP
clean = unique_ipv4()
for i, wallet in enumerate(wallets):
requests.post(url, proxies={"http": clean[i % len(clean)]["http"], "https": "..."})proxies.json is the cross-language interface — a JSON array sorted by latency:
[
{
"index": 0,
"name": "n003-Tokyo-optimised",
"proto": "trojan",
"port": 33003,
"http": "http://127.0.0.1:33003",
"socks5": "socks5://127.0.0.1:33003",
"egress_ip": "1.2.3.4",
"latency_s": 0.312
}
]curl -x http://127.0.0.1:33003 https://example.com| File | Description |
|---|---|
sub2node.py |
CLI entry point — run, start, stop, probe, status |
gen_config.py |
Subscription parser → mihomo config |
probe.py |
Per-port reachability and egress-IP checker |
proxypool.py |
Python import helper |
config.yaml |
Generated mihomo config (do not edit) |
nodes_meta.json |
All parsed nodes (input to probe) |
proxies.json |
Alive nodes sorted by latency (consumer interface) |
mihomo.log |
mihomo runtime log |
| Symptom | Cause | Fix |
|---|---|---|
Connection refused on port 330xx |
mihomo not running | python3 sub2node.py start |
502 Bad Gateway |
Node expired | python3 sub2node.py probe |
| All nodes fail / wrong egress IPs | TUN / system proxy intercepting | Disable conflicting proxy software |
proxies.json is empty |
Nothing passed probe | Re-run with --workers 32; or try a different subscription |
SOCKS5 Missing dependencies |
pysocks not installed | pip install "requests[socks]" or use the http field |
MIT
📖 中文说明(点击展开)
机场订阅是给人用的——打开客户端、选节点、上网。
sub2node 把它变成程序可调用的代理池:订阅里的每个节点都会被绑定到一个本地端口(127.0.0.1:33001、33002……),脚本和爬虫像访问数组一样 POOL[i] 取一个代理就能用,不需要购买昂贵的商业代理服务。
VMess · Trojan · VLESS(含 Reality)· Shadowsocks · Hysteria2 · TUIC v5
- mihomo:
brew install mihomo(macOS)或从 releases 下载 - Python 3.10+:只用标准库,无需额外安装包
git clone https://github.com/Soth912/sub2node
cd sub2node
# 一条命令:拉订阅 → 启动 mihomo → 探测可用节点
python3 sub2node.py run "https://你的机场/你的订阅链接"
# 推荐:用环境变量存 URL,避免出现在 shell 历史里
export PROXY_SUB="https://你的机场/你的订阅链接"
python3 sub2node.py runmacOS 注意
如果 Shadowrocket 开了 TUN 模式或全局代理,会劫持 mihomo 的流量导致节点全部失效。
使用 sub2node 前请先退出 Shadowrocket 或关掉它的 TUN/全局开关。
python3 sub2node.py run [URL] [--workers N] # 拉订阅 + 启动 + 探测 ← 从这里开始
python3 sub2node.py start [URL] # 只拉订阅并启动 mihomo
python3 sub2node.py probe [--workers N] # 不重启、只重新探测节点
python3 sub2node.py status # 查看 mihomo 状态和节点概况
python3 sub2node.py stop # 停止 mihomo
import sys
sys.path.insert(0, "/path/to/sub2node")
from proxypool import POOL, get, proxies_for, rotator, unique_ipv4
import requests
# 用延迟最低的节点(index 0)
requests.get(url, proxies=proxies_for(0))
# 每 5 次请求自动换下一个节点(循环)
nxt = rotator(every=5)
for _ in range(100):
p = nxt()
requests.get(url, proxies={"http": p, "https": p})
# 每个身份用一个唯一 IPv4 出口(适合多账号场景)
clean = unique_ipv4() # 去重 + 过滤 IPv6
for i, wallet in enumerate(wallets):
p = clean[i % len(clean)]["http"]
requests.post(url, proxies={"http": p, "https": p})[
{
"index": 0,
"name": "n003-Tokyo-优化",
"proto": "trojan",
"port": 33003,
"http": "http://127.0.0.1:33003",
"socks5": "socks5://127.0.0.1:33003",
"egress_ip": "1.2.3.4",
"latency_s": 0.312
}
]每次 probe 后数组按延迟重新排序,下标会变。跨会话认准某个节点请用 egress_ip 或 name 匹配,不要依赖 index。
| 现象 | 原因 | 处理 |
|---|---|---|
| 330xx 端口连接被拒 | mihomo 没有运行 | python3 sub2node.py start |
| 大量 502 Bad Gateway | 节点失效 | python3 sub2node.py probe |
| 全部连不上 / 出口 IP 不对 | Shadowrocket TUN 在劫持流量 | 退出 Shadowrocket |
| proxies.json 为空 | 探测全失败 | 加 --workers 32 重试,或换订阅 |
| SOCKS5 缺依赖报错 | 没有 pysocks | pip install "requests[socks]" 或改用 http 字段 |