crates.io 下载量:我们 80% 的下载都是跟着新版本来的

太长不看:2026 年 10 月 17 日,我把 crates.io 上我那 69 个 Rust crate 的每日下载数据全部拉了下来。89 天里一共记录了 3,676 次下载,其中 80.1% 落在新版本发布的当天或次日。一个新版本不管里面写了什么,头 24 小时大约能拿到 13 次下载,然后就归零。依赖这 69 个 crate 中任何一个的外部 crate:零。真正能说明有没有人在用你的 crate 的数字,是安静日基线,我们的是 339 次下载,大约每天 3.8 次。
先给搜索过来的朋友一个简短答案。crates.io 下载量,就是这个 crate 的 .crate 文件从 crates.io CDN 被拉取的次数,按 CDN 的请求日志统计(crates.io 在 2024 年 3 月改为基于日志计数)。自 2025 年 10 月 29 日起,只统计看起来来自 cargo 的请求(或者完全不带 user agent 的请求)。它不是用户数、安装数或项目数。某位开发者的 CI 每天在干净机器上跑 30 次 cargo build,每天就贡献 30 次下载;而某个人装了一次你的 crate 并留在本地缓存里,这辈子只贡献 1 次。
那么一个小 crate 的下载数里,到底装的是什么?下面是对一组真实 crate 的最新测量、拉取数据的脚本(直接复制,拿去跑你自己的 crate),以及怎样读懂自己的图表,既不自欺欺人地高估,也不妄自菲薄。

这 69 个 crate 是怎么来的
先交代一下背景,因为数据的形状直接取决于这些 crate 的发布方式。我维护着一堆 Rust 库和工具:fin-primitives(交易系统的基础组件)、tokio-prompt-orchestrator(一个带去重和熔断器的 LLM 代理)、proof-engine(一个 OpenGL 游戏引擎),还有 CoreScout,一个拆成 33 个 crate 的 workspace 的 MCP server。
它们分三波发布:2026 年 3 月 51 个版本,9 月 116 个,10 月 68 个,总共 235 个版本。10 月那波是 10 月 4 日的一次大规模发版:先让 AI 编程 agent 把每个 crate 都升级了一遍,最后我一口气全部发布。这一天在后面会很关键。
10 月 17 日美东时间上午 7:10 拉取时的累计总量:4,815 次下载。crates.io 的“recent downloads”(最近 90 天)数字是 3,677。我给这些 crate 定的目标是 100,000,所以实打实的问题是:这 4,815 里有多少是真人。
40 行代码,自己把数据拉下来
crates.io 有一个公开的 JSON API。它没有正式的版本号,但网站自己就在调用它,数据访问政策只要求两件事:每秒最多一个请求,以及一个说明你是谁的 User-Agent。三个接口就能拿到全部数据:
/api/v1/crates?user_id=N:某个用户拥有的全部 crate,附带累计和 90 天总量。/api/v1/crates/<name>/downloads:最近 90 天的每日下载量,按版本拆分(更早的版本合并在extra_downloads里)。/api/v1/crates/<name>/reverse_dependencies:哪些已发布的 crate 依赖了这个 crate。
这是我今天早上跑的脚本(pull.py,只删掉了挑选 JSON 字段的部分)。遇到 429 它会直接停下,不会重试,因为一个有礼貌的爬虫不会跟限流较劲:
UA = "tensorust-deepdive-research (contact: hello@tensorust.com)"
last = [0.0]
def get(path):
wait = 1.05 - (time.time() - last[0]) # stay under 1 request/second
if wait > 0:
time.sleep(wait)
last[0] = time.time()
req = urllib.request.Request("https://crates.io" + path, headers={"User-Agent": UA})
try:
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
except urllib.error.HTTPError as e:
if e.code == 429:
print("429 - stopping"); sys.exit(2)
raise
user = get("/api/v1/users/mattbusel")["user"]
crates = get(f"/api/v1/crates?user_id={user['id']}&per_page=100")["crates"]
for c in crates:
n = c["name"]
dl = get(f"/api/v1/crates/{n}/downloads")
vers = get(f"/api/v1/crates/{n}/versions?per_page=100")
rev = get(f"/api/v1/crates/{n}/reverse_dependencies?per_page=100")
# ... store dl["version_downloads"], dl["meta"]["extra_downloads"], versions, rev
69 个 crate 乘以三次调用,每秒一次,大约花了三分半钟。之后还要再跑一轮 69 次调用,把版本 id 映射到版本号和发布时间,因为 downloads 接口给每一行标的是数字形式的版本 id,而不是“2.15.0”。

