惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
罗磊的独立博客
云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
T
The Blog of Author Tim Ferriss
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
博客园 - Franky
F
Fortinet All Blogs
D
Docker
博客园 - 司徒正美
腾讯CDC
Recent Announcements
Recent Announcements
The Cloudflare Blog
B
Blog RSS Feed
GbyAI
GbyAI
T
Tailwind CSS Blog
雷峰网
雷峰网
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)
阮一峰的网络日志
阮一峰的网络日志

Hi, I Am I

[I Am I 年度简报] — 不知终日梦为鱼 初探 ESP32-CAM QQ 聊天记录 MHT 文件转 HTML [I Am I 年度简报] - 草木本无意,荣枯自有时。 Hexo 中实现 Live Photos 支持 写在当下 NKCTF 2024 1z_F0r3ns1c5 Writeup 春秋杯冬季赛 2023 Writeup [I Am I 年度简报] - 2023 某内网渗透内部赛 Writeup 强网拟态 2023 Writeup Github Actions 自动化部署 Hexo 浅析CobaltStrike流量解密 陇剑杯 2023 Writeup CTF线下赛AWDP总结 ISCC 2023 Writeup ISCC 2023 实战题 Writeup CISCN 2023 Writeup 福建闽盾杯网络空间安全大赛 2023 Writeup 天一永安杯宁波市网络安全大赛 2023 Writeup 贵阳大数据及网络安全精英对抗赛 2023 Writeup 红明谷杯 2023 Writeup Confetti 带来有仪式感的鼓励 记一次 JS 逆向密码加密 [I Am I 年度简报] – 2022 PHP 读取 Excel 文件内容并写入数据库 从0开始的 MoeCTF 开发之路 观安杯 2022 Writeup 利用微信服务号实现早安自动化 Cloudflare批量拉黑IP脚本
Astro 构建阶段自动同步 GitHub 统计数据
2026-05-31 · via Hi, I Am I

写在前面

目前博客首页 About 区域展示了一些 GitHub 统计信息,例如 Followers、Stars 和 Commit 贡献数

image-20260601191901802

这些数据目前直接写在 config.yaml 中维护,每当数据发生变化时,都需要手动修改配置并重新部署

考虑到博客本身已经通过 GitHub Actions 自动构建,博主打算在 Astro 构建阶段直接调用 GitHub API 拉取最新数据,并将结果编译进最终生成的静态页面

获取 Followers

Followers 的获取最为简单,可以直接通过 Github 用户接口获取

GET https://api.github.com/users/{username}

返回结果中包含当前账号粉丝总量:

{
  "followers": 123
}

获取 Stars

Stars 的获取稍微麻烦一些,GitHub 并没有提供用户总 Star 数接口

因此只能遍历当前用户的所有仓库,并累加每个仓库的 Star

GET https://api.github.com/users/{username}/repos

接口返回的数据中 stargazers_count 即为单个仓库的 Star 数量

[
  {
    "name": "likeMe",
    "stargazers_count": 15
  },
  {
    "name": "MoeGate",
    "stargazers_count": 8
  }
]

该接口默认单次仅返回 30 条数据,我们可以通过请求参数进一步调用

  • per_page=100:将单页数据量提升至接口允许的最大值
  • type=owner:仅筛选当前用户创建的仓库,排除 Fork 仓库

由于 Fork 仓库的 Star 数通常不属于个人项目成果,因此这里通过 type=owner 仅统计当前用户拥有的仓库。

GET https://api.github.com/users/{username}/repos?per_page=100&type=owner

遍历仓库列表并累加 stargazers_count 即可得到当前页的 Star 总数

不过这里还有一个问题,虽然单页100 条数据已经是 GitHub API 允许的最大值,但如果用户拥有超过 100 个仓库,那么一次请求仍然无法获取全部数据。

翻阅官方文档发现,当存在多页数据时,GitHub 会在响应头中返回一个 Link 字段

<https://api.github.com/user/repos?page=2>; rel="next",
<https://api.github.com/user/repos?page=3>; rel="last"

image-20260601153941414

因此我们可以通过正则解析 Link Header 获取最后一页页码

[&?]page=(\d+)>;\s*rel="last"

拿到总页数后,使用并发请求批量拉取剩余分页数据,最后整合所有分页数据,汇总后即可得到总 Star 数

获取 Commits

原本博主以为 Commit 数获取会像前两个数据获取一样简单,结果翻遍 GitHub 官方文档后才发现,并不存在类似的接口

网上较为通用的方案是使用搜索接口,通过返回结果中的 total_count 字段统计提交次数

