Astro 构建阶段自动同步 GitHub 统计数据
写在前面
目前博客首页 About 区域展示了一些 GitHub 统计信息,例如 Followers、Stars 和 Commit 贡献数

这些数据目前直接写在 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"

因此我们可以通过正则解析 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 页面,长期维护成本过高,最终放弃该思路

既然 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