自动抓取并合并互联网上公开的免费节点,用中美双出口的多轮探测筛出真正 能用的节点,输出 Clash Meta / Clash / V2Ray 订阅。
核心代码与规则集源自 peasoft/NoMoreWalls(反 996 许可证)。 上游只做「抓取 + 合并 + 按名称分类」,不判断节点是否可用;本项目加了一整条 探测 → 筛选 → 分发的流水线。
不同内核要用不同的订阅文件 —— 用错了会加载失败或功能缺失。
| 你的客户端 | 内核 | 大陆可用(推荐) | 全量 |
|---|---|---|---|
| Clash Verge Rev / Mihomo Party / FlClash / Clash Meta for Android | Clash Meta (Mihomo) | list.meta.cn.yml |
list.meta.yml |
| 老版 Clash for Windows / Clash(经典内核) | 原版 Clash | list.cn.yml |
list.yml |
| v2rayN / v2rayNG / Nekoray / sing-box | — | list.txt |
— |
为什么原版 Clash 要单独一份:
list.meta.cn.yml里含flow、grpc-opts、obfs、client-fingerprint、xhttp-opts等 Meta 专有字段, 原版 Clash 内核不认识,导入会直接报配置错误。list.cn.yml是同样一批节点、同样经过中美判定与延迟筛选, 但只含老内核认识的字段(alpn/alterId/cipher/network/ …)。两份的节点数不同是正常的:原版 Clash 不支持 vless / hysteria2 / tuic, 它的候选池本来就小一圈(但判定用的是同一批地址,结论一致)。
| 订阅 | 节点数 | 用途 |
|---|---|---|
list.meta.cn.yml |
~1100 | Clash Meta,大陆可用(推荐) —— 中美多轮联合判定 + 延迟上限 + 协议偏好排序 |
list.cn.yml |
~430 | 原版 Clash,大陆可用 |
list.meta.yml |
~1550 | Clash Meta,全量(美国侧视角筛的) |
list.meta.us.yml |
~1400 | Clash Meta,美国侧多轮筛选 |
list.yml |
~600 | 原版 Clash,全量 |
list.txt |
~2100 条 | V2Ray / 通用(Base64) |
list_raw.txt |
~2100 行 | V2Ray 明文(查看用) |
主通道(GitHub Releases) — 稳定 URL,无需跟随分支:
# Clash Meta(Mihomo)—— 大陆可用
https://github.com/learnLi/free-nodes/releases/latest/download/list.meta.cn.yml
# 原版 Clash(老内核)—— 大陆可用
https://github.com/learnLi/free-nodes/releases/latest/download/list.cn.yml
# 全量
https://github.com/learnLi/free-nodes/releases/latest/download/list.meta.yml
https://github.com/learnLi/free-nodes/releases/latest/download/list.yml
# 其他
https://github.com/learnLi/free-nodes/releases/latest/download/list.meta.us.yml
https://github.com/learnLi/free-nodes/releases/latest/download/list.txt
备用通道(output 分支) — 走 raw.githubusercontent / jsDelivr:
https://raw.githubusercontent.com/learnLi/free-nodes/output/list.meta.cn.yml
https://cdn.jsdelivr.net/gh/learnLi/free-nodes@output/list.meta.cn.yml
配置片段(rule-providers / proxy-providers) — 单独的 Release:
| 用途 | 链接 |
|---|---|
| 节点列表(Meta) | .../releases/download/snippets/nodes.meta.yml |
| 节点列表(原版 Clash) | .../releases/download/snippets/nodes.yml |
| 广告拦截 | .../releases/download/snippets/adblock.yml |
| 需代理域名 | .../releases/download/snippets/proxy.yml |
| 直连域名 | .../releases/download/snippets/direct.yml |
| 锁区域名 | .../releases/download/snippets/region.yml |
Release 的资产名不允许包含
/,因此顶层订阅与配置片段分装在两个 Release(latest/snippets)中。交叉表(判定过程):
.../releases/latest/download/crosscheck_summary.md
- 从上表复制对应链接,在客户端里添加为订阅。Clash Meta 用户把订阅类型选为 Meta。
- 导入后建议:
🐟 漏网之鱼保持DIRECT- 遇到跳转反诈中心时,把
❓ 疑似国内切到🚀 选择代理 - 想自动选快节点,用
♻ 自动选择/🔰 延迟最低组(依赖客户端自身的 health-check)
┌───────────── GitHub Actions(美国出口)─────────────┐
抓节点 ─▶ 探测(US) ─▶ 单轮裁剪 ─▶ Stage 1 ─▶ Stage 3
probe.py prune.py stage1.py crosscheck.py
│ │ │
list.meta.yml list.meta.us.yml list.meta.cn.yml
│ │ │
│ ▼ │
│ output 分支 │
│ list.meta.us_<ts>.yml│
│ │ │
│ ▼ │
│ ┌─── NAS(大陆出口)───┐ │
│ │ 探测 Stage 1 产物 │ │
│ │ probe_domestic_<ts> │ │
│ └────────┬────────────┘ │
│ ▼ │
└───── 交叉比对 ◀───────────┘
| 阶段 | 做什么 | 产物 |
|---|---|---|
| 全量 | 抓取 + 单轮裁剪(剔明文代理、确凿已死、延迟超限) | list.meta.yml(Meta)/ list.yml(原版 Clash) |
| Stage 1 | 用美国侧多轮历史筛出跨轮一致的 | list.meta.us.yml |
| Stage 2 | NAS 在 Stage 1 产物上做国内探测 | probe_domestic_<ts>.csv |
| Stage 3 | 中美多轮历史联合判定 | list.meta.cn.yml(Meta)/ list.cn.yml(原版 Clash) |
单轮探测噪声很大。同一批 409 个地址连测 5 轮,单轮说「活」的结果并不稳定; 跨轮一致的才是真信号。实测(美国 5 轮 + 国内 3 轮):
美国跨 5 轮全活 314 / 全死 87 / 不定 8
中美交叉:真·大陆可用 120,美国说活国内说死 109,美国误杀 10
也就是说 —— 单轮美国探测会误导 27% 的节点(美国说活、国内被墙), 而「美国跨轮全死」也不能当判死用(其中 10 个国内是活的)。
| 美国 \ 国内 | 跨轮全活 | 跨轮全死 | 未定 |
|---|---|---|---|
| 跨轮全活 | ✅ 保留(真正的大陆可用) | ❌ 剔除 | ✅ 保留 |
| 跨轮全死 | ✅ 保留(被美国侧误杀) | ❌ 剔除 | ❌ 剔除 |
| 未定 | ✅ 保留 | ❌ 剔除 | ✅ 保留(数据不足,保守) |
另有一条硬指标独立于上表:国内延迟超过 max_latency_ms(默认 1000ms)的节点直接剔除。
走 TLS 的协议(vmess / vless / trojan)如果 TCP 通了但 TLS 握手失败或超时, 说明代理服务没起或 SNI 不对,节点实际上不可用。
早期版本只判断「TCP / TLS / QUIC 任一为 Y」,导致这批节点被算成活着 —— 它们同时污染了「跨轮存活」判定(误判为稳定存活)和延迟统计(把 5 秒超时值 当成真实延迟,让 p90 从 1282ms 虚高到 5002ms)。实测有 64 个这样的节点。
修法:TLS=N 或 QUIC=N 一律不算活着(协议不走 TLS 时该字段是 N/A,不受影响)。
这是整套东西能对上的关键:
CI 抓节点 ─▶ 算出时间戳 ts
├─▶ 发布 list_<ts>.full.yml 到 output 分支
├─▶ 发布 list.meta.us_<ts>.yml 到 output 分支
└─▶ 发布 probe_<ts>.csv 到 Release
NAS 拉 output 分支最新的 list.meta.us_<ts>.yml ─▶ 探测
└─▶ 结果命名 probe_domestic_<ts>.csv ─▶ 推到 probe-domestic 分支
交叉比对按 ts 配对 probe_domestic_<ts>.csv ↔ probe_<ts>.csv
如果时间戳表示「探测时刻」就会错位:NAS 探测时 output 分支上的列表往往是上一版 (CI 那一轮还没推完),两边探测的节点池不是同一批。实测这种情况下节点池只重叠 78%,交叉表里 32% 的节点只能判成「未测出」,「可用」数被稀释到 110; 改成按节点池版本对齐后重叠 100%,「可用」数 110 → 201。
| 协议 | 可用率 | 说明 |
|---|---|---|
| http / socks5 | 100% | 明文开放代理,已被剔除,不进入推荐订阅 |
| vmess | 84.6% | 最稳 |
| vless | 54.5% | |
| trojan | 31.4% | |
| ss | 25.0% | 无 TLS、协议特征明显,最容易被 GFW 识别 |
| hysteria2 | 2.5% | QUIC/UDP,服务器两端都无响应 |
协议选择比节点数量重要得多。
ss占节点总量的 12%,可用率只有 25%。 节点列表按这个偏好排序(见protocol_order)。
probe.py 对每个节点做:
| 协议 | 探测方式 |
|---|---|
| vmess / vless / ss / ssr | TCP 连接 + TLS 握手(走 TLS 的协议才做握手) |
| trojan | TCP 连接 + TLS 握手 |
| hysteria2 / tuic | QUIC Initial 探测(aioquic 做真实 TLS 1.3 握手) |
QUIC 部分不能用「只发一个 Initial 包」的简化做法:按 RFC 9000 §17.2.5.2,回 Retry 是 「可以」而非「必须」,quic-go 等服务端默认直接丢弃 —— 实测美国侧 46 个 QUIC 节点只有 2 个有响应。必须做完整握手才有意义。
每次探测带三个对照目标(1.1.1.1:443、8.8.8.8:443、github.com:443),
用来区分「节点死了」和「runner 整体网络不通」—— 对照全挂时本次结果不可信。
⚠️ 边界:CI runner 在美国,探到的是「海外可达」,不等于「大陆可达」。 这一点在设计里是刻意的 —— 见下面的三阶段流水线。
这些都不是连通性测试,全部基于节点字段本身:
- 假节点剔除 — 占位符地址(
8.8.8.8/127.0.0.0/8/0.0.0.0/8/169.254.0.0/16/ RFC 5737 文档保留网段)、假域名(.google.com/.github.com)、端口 < 20其中
127.0.0.53(systemd-resolved 的 DNS stub)特别重要:国内社区会植入指向它的 哨兵节点来检测客户端是否被 DNS 污染。上游只列了127.0.0.1,漏掉了它。 - SNI 修正 — SNI 含
google.com的节点改为www.bing.com(这类服务端并非面向国内设计) - 协议兼容性判定 — cipher 是否在目标客户端白名单内、SSR 的 obfs/protocol 是否兼容、 reality 的 public-key 长度是否合法
- 地区归类 — 纯按节点名称关键词匹配,与节点实际位置无关,仅供快速筛选
只剔除确凿的问题,判据是「路径通、但服务不在」(对端主动回 RST):
| 剔除 | 保留 |
|---|---|
Connection refused / reset by peer |
timeout(防火墙丢包,可能只对大陆 IP 放行) |
| TLS 被对端明确拒绝 | Network is unreachable(探测端没有该协议族的路由) |
| 明文 HTTP / SOCKS 代理 | DNS 解析失败(探测端 DNS 的问题) |
这里踩过一个坑:
Network is unreachable曾被当成「节点已死」, 结果一次性误杀全部 IPv6 节点 —— GitHub 的 runner 根本没有 IPv6 出口。
scripts/validate_config.py 会在构建时校验发布的所有配置:组引用是否都能解析、
组名/节点名是否重复、节点字段是否完整、port 是否合法、rules 引用的策略组是否存在、
url-test/fallback 组有没有 url。
Mihomo / Clash 遇到 proxy-group 引用了不存在的节点时,会直接报
proxy <name> not found 并拒绝加载整个配置 —— 用户在客户端里看到的就是「订阅无效」,
而在 YAML 上完全看不出来。校验不过就中止构建,绝不发布坏配置。
上游的 config.yml 里两个自动选组都是 interval: 300 + url: https://www.google.com/
include-all: true(探测全部节点)。现在有几百个节点,客户端启动时并发探测全部 是开机卡顿的主因。调优(lazy与max-failed-times是 Mihomo 字段,原版 Clash 会忽略):
| 项 | 上游 | 现在 |
|---|---|---|
url |
https://www.google.com/ |
https://www.gstatic.com/generate_204(204 空响应) |
interval |
300 | 600 |
timeout |
未设 | 5000 |
lazy |
未设 | true —— 只在组真正被使用时才探测 |
max-failed-times |
未设 | 3 —— 连续失败 3 次后不再探测 |
所有可调参数集中在 fetch_config.local.yml,改完直接生效
(CI 与 NAS 每次跑都重新读它):
filter: # Stage 1 与 Stage 3 共用
rounds: 6 # 参与判定的最近 N 轮探测历史
min_rounds: 2 # 至少这么多轮才判定(只出现一轮的判「未定」,保守保留)
alive_ratio: 1.0 # 存活占比阈值:1.0 = 跨轮全活;调到 0.7 更宽松
max_latency_ms: 1000 # 国内延迟上限,用于 list.meta.cn.yml
max_latency_ms_us: 1000 # 美国侧延迟上限,用于 list.meta.yml / list.meta.us.yml
> **两个阈值用在哪**:
>
> | 阈值 | 作用范围 | 用的延迟 |
> | :- | :- | :- |
> | `max_latency_ms` | `list.meta.cn.yml` | **国内侧**中位延迟 |
> | `max_latency_ms_us` | `list.meta.yml`、`list.meta.us.yml` | 美国侧中位延迟 |
>
> 必须是两个值:美国到亚洲天生就慢(200~400ms),拿美国侧的延迟去筛
> 「国内可用」那份,会误杀「离美国远但离大陆近」的好节点。
>
> 实测国内延迟分布:p50 = 434ms、p75 = 573ms、p90 = 1282ms、p99 = 2559ms。
> 1000ms 大约落在 p85 附近,会剔掉约 4% 的节点。想更宽松改 3000,
> 想更严格改 500。
>
> 延迟只对**成功**的探测有意义 —— 失败时耗时记的是超时上限(5 秒),
> 混进来会让分布图出现假尖刺(曾经把 p90 虚高到 5002ms)。
protocol_order: # 节点排序依据实测国内可用率
- vmess
- vless
- trojan
- hysteria2
- ss
- ...
consensus: # 诊断用,不参与任何过滤
enable_stats: true
min_sources: 0
alive_ratio调到 0.7 会更宽松(允许 70% 的轮次存活即算稳定),节点更多但 质量下降。随着历史积累,1.0 的门槛会自然变严,节点数缓慢下降 —— 这是好事, 说明淘汰机制在工作。
fetch.py 会统计每个节点被几个独立来源收录(consensus.csv / sources.csv)。
「独立来源」按仓库归并计数 —— 同一仓库的多个文件只算一个来源。
但实测结论是这个信号不可用:8 个独立来源之间几乎零重叠(独占率 95~100%),
98.1% 的节点只被一个源收录。换 server:port 口径也没改善 —— 因为那不是口径问题,
是源生态本身长尾。统计代码保留作为诊断数据,不用于任何过滤。
延迟该在哪儿测,取决于节点最终由谁去连:
| 探测点 | 判什么 | 为什么准 |
|---|---|---|
| CI(美国) | 服务器死没死 | 没有墙干扰,判死最准 |
| NAS(大陆) | 大陆被墙 | 只有墙的视角才有这个信息 |
实测 TCP 可连率(同一批节点):
美国 85.8% ← 中间是跨洋损耗
大陆 ~55% ← 差的那 42% 正是被墙的
GitHub 没有大陆区域的 runner(探针自报出口恒为美国,见 probe_summary.md)。
但家里的设备反而是更好的探测点:
| 维度 | GitHub runner(美国) | NAS(国内) |
|---|---|---|
| 出口路径 | 境外机房 → 境外节点 | 大陆住宅宽带 → 境外节点,与目标用户一致 |
| IP 信誉 | Azure 机房段,部分节点直接拒绝 | 住宅 IP,更可能被放行 |
| IPv6 | 没有(实测所有 IPv6 节点 Network is unreachable) |
家宽通常有公网 IPv6 |
NAS 侧只需要一个定时任务。本项目实际部署用的是 Docker 版:
git clone --depth=1 https://github.com/learnLi/free-nodes.git
cd free-nodes
docker build -t free-nodes-probe -f docker/Dockerfile .分工是刻意分开的:
| 谁 | 做什么 |
|---|---|
| 宿主机 | git:拉代码、取 output 分支的 Stage 1 产物、提交推送 |
| 容器 | 探测:TCP 连接 + TLS 握手 + QUIC 握手 |
仓库通过 -v 挂载到容器的 /app,所以 probe.py 改了 git pull 一下就生效,
不需要重建镜像。入口脚本放在镜像的 /entrypoint.sh(不是 /app),避免被挂载覆盖。
定时任务:
# 每 6 小时一次;脚本内部有 0~15 分钟随机抖动,打散固定周期的外连特征
17 */6 * * * /root/free-nodes/repo/docker/run.sh /root/free-nodes/repo /root/free-nodes >> /root/free-nodes/probe.log 2>&1为什么要有随机抖动:住宅 IP 每 6 小时固定向几百个境外 IP 发连接,形态很规整。 加上 0~15 分钟的随机等待,特征就没那么明显了。
不用 Docker 的话:
scripts/probe_domestic.sh是等价的原生脚本, 只依赖python3+ 5 个 pip 包(aioquic/PyYAML/requests/requests-file/typing_extensions;aarch64、arm64 都有预编译轮子,不用编译)。
想在 1Panel 里管理:1Panel 的「定时任务」支持 Shell 脚本类型, 填上面那条 cron 命令即可。注意别和 crontab 里的条目重复 —— 1Panel 的任务也是写进 crontab 的,两边都配会跑两遍。 (1Panel v1 的 API 接口默认是关的,
ApiInterfaceStatus = disable, 所以只能从面板 UI 里建。)
推回 GitHub 需要一把能 push 的 SSH 密钥:
ssh-keygen -t ed25519 -C 'nas-probe' -f ~/.ssh/nas_probe -N ''
cat ~/.ssh/nas_probe.pub # 加到 GitHub 账号的 Settings → SSH keys加到账号的 SSH keys,不是仓库的 Deploy keys —— 后者需要仓库 admin 权限。
建议同时用
ssh-keyscan github.com固定主机密钥,并与 GitHub 官方公布的指纹核对 (ED25519SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM, RSASHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU)。
也提供了 Docker 方案(docker/):容器只做探测(不含 git),推送由宿主机负责。
| 项 | 结果 |
|---|---|
| 出口 IP | 113.75.38.207(广州电信)—— 与同宽带的 PC 完全同一个出口 |
| 单轮耗时 | 51.9 秒(441 节点) |
| 流量 | 约 0.9 MB/轮 |
| TCP 连通率 | 54.7% |
| QUIC 响应率 | 1/46(2.2%) |
GFW 干扰特征(用于自检探针出口是否正常):
1.1.1.1:443 超时
8.8.8.8:443 超时
35.158.96.132:443 超时
104.16.111.193:443 正常(Cloudflare)
www.gstatic.com:443 正常
项目的阶段划分、验收标准与待决策项见 docs/ROADMAP.md。
Sync upstream config 工作流每天自动同步配置类文件:
| 文件 | 是否跟随上游同步 |
|---|---|
fetch_config.yml / abpwhite.txt |
✅ 每日自动覆盖 |
sources.list |
❌ 不覆盖,由本仓库自行维护 —— 上游的清单里没有本仓库自己筛出来的源,每天覆盖会把它们抹掉 |
config.yml |
❌ 不覆盖,由本仓库自行维护 |
fetch.py / dynamic.py |
❌ 不覆盖,由本仓库自行维护 |
config.yml 不再自动覆盖,是为了能自由定制分流规则与健康检查参数。代价是会错过上游的
规则更新,所以 sync.yml 会额外产出一份规则差异报告(artifact upstream-config-diff),
列出上游新增/删除了哪些规则,人工按需合并。
sources.list 不再自动覆盖,是因为上游的源清单和本仓库的实际用源已经不一样了 ——
本仓库会自己评估、增删源(见下面的「订阅源」)。被上游覆盖等于把评估结果清零。
fetch.py 抓取的源写在 sources.list 里。本仓库在评估之后新增的源:
| 源 | 节点数 | 说明 |
|---|---|---|
chenzhihan1751-cyber/node-sub-zixf |
1793 | 聚合 9 个公开源(含本仓库已有的 peasoft、ripaojiedian,另 7 个是新的) |
MatinGhanbari/v2ray-configs |
200 | 独立源,与本仓库原有源零重叠 |
评估方式:在境外机器上实测这两个源的样本节点可用率与重叠度,确认有增量价值后
才写进 sources.list。合并去重后的节点池从 520 涨到 2262。
本仓库自己的定制统一放在 fetch_config.local.yml,加载顺序:
fetch_config.yml(上游) → fetch_config.local.yml(本仓库) → local_fetch_config.yml(本地调试)
git clone --depth=1 git@github.com:learnLi/free-nodes.git
cd free-nodes
pip install -r requirements.txt
# 大陆无代理时:先建空的 local_proxy.conf 让脚本走 ghproxy
touch local_proxy.conf
python fetch.py有代理的话,在 local_proxy.conf 里写上代理地址(如 http://127.0.0.1:7890/),脚本会自动使用它。
调试开关(创建对应文件即可生效,勿提交):
| 文件 | 作用 |
|---|---|
local_NO_NODES |
不抓节点,只跑规则部分 |
local_NO_DYNAMIC |
不抓动态节点 |
local_NO_ADBLOCK |
不抓广告规则 |
local_fetch_config.yml |
覆盖配置(优先级最高) |
python tests/test_consensus.py
python tests/test_crosscheck.py
python tests/test_dynamic.py
python tests/test_fake_filter.py
python tests/test_parse.py
python tests/test_prefs.py
python tests/test_probe.py
python tests/test_prune.py
python tests/test_quic.py
python tests/test_validate.py10 个文件、约 250 项断言,全部离线(不联网)。
fetch.py 核心:协议解析、去重、分类、规则生成、产物写出
dynamic.py 动态订阅源
probe.py 可达性探测(TCP / TLS / QUIC)
prune.py 按探测结论裁剪
scripts/
rounds.py 多轮探测的公共逻辑
stage1.py Stage 1:美国多轮一致性筛选
crosscheck.py Stage 3:中美多轮联合判定
prefs.py 读配置(协议偏好、筛选参数)
get_setting.py 给 shell 读配置用
validate_config.py 配置一致性校验
probe_domestic.sh NAS 侧探测脚本
docker/ Docker 方案
tests/ 单元测试
生成物(list* / snippets/ 下除 example.yml 外 / consensus.csv 等)不进 git,
由 CI 打包上传到 Release。
| 工作流 | 触发 | 作用 |
|---|---|---|
Fetch |
每 3 小时 + 手动 | 抓取 → 探测(US) → 裁剪 → Stage 1 → 发布 → 输出分支 |
Cross-check |
由 Fetch 调用(每 3 小时)+ 定时(每 6 小时)+ 手动 | 取中美多轮数据 → 联合判定 → 发布大陆可用订阅 |
Sync upstream config |
每天 + 手动 | 同步上游配置类文件,产出规则差异报告 |
三个工作流都配了 concurrency 与 timeout-minutes。Cross-check 作为可复用工作流
被 Fetch 调用时,并发组必须与调用方区分开(${{ github.workflow }}-xcheck-...),
否则会去抢调用方正持有的锁,表现为 job 根本不出现。
- 本项目不审计来自第三方的节点内容。免费节点由陌生人自建,流量经过其服务器, 请勿用于敏感通信。
- 订阅内容来自多个第三方仓库,其中任何一方被投毒,都可能向你的客户端注入指向 攻击者服务器的节点。
- 广告拦截规则同样来自远程列表,理论上可被用于屏蔽正常站点。
- 明文 HTTP / SOCKS 开放代理已被剔除(代理方可见明文流量,且本就不是「免费节点」)。
- 请自行评估风险。本仓库仅供学习交流使用。
反 996 许可证 v1.0(继承自上游)。上游项目的许可证明确禁止实行 996 工作制的 公司使用,请知悉。