这个项目想回答的问题很具体:如果手里只有公开数据,能不能搭出一条“说得清口径、对得上数字、能给别人看”的供应链风险主题域?
我没有从淘宝硬爬开始,而是选了 OSV 漏洞库、deps.dev 依赖图和 npm downloads。原因很现实:免登录、字段相对稳定,足够练分层、事实粒度和指标治理;反爬对抗会把时间全耗在采集上,数仓本身反而做浅。
在线演示:开源供应链数据大屏
01主链路
OSV dump / deps.dev / npm downloads / PyPI JSON
-> ODS 原文(按 dt 分区)
-> DWD 明细(边、affects、下载日、发版)
-> DIM(包、版本、漏洞、生态)
-> DWS(包日、生态日)
-> ADS(高危榜、热点、僵尸包、日指标)
-> 静态 JSON 导出
-> Caddy 托管 oss.zl4629.xyz本地计算用 Python 做采集,DuckDB 做建模;可选本机 PySpark 复算同一套汇总。展示站刻意做成静态文件,服务器只负责托管,不在线上跑采集。
02数据规模是怎么撑起来的
依赖边包含 DIRECT 与 INDIRECT 两类,漏洞侧来自 PyPI 与 npm 的 OSV dump,affects 按版本范围展开成明细。边量主要靠“高依赖包种子 + 多版本拉图”堆起来:种子包优先选框架和工具链,一个种子会展开出一大片下游。
有两点容易被误读:叶子包 edges=0 是正常现象,不能当采集失败;npm 下载侧部分包会撞上限流,缺就标缺,不补假数据。所有产物都是可复跑的分区,口径质疑可以随时回到同一批输入上重算。
03建模里真正重要的点
·1. 事实粒度先写死
依赖边的粒度是:某次解析图中的一条有向边。
漏洞 affects 的粒度是:漏洞对某包的一段版本范围,而不是“当前最新版本一定中招”。
如果这两句说不清,后面所有榜单都会漂。
·2. 代表版本,避免多版本重复计数
包日指标如果把每个采样版本的直接依赖简单相加,数字会虚高。
当前口径是:优先 is_default,否则取最新 published_at,只在代表版本上算 M01。
·3. 指标必须带边界
| 指标 | 口径 | 容易误读成 |
|---|---|---|
| M01 直接依赖 | 仅 DIRECT,且代表版本 | “传递依赖也算直接” |
| M03 高危 | CRITICAL/HIGH 的范围级 affects | “线上已被利用” |
| M08 被依赖 | 种子闭包内 DIRECT 入边 | “全网有多少包依赖它” |
| M07 僵尸包 | npm,久未发版且下载仍高 | “一定要立刻下线” |
deps.dev 的 dependents 接口实测 404,所以 M08 明确写成 seed_closure。这不是偷懒,是接口事实。
04质量门禁
采集阶段就拦了一批“货不对板”的路径:
- OSV zip 必须下完整、可解压。
- 漏洞 id 唯一。
- affects 的 ecosystem 映射率设阈值,低于阈值直接拦。
- 依赖边关键字段非空。
- 建模后检查 package_sk 关联、指标字典是否登记。
还有一批 warn 项会长期存在,例如下游解析版本未必都在已采 dim 里。门禁的意义是区分“能发布”和“还要解释”,不是把所有 warn 伪装成绿勾。
05展示层:为什么最终是静态大屏
最早本地有 FastAPI + 动态读 parquet。部署到 1.6G 内存的小机器时,我选择导出 JSON,让 Caddy 只做 file_server:
- 几乎不占运行内存。
- 和现有 blog 的静态托管方式一致。
- 演示稳定,不依赖服务器上的 Python 服务是否挂着。
图表数据用 DuckDB SQL 预聚合,前端 ECharts 只负责画。页面注释压到一行,避免大屏变成说明书。
06还没做、也不装做了
- 版本范围精确命中(M04)还没落地。
- npm 下载因限流没有拉全。
- 没有生产级 Hadoop/Hive 集群,本机 PySpark 只证明 SQL 形态可迁移。
- 被依赖次数不能解释成全网统计。
把边界写清楚,反而更像工程记录,而不是作品集包装。
07小结
这条链路让我练到的不是“会接几个公开 API”,而是:
- 主题域怎么切。
- 事实粒度怎么定。
- 指标怎么避免会上吵口径。
- 展示层怎么按部署约束收敛。