文章
在 X 上关注
为什么 star-history.com 这类工具突然拿不到数据了?0%
x

GitHub Star History API 实测:从约 2,500 次请求到 24 次

开发实践

Evan Long发布于 2026-10-15更新于 2026-10-15

要点 约 25.1 万 star 的 react/react,用旧接口拼全量曲线约需 2,511 次请求;新接口约 24 次,数据库在整段历史上对每个 star 大约只扫一遍(约 25.1 万行)。

这篇文章拆解 GitHub 工程博客 10 月 14 日发布的《From 2,500 requests to 24: Rebuilding star history without the stargazers》,再用我自己的开源项目 model-usage 实际调用一遍新接口。我关心的是两件事:一是作为维护者,现在怎么最省事地拿到 star 曲线;二是作为同样在做 API 和开发者工具的人,这次改造里有哪些可以直接搬走的做法。

图:GitHub 工程博客原文标题与作者区(作者 Taylor Cuilty & Sarita Iyer,页面日期 October 14, 2026)。截图于 2026-10-15 16:17(北京时间)。来源:GitHub 工程博客,headless Chromium 截取自标题到作者与日期行。

来源:GitHub 工程博客2026-10-15

图片预览

图:GitHub 工程博客原文标题与作者区(作者 Taylor Cuilty & Sarita Iyer,页面日期 October 14, 2026)。截图于 2026-10-15 16:17(北京时间)。来源:GitHub 工程博客,headless Chromium 截取自标题到作者与日期行。

为什么 star-history.com 这类工具突然拿不到数据了?

过去,想画一条 star 增长曲线,只能去翻 stargazer 列表:每一页是一批「当前给仓库点了 star 的人」以及点 star 的时间,工具保留时间戳、丢掉身份,自己累加。

问题出在两头。博客原文写道:

"Identities were available for bulk collection, so bad actors started scraping public stargazer lists for spam and phishing campaigns."(译:这些身份信息可以被批量收集,于是坏人开始爬取公开的 stargazer 列表,用于垃圾信息和钓鱼活动。)

GitHub 的第一步是止血:把 stargazer 和 watcher 列表收紧为仅仓库管理员和协作者可见。没有权限的调用方会拿到空响应或 403 Forbidden。博客里图表标注的收紧时间是 7 月 22 日。

副作用是,大量本来只用时间戳、根本不展示用户名的工具一起坏了,star-history.com 是最典型的一个。GitHub 随后去和维护者、工具作者聊,结论很朴素:

"What they cared about was the curve."(译:他们在意的是那条曲线。)

star-history.com 维护者、Bytebase 联合创始人 Tianzhou Chen 在博客中这样描述这段经历:

"When the API change disrupted Star History, users rallied around the project. GitHub's Community team listened and connected us with product engineering, who dug into our use case and worked alongside us to design a purpose-built endpoint. A difficult situation became a constructive collaboration between GitHub, Star History, and the broader community—ultimately leading to a better solution."(译:API 变更打乱了 Star History 之后,用户们纷纷声援这个项目。GitHub 社区团队听取了意见,把我们和产品工程团队联系起来;他们深入研究了我们的用例,与我们一起设计了一个专门的接口。一次困难的局面变成了 GitHub、Star History 和更广泛社区之间的建设性合作,最终带来了更好的方案。)

9 月 4 日,GitHub 更新日志宣布了新接口:GET /repos/{owner}/{repo}/stargazers/history,按时间返回 star 计数,不暴露任何 stargazer 身份。

旧的 stargazers 接口为什么这么贵?

旧接口 GET /repos/{owner}/{repo}/stargazers 回答的问题是「谁 star 了这个仓库」。每页最多 100 人,所以拼全量曲线的成本随热门程度线性增长:react/react 约 25.1 万 star,就要约 2,511 次请求,工具常常撞上限流,只好抽样翻页。

更糟的是一个分页 bug:

"Due to a bug in the pagination implementation, every request also counted all of those stars to work out how many pages existed, even though the caller received only one page of up to 100 people."(译:由于分页实现里的一个 bug,每次请求还会把全部 star 数一遍来算总页数,即使调用方只拿到一页、最多 100 人。)

代价有多大?原文给的数字是:收紧访问之前那一周,接近 37,000 次这类计数查询慢到被记录日志,平均接近 3 秒一次,数据库保护机制终止了 1,000 多次,并导致了请求超时。收紧访问后,数据库处理的 stargazer 请求减少约 60%,计数查询的总耗时下降约 80%。

图:计数查询每日总耗时(各数据库服务器合计,2026-06-22 至 08-21,UTC),虚线为 7 月 22 日访问收紧。来源:GitHub 工程博客,2026-10-14;原图为页面内 HTML 图表,无原图文件,改为元素截图,截于 2026-10-15 16:17(北京时间)。

来源:GitHub 工程博客2026-10-15

图片预览

图:计数查询每日总耗时(各数据库服务器合计,2026-06-22 至 08-21,UTC),虚线为 7 月 22 日访问收紧。来源:GitHub 工程博客,2026-10-14;原图为页面内 HTML 图表,无原图文件,改为元素截图,截于 2026-10-15 16:17(北京时间)。

