自建节点在线监控探针:从解析订阅到状态页的完整实现
本站 节点状态页 从一台 2GB 内存的廉价 VPS 上,每 10 分钟探测数百个节点的连通性,并维持 30 天历史、邮件告警。整套系统不到 300 行 Python,这篇完整拆解实现思路,你可以照抄到自己的服务器上。
总体架构
cron (每10分钟)
│
├─ 1. 拉取订阅(每6小时一次,平时用缓存)──► node_cache.json
│
├─ 2. 解析节点列表 ──► 过滤信息节点、按 host:port 去重
│
├─ 3. 并发 TCP ping(2次取优,超时5s)
│
├─ 4. 写入 SQLite ──► 保留30天
│
└─ 5. 评估告警(订阅失效/在线率<10%)──► Resend 发邮件
│
HTTP 服务 (tracker) ◄── 查询 SQLite 渲染状态页
设计原则:标准库优先。只用 Python 内置的 socket / sqlite3 / http.server,零第三方依赖,2GB 小机器无压力。
第一步:解析订阅
订阅接口按客户端 UA 返回不同格式(实测 v2rayN UA 稳定拿到 base64 分享链接)。base64 解码后是逐行 URI:
vmess://eyJ2IjoiMiIsInBzIjoiSEswMS... ← 二次 base64 JSON
anytls://uuid@host:port?sni=xxx#节点名
trojan://password@host:port#节点名
解析要点:
- vmess 的 base64 里是 JSON,取
add/port/ps(节点名) - 其他协议走标准 URI 解析,节点名在
#后的 fragment 里(URL 编码需解码) - 过滤信息节点:订阅商喜欢塞"剩余流量、套餐到期、TG 群组"这类伪节点,按关键词过滤,否则状态页全是噪声
- 去重:同一个 host:port 只探测一次(很多名字不同、实际是同一台机器)
第二步:并发 TCP ping
def tcp_ping(host, port):
best = None
for _ in range(2): # 测两次取最优,过滤偶发抖动
t0 = time.monotonic()
try:
with socket.create_connection((host, port), timeout=5):
ms = (time.monotonic() - t0) * 1000
best = ms if best is None else min(best, ms)
except OSError:
pass
return best # None = 不可达
几百个节点用 ThreadPoolExecutor(24) 并发,3 秒左右全部测完。注意:ping 目标节点是合规的探测流量,但不要对订阅接口高频请求——我们把它降到每 6 小时一次,避免被机场风控误伤。
第三步:存储与清理
SQLite 完全够用。单表 probe_results(ts, airport, node, host, port, proto, ok, latency),按 (airport, ts) 建索引。30 天数据量约几十万行,查询毫秒级。定期 DELETE WHERE ts < now-30d。
第四步:状态页渲染
状态页是查询数据库动态生成的 HTML,关键设计:
- 机场卡片:在线率、平均延迟 + 60 格状态条(最近 10 小时,纯 CSS 色块)
- 日/周/月视图:按东八区自然日聚合
SUM(ok)/COUNT(*) - 节点 ID 脱敏:URL 里用
md5(机场|host|port)前 12 位,真实地址不出现在公开链接 - 零 JS 依赖:不用任何 CDN,大陆访问无白屏风险
第五步:邮件告警
接入 Resend API(curl POST 即可,注意 Python urllib 会被其 Cloudflare 风控拦,curl 不会)。告警状态机:
- 订阅连续 2 次刷新失败 → 发"订阅失效"
- 在线率 < 阈值 → 发"在线率过低"
- 故障恢复 → 发"已恢复"
- 同一故障只发一次,用状态文件记录
避坑记录(都是踩过的)
- 机场订阅接口识别 UA,返回 YAML 还是 base64 全看运气 → 固定
v2rayN/6.0 - 与监控服务共用一个 SQLite 会
database is locked→ 加busy_timeout - 裸 IP + 自签证书的订阅 → 需要
insecure选项跳过校验 - 历史数据里的信息节点要清理,否则在线率被拉低
这套系统支撑了本站全部状态数据。配合 监控方法论 阅读,你能做出比自己买的服务更可信的稳定性判断。