如果你的 crate 超过几百个(或者想要整个 registry 的数据),别去猛刷 API:crates.io 每 24 小时会发布一份数据库转储(static.crates.io/db-dump.tar.gz),里面包含最近 90 天按天、按版本统计的下载表。
crates.io 页面给你看的是什么
下面是 fin-primitives 页面的底部,它是我下载量最高的 crate。先看图再看数字:整个夏天都是每天 0 到 3 次的低矮波动,然后在 9 月最后一周和 10 月第一周出现一根根高耸的尖峰,每根颜色都不一样。每种颜色就是一个版本。

这张图很诚实,但它只针对单个 crate。69 个 crate 就是 69 张图,不可能一张张用肉眼看,所以我把 90 天窗口里每个“crate × 日期”都归进了三类。
三种下载日
规则很简单,完全机械化:
- 发版窗口:这个 crate 在当天(UTC)或前一天发布了版本。
- 扫库日:没有发布,但当时已存在的 crate 中至少有一半在同一天被下载。
- 安静日:其余所有情况。
分类器的核心代码,摘自 analyze.py:
for n, c in C.items():
for v in c['downloads']['version_downloads'] + c['downloads']['meta']['extra_downloads']:
d = dt.date.fromisoformat(v['date'])
if any(0 <= (d - p).days <= 1 for p in pub[n]):
cat[v['date']][0] += v['downloads'] # publish window
elif v['date'] in sweeps:
cat[v['date']][1] += v['downloads'] # sweep day
else:
cat[v['date']][2] += v['downloads'] # quiet
2026 年 7 月 20 日到 10 月 16 日(89 个完整的 UTC 日,3,676 次下载)的结果如下:

2,945 次下载(80.1%)落在发版窗口里。392 次(10.7%)出现在六个扫库日。339 次(9.2%)是安静日下载。单日最高是 10 月 4 日那次大发版:发布了 68 个版本,下载 1,195 次。
换句话说:如果我从没发过 9 月和 10 月那些版本,90 天的数字大概在 730 左右,而不是 3,676。
一个新版本大约拿 13 次下载,然后就没了
为了看清单独一次发布值多少下载,我选出窗口内发布、且之后至少有一周数据的所有版本(181 个),按发布后的天数把下载量对齐。