怎样返回 star 计数才真正省数据库?三种方案对比

去掉身份解决了隐私,难的是别让每次请求都扫一遍整个仓库的 star。博客记录了三条路:

三种返回 star 计数的方案
方案做法隐私数据库代价结果
1. 只去身份、保留逐条事件无协作者权限时返回 starred_at,不带身份解决请求数不变,调用方仍要翻成千上万条记录再自己聚合放弃
2. 先聚合再分页全部 star 按天/周分桶、排序,再 OFFSET/LIMIT解决每页都重扫全部 star;react/react 全量 24 次请求约检查 600 万行放弃
3. 先按时间缩范围再聚合把 page/per_page 换算为有索引的 created_at 区间,只取这些周再按天计数解决每次只处理返回的那几周;react/react 约 24 次、全程约 25.1 万行上线

方案 2 最有迷惑性:响应确实小了,但数据库的活一点没少。原文的总结一针见血:

"Returning fewer buckets would shrink the JSON response, but it would not reduce the expensive grouping and sorting the database had already performed."(译:返回更少的桶会让 JSON 响应变小,但不会减少数据库已经做完的昂贵分组和排序。)

上线方案的数据流是:

text
indexed date range -> group rows in that range -> daily buckets -> response

每次请求最多 30 周,page 越大越往历史回溯;响应结构与 GET /repos/{owner}/{repo}/stats/commit_activity 一致。官方文档补充了契约细节:按自然周分组、最近一周在前,拼接各页就是一条连续序列;没有 star 的周返回 0;days 从周日开始;per_page 最大 30,page 最大 100;文档明确说周和日的边界不保证与 UTC 对齐。

图:GitHub REST API 文档「Get repository star history」段落。来源:GitHub 文档,headless Chromium 截图于 2026-10-15 16:17(北京时间)。

来源:GitHub 文档2026-10-15

图片预览

图:GitHub REST API 文档「Get repository star history」段落。来源:GitHub 文档,headless Chromium 截图于 2026-10-15 16:17(北京时间)。

新接口还有哪些限制?

只列原文和文档明确写的:

  • 按自然周切:分页单位是日历周,不是任意区间。
  • 边界不保证 UTC:文档原话 "Week and day boundaries are not guaranteed to align with UTC."(译:周和日的边界不保证与 UTC 对齐。)做精确到日的对比要留余量。
  • 特别密集的周仍然慢:时间范围限定了每次请求覆盖的周期,但不限定这段时间里有多少 star,"exceptionally dense weeks remain a scaling limitation"(译:异常密集的周仍是一个扩展性限制)。
  • 数据模型本身:原文说 star 模型记录的是「当前」的 star 集合,并非历史事件日志;博客在总结中也把 "removed stars"(译:被取消的 star)列为仍会影响返回结果的因素之一。具体怎么影响,原文没有展开,我这里不做推断。

顺带的收获:对比新旧路径时,GitHub 发现 GitHub.com 的 stargazers 页面也在实时数 star,改用缓存计数后,这个页面每周数以百计的慢查询降到了零。

图:每周慢或被终止的 stargazer 计数查询(按来源:REST API、GraphQL API、GitHub.com stargazers 页面),两条虚线分别为访问收紧和 stargazers 页面修复。来源:GitHub 工程博客,2026-10-14;页面内 HTML 图表元素截图于 2026-10-15 16:17(北京时间)。

来源:GitHub 工程博客2026-10-15

图片预览

图:每周慢或被终止的 stargazer 计数查询(按来源:REST API、GraphQL API、GitHub.com stargazers 页面),两条虚线分别为访问收紧和 stargazers 页面修复。来源:GitHub 工程博客,2026-10-14;页面内 HTML 图表元素截图于 2026-10-15 16:17(北京时间)。

独立开发者怎么用 GitHub Star History API 画自己项目的曲线?

我在 2026-10-15 16:16(北京时间)用 curl 无认证调用了 model-usage 的 stargazers history endpoint:

  • 第 1 页返回 HTTP 200,30 周;Link 头给出 rel="next" 和 rel="last"(共 3 页)。注意 Link 里的地址是 /repositories/{id}/... 形式,直接跟着走即可。
  • 3 页共 64 周,最早一周从 2025-07-27 开始(按 UTC 解读;仓库创建时间为 2025-07-27T16:10:46Z)。
  • 64 周的 total 加起来是 21;同时调用 GET /repos/evanlong-me/model-usage/stargazers/count 返回 {"count": 21},仓库接口的 stargazers_count 也是 21,三者一致。
  • 无认证限流为每小时 60 次(响应头 x-ratelimit-limit: 60)。

也就是说,一个创建一年多的小项目,3 次请求就拿到了完整曲线。

图:curl 无认证调用 model-usage 的 star history 接口,响应头与 JSON 片段。实测于 2026-10-15 16:16(北京时间)。由响应内容经 PIL 与 DejaVu Sans Mono 渲染。

来源:GitHub API 实测2026-10-15