GET https://api.github.com/search/commits?q=author:{username}

但实际测试后发现,该结果与个人主页显示的贡献数据并不一致

一方面,该接口不会统计私有仓库贡献;另一方面,GitHub 官方文档中也明确说明

Find commits via various criteria on the default branch (usually master)

也就是说,该接口主要基于默认分支进行搜索,很难作为个人总提交次数的准确统计依据

博主甚至考虑直接抓取 GitHub Profile 页面中的绿墙数据,不过这种方案需要解析 HTML 页面,长期维护成本过高,最终放弃该思路

image-20260601155335700

既然 REST API 无法提供准确结果,那么只能继续寻找 GitHub 内部用于生成贡献图的数据来源

后续在研究 GitHub GraphQL 文档时,最终找到了 ContributionsCollection 对象

注意:调用 GitHub GraphQL 接口需要配置个人 Token;若需统计私有仓库提交,Token 需开启 repo 读取权限。

该对象中的 totalCommitContributions 字段,可以精准统计指定时间段内的所有提交贡献,统计口径和 GitHub 贡献绿墙完全一致

query {
  user(login: "5ime") {
    contributionsCollection(
      from: "2026-01-01T00:00:00Z"
      to: "2026-12-31T23:59:59Z"
    ) {
      totalCommitContributions
    }
  }
}

返回结果如下:

{
  "data": {
    "user": {
      "contributionsCollection": {
        "totalCommitContributions": 123
      }
    }
  }
}

不过 totalCommitContributions 仅支持时间区间统计,而我们需要获取账号从注册至今的累计提交数,因此需要按年份拆分统计再汇总

此前获取 Followers 时调用的的用户接口中本身就包含账号创建时间

{
  "followers": 422,
  "created_at": "2016-03-28T07:21:11Z"
}

我们可以直接将账号创建年份作为统计起点

const startYear = new Date(user.created_at).getFullYear();

根据起始年份与当前年份,动态生成需要统计的年份列表

function getCommitYears(startYear: number): number[] {
	const endYear = new Date().getFullYear();

	return Array.from(
		{ length: endYear - startYear + 1 },
		(_, i) => startYear + i,
	);
}

如果按常规思路,每一年单独发起一次 GraphQL 请求,账号注册年限越久,请求次数就越多,写法冗余且效率偏低

好在 GraphQL 支持字段别名,我们可以在单次请求中拼接多个时间段查询,一次性获取全年度数据,极大简化逻辑、减少请求

动态拼接后的多年份查询示例如下

query($login: String!) {
  user(login: $login) {
    y2020: contributionsCollection(
      from: "2020-01-01T00:00:00Z"
      to: "2020-12-31T23:59:59Z"
    ) {
      totalCommitContributions
    }

    y2021: contributionsCollection(
      from: "2021-01-01T00:00:00Z"
      to: "2021-12-31T23:59:59Z"
    ) {
      totalCommitContributions
    }

    y2022: contributionsCollection(
      from: "2022-01-01T00:00:00Z"
      to: "2022-12-31T23:59:59Z"
    ) {
      totalCommitContributions
    }
  }
}

对于绝大多数个人账号,仅需一次请求即可完成全年度数据拉取

最后将每一年的 totalCommitContributions 累加,就能得到账号累计 Commit 贡献数

接入 Astro

完成数据获取后,剩下的工作就比较简单了

博主直接将 GitHub 用户名以及失败时的兜底数据放在 config.yaml 中维护

github:
  username: "5ime"
  fallbacks:
    stars: "4,000"
    commits: "1,000"
    followers: "400"

由于 Astro Frontmatter 会在构建阶段执行,因此这里的 GitHub API 请求仅发生在构建过程中,不会暴露到用户浏览器中

---
import { loadHomeConfig } from '@/utils/config';
import { resolveGitHubStats } from '@/utils/github/github-stats';

const config = loadHomeConfig();
const stats = await resolveGitHubStats(config.content.github);
---

随后将 stats 传递给 About 组件渲染即可

这样一来,每次 GitHub Actions 触发构建时都会自动同步最新统计数据

如果 GitHub API 请求失败,则回退到 config.yaml 中配置的 fallback 值,避免因网络异常或接口限流导致构建中断

写在后面

至此,Followers、Stars 以及 Commit 贡献数都已经能够在 Astro 构建阶段自动获取

对于大多数个人账号而言,整个统计过程通常仅需 2~3 次 API 请求即可完成

并且所有数据都会被直接编译进静态页面,不会增加运行时请求开销~

完整 Github 用户统计信息代码详见 5ime/github-stats