第 0 天平均下载:13.45(中位数 10)。第 1 天:1.21。第 2 天:0.02。一个版本首周下载的 86.9% 出现在第 0 天或第 1 天。所有版本中,第 0 天加第 1 天的合计最少是 8,最多是 66,中位数 12。
露馅的是下限。在有史以来发布的全部 235 个版本里,单个版本的最低累计下载是 9,中位数是 18。我有 19 个 crate 只发过一个版本,其中包括 corescout-cli 和 corescout-lineage 这类内部管道 crate,项目之外的人根本没有理由知道它们。它们的累计下载在 10 到 28 之间,中位数 15。不管有没有真人读过 README,总有某种东西会在发布后几小时内把每个新版本拉一遍。
这个“东西”是什么?数据回答不了。我的猜测(只是猜测):跟踪 crates.io 索引、新版本一出现就拉取的 registry 镜像和构建农场,再加上安全扫描和许可证扫描工具。2025 年的 user-agent 过滤就是为了剔除镜像而写的,所以能通过过滤的,要么是 cargo 本身,要么是不发 user agent 的客户端。docs.rs 也会自动为每个新版本构建文档;我没能从公开文档里确认它的拉取会不会被计数,所以我不下这个结论。
结论非常实际:在 crates.io 上,你发布的版本数量,比代码本身的任何特质都更能预测下载量。在我的 69 个 crate 里,版本数和累计下载量的相关系数是 0.82。这就是为什么 fin-primitives(13 个版本)遥遥领先,而我那些真正更有意思的 crate 反而落在后面。
扫库日:所有 crate 同时被拉一遍
第二类更让我意外。10 月 9 日什么都没发布,69 个 crate 却在同一天全部被下载,一共 184 次。10 月 13 日:69 个里有 56 个,大多各一次。这不可能是真人。一个人会装一个工具和它的依赖树,而不会在同一个下午同时装一个统计 crate、一个交易 crate、一个游戏引擎和一个 TFT 助手。