图片预览

图:curl 无认证调用 model-usage 的 star history 接口,响应头与 JSON 片段。实测于 2026-10-15 16:16(北京时间)。由响应内容经 PIL 与 DejaVu Sans Mono 渲染。

图:evanlong-me/model-usage 累计 star 曲线,数据来自 GET /repos/evanlong-me/model-usage/stargazers/history(3 页、64 周、无认证),抓取于 2026-10-15 16:16:34(北京时间)。由接口数据经 matplotlib 绘制。

来源:GitHub API 实测2026-10-15

图片预览

图:evanlong-me/model-usage 累计 star 曲线,数据来自 GET /repos/evanlong-me/model-usage/stargazers/history(3 页、64 周、无认证),抓取于 2026-10-15 16:16:34(北京时间)。由接口数据经 matplotlib 绘制。

最小可用脚本:

shell
curl -sS -i -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/OWNER/REPO/stargazers/history?per_page=30&page=1"
python
import json, re, urllib.request, datetime as dt
import matplotlib.pyplot as plt

url = "https://api.github.com/repos/OWNER/REPO/stargazers/history?per_page=30"
weeks = []
while url:
    req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json"})
    with urllib.request.urlopen(req) as r:
        weeks += json.load(r)
        m = re.search(r'<([^>]+)>;\s*rel="next"', r.headers.get("Link", ""))
        url = m.group(1) if m else None

weeks.sort(key=lambda w: w["week"])      # 接口最近一周在前,这里改为正序
xs, ys, total = [], [], 0
for w in weeks:
    for i, n in enumerate(w["days"]):    # days 从周日开始
        total += n
        xs.append(dt.datetime.fromtimestamp(w["week"], dt.timezone.utc) + dt.timedelta(days=i))
        ys.append(total)

plt.step(xs, ys, where="post"); plt.ylabel("cumulative stars")
plt.savefig("star-history.png", dpi=150)

两点提醒:total 累加得到的是这条接口视角下的曲线,建议像我一样用 /stargazers/count 对一下总数;page 上限是 100、每页 30 周,按文档换算大约能覆盖 3,000 周,对绝大多数项目够用。

自己做 API 时,这次改造能借鉴什么?

GitHub 在文末总结了四条,我挑两条落到独立开发者的日常:

只返回用例需要的。 原文:"Return only what the use case needs. Anything more is data someone else can misuse."(译:只返回用例需要的东西,多出来的都是别人可能滥用的数据。)落地办法:设计接口前先写下「调用方拿到数据后要做什么」。如果答案是「画曲线」「做排行」,就返回聚合结果,而不是带身份的明细列表。明细列表一旦公开,就会被拿去做你没想到的事。

先缩范围,再聚合。 原文:"A smaller response does not always mean a cheaper query."(译:响应更小并不总意味着查询更便宜。)落地办法:

  1. 分页参数换算成索引上的区间条件(时间、ID 范围),放在 WHERE 里,而不是在聚合后用 OFFSET/LIMIT 截断。
  2. 每次请求的工作量要有上界(GitHub 选的是「最多 30 周」),并且在文档里讲清楚它管不住什么(比如密集周)。
  3. 「总数」这类会被高频访问的值,用缓存计数,别在每个请求里现数,GitHub 自己的 stargazers 页面就是这么修好的。
  4. 把边界和取舍写进契约:自然周、非 UTC 边界、最大页数,这些也是 API 的一部分。

对我这种做开源工具的独立开发者来说,这次改动的直接好处是:不用申请权限、不用 token、几次请求就能拿到自己项目的完整 star 曲线,发版前后的变化一眼可见。

来源与方法

  • GitHub 工程博客:Taylor Cuilty & Sarita Iyer,《From 2,500 requests to 24: Rebuilding star history without the stargazers》,页面日期 2026-10-14。https://github.blog/engineering/architecture-optimization/from-2500-requests-to-24-rebuilding-star-history-without-the-stargazers/
  • GitHub 更新日志:《New API endpoint provides privacy-safe star history data》,2026-09-04。https://github.blog/changelog/2026-09-04-new-api-endpoint-provides-privacy-safe-star-history-data/
  • GitHub REST API 文档「Get repository star history」,2026-10-15 访问。https://docs.github.com/en/rest/activity/starring
  • 项目仓库:https://github.com/evanlong-me/model-usage

方法说明:实测于 2026-10-15 16:16:34(北京时间,UTC+8),所有调用均未带认证,请求头 Accept: application/vnd.github+json;按 Link 头依次请求第 1–3 页,并调用 /stargazers/count 与 GET /repos/evanlong-me/model-usage 交叉核对。累计曲线由各周 days 数组按周日起始逐日累加,时间轴按 UTC 解读周时间戳(文档提示边界不保证 UTC)。博客中的 react 数字、查询统计均引用原文,我没有也无法独立复现。

继续读

更多文章
Scion 给小团队的改法开发实践2026-10-15
一次文章目录迁移的 SEO 验收开发实践2026-10-09
X 推荐算法代码解读AI 工具2026-10-15