窗口内有六天符合这个模式:8 月 3 日(当时已存在的 17 个 crate 全部被下载)、9 月 29 日、10 月 5 日、9 日、13 日和 15 日。后面几次偏向 CoreScout 家族:10 月 6 日和 15 日下载最多的是 corescout-core、corescout-mirror 和 corescout-memory,这正好是有人解析 CoreScout 某个二进制的依赖树时会看到的情形。这就引出了一个没人提的放大因素。
Workspace 会把下载量成倍放大
CoreScout 是一个 Cargo workspace,而把 workspace 发布到 crates.io 时,每个成员都会变成一个独立的 crate。这是真实的根 Cargo.toml(节选):
corescout-core = { version = "1.2.0", path = "crates/core" }
corescout-mirror = { version = "1.2.0", path = "crates/mirror" }
corescout-substrate = { version = "1.2.0", path = "crates/substrate" }
corescout-memory = { version = "1.2.0", path = "crates/memory" }
corescout-represent = { version = "1.2.0", path = "crates/represent" }
corescout-science = { version = "1.2.0", path = "crates/science" }
corescout-core 有 25 个依赖方,全是它自己的兄弟 crate。根据反向依赖数据,corescout-mcp 的依赖树里有我另外 16 个 crate,所以在干净机器上执行一次 cargo install corescout-mcp,应该会在 17 个 crate 页面上合计显示最多 17 次下载。在全部 69 个 crate 中,反向依赖 API 列出了 178 个依赖版本,每一个都是我自己的。
这就是残酷的地方。依赖方最多的 crate 看起来“用的人”最多(有五个及以上依赖方的 11 个 crate,安静日下载中位数是 6;没有依赖方的 25 个 crate,89 天里的中位数是零),而这完全是我自己接线接出来的。
安静日基线:唯一代表真人的数字
去掉发版窗口和扫库日,剩下的是 89 天 339 次下载,69 个 crate 合计每天约 3.8 次。这是最接近“今天某处有人用我的代码构建了东西”的数字。它不是零,而且一直在涨:整组 crate 在 7 月下旬平均每天 1.7 次安静日下载,8 月 3.4 次,9 月 2.8 次,10 月上半月 8.1 次(10 月的 crate 数量多得多,所以要谨慎看待)。
大部分是一个 crate 撑起来的。光 fin-primitives 一个就占了 339 次安静日下载中的 128 次(38%),从 7 月起几乎每天一两次,远早于今年秋天那堆发版。我猜是有那么几个人或者一条 CI 流水线在定期基于它构建。这是真实的信号,而如果一句“都是发版日机器人”就把下载量打发掉,这个信号也会跟噪声一起被扔掉。
还有一个能终结一切争论的数字:外部反向依赖,0。我自己之外,没有任何已发布的 crate 用到这 69 个中的任何一个。ecosyste.ms 的 crates.io 索引是查这个的好的第二来源,因为它还会追踪那些依赖某个 crate 但自己不发布 crate 的仓库。
拿你自己的 crate 试试
- 把你的数字拆成三个数字来读。发版窗口、扫库日、安静日。跑一下上面的分类器(或者直接看你 crate 页面上按版本区分的颜色);只有安静日那条线跟用户有关。
- 每个版本白送 10 到 15 次下载,心里有数。如果一个全新的 crate 第一天显示 12 次下载,说明还没人发现它,这很正常。如果三周后某个普通的周二显示 12 次,那很可能有人发现了。
- 别为了刷下载量去发版。这招确实管用(我们刚测出来它管用),也正因如此,下载量不能当作受欢迎程度的指标,任何评估你 crate 的人都会转而看依赖方和近期活跃度。
- 数依赖方,别数下载。crates.io 的 Dependents 标签页、反向依赖 API 和 GitHub 的“Used by”才是真实采用率的体现。出现在别人依赖树里的一个 crate,抵得上一千次发版日拉取,因为对方每跑一次 CI 都会顺带下载你的,永远如此。
- 记住 workspace 会按宽度放大数字。把一个工具拆成 30 个 crate,每次安装看起来就像 30 次下载。想要一个诚实的标题数字,就报二进制 crate 的数。
- 定期重新拉取。API 对每个 crate 只保留 90 天的每日数据。想看趋势,就每个月把 JSON 存一份;某一天一旦滑出窗口,按天的拆分数据就永远没了。
全部数字
| 指标(crates.io API,2026 年 10 月 17 日美东时间上午 7:10 拉取) | 数值 |
|---|---|
| crate 数 / 版本数 | 69 / 235 |
| 累计下载 | 4,815 |
| 7 月 20 日至 10 月 16 日下载(89 天) | 3,676 |
| 发版窗口内 | 2,945(80.1%) |
| 扫库日 | 392(10.7%) |
| 安静日 | 339(9.2%),每天 3.8 次 |
| 新版本第 0 天下载(平均 / 中位数) | 13.45 / 10 |
| 首周下载中第 0 天或第 1 天的占比 | 86.9% |
| 单个版本的最低累计下载 | 9 |
| 相关系数:版本数 vs 下载量 | 0.82 |
| 外部反向依赖 | 0 |
先说清楚,这不是一篇“下载量都是假的”的文章。计数规则是公开的,也很合理,安静日基线是真实的,而且在增长。这是一篇“搞清楚你看的是数字里哪一部分”的文章。我接下来在意的里程碑不是 10,000 次下载,而是第一个拥有外部依赖方的 crate。30 天后我会再拉一次数据,并公布前后对比。
如果你也在维护 crate:你的安静日基线和总量放在一起是什么样?我真心想知道,80% 落在发版窗口对小 crate 来说是常态,还是我的发版习惯让情况更糟了。
同一批项目的更多内容:tokio-prompt-orchestrator 里的语义缓存、在一台 PC 上跑 20 个编程 agent(就是升级这些 crate 的那批 agent),以及 Bevy 实例化渲染 639,000 个单位。
2026 年 10 月 17 日首发于 tensorust.com,同时发布在 dev.to。相关链接:查任意 crate:真实下载 vs 发版日机器人、crates.io 上全部 69 个 crate、运行编程 agent 的工具、tokio-prompt-orchestrator 中的语义缓存。
接着读
- 开发 · 2026 年 10 月 23 日 · 阅读约 13 分钟选一个能保持顺序的 UUIDv7 库:实测 10 个,.NET 失败率 50%
- 游戏 · 2026 年 10 月 19 日 · 阅读约 18 分钟Balatro 的存档文件会被当作代码执行。损坏的存档每 5 个就有 1 个照样能读
- 开发 · 2026 年 10 月 18 日 · 阅读约 14 分钟用 Rust 写数据库:AI 通过了 86.6 万个测试,却栽在 ROLLBACK 上