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

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Latest
Security Latest
Jina AI
Jina AI
爱范儿
爱范儿
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
GbyAI
GbyAI
N
News | PayPal Newsroom
Last Week in AI
Last Week in AI
V
Visual Studio Blog
SecWiki News
SecWiki News
H
Heimdal Security Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
腾讯CDC
L
LINUX DO - 热门话题
I
InfoQ
罗磊的独立博客
博客园_首页
Know Your Adversary
Know Your Adversary
T
The Exploit Database - CXSecurity.com
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
美团技术团队
IT之家
IT之家
AWS News Blog
AWS News Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
Help Net Security
Help Net Security
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
B
Blog RSS Feed
N
News and Events Feed by Topic
L
Lohrmann on Cybersecurity
Google Online Security Blog
Google Online Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
N
News and Events Feed by Topic
I
Intezer
Blog — PlanetScale
Blog — PlanetScale
云风的 BLOG
云风的 BLOG
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
F
Full Disclosure

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脚本 蓝帽杯 2022 Writeup 为你的网站添加 Do you like me 小组件 ISCC 2022 Writeup CISCN 2022 Writeup ISCC 2022 实战题 Writeup CTF线下赛AWD攻防总结 [I Am I 年度简报] – 2021 记一次 CNVD 通用型漏洞证书挖掘 Google Adsense 收款流程 使用 Digispark 开发板制作 BadUSB 在 Vue 中使用 Axios 获取钉钉群直播回放的 M3U8 地址 Flask 框架学习记录 关于 Ten·API 防火墙的配置 关于最近 关于 Burp Suite 调教这档事 ISCC 2021 Writeup 记一次 CTF 环境和动态独立靶机部署 各大平台图集解析思路 情话总雷同,恨意千万种 HackThisSite Basic Writeup [I Am I 年度简报] - 2020 Kali 设置中文语言和更换镜像源 使用 Python 下载哔哩哔哩视频 PHP使用 CURL 发送网络请求 PHP蓝奏云直链解析源码 从0开始写一个短视频去水印接口 Windows+Ubuntu 双系统之美化 GRUB Windows+Ubuntu 双系统安装 树莓派安装 Aria2 实现24小时不间断的下载机 抖音无水印解析最新PHP源码 API-Admin Ten·API管理后台 树莓派安装 DLNA 实现流媒体服务器 [I Am I 年度简报] - 2019 Hello Hexo Goindex 将 Google Drive 打造成网盘 自用博客评论邮件通知美化模板 使用 IFTTT 长久保留 Google Voice 号码 Telegram MTProxy 代理一键安装脚本 三分钟学会搭建我的世界基岩版服务器 PHP跳转QQ聊天窗口源码 推荐几款开源HTML5(Web)框架 谷歌新出浏览器 Chromium 可以直接翻墙 Live2D!为你的网站添加看板娘 为你的网站添加Gittalk评论 网站数据离奇丢失...... Lsky Pro(兰空图床)—又一款单纯的图床程序 LOL明天解封ヾ(◍°∇°◍)ノ゙ 唔~本站受到DDOS攻击 [I Am I 年度简报] – 2018 死肥宅也要谈恋爱之早安晚安自动化 1024,Hello,world! 宝塔面板 Bt.cn 专业版破解教程 Uptime >>16 years 震惊!坐在家里竟然可以日入百万 免费获取一年的 .ooo 域名 PHP 调用 新浪API 生成短网址 思杰马克丁成 Adobe 中国授权经销商 畅言商业广告上线-去除畅言评论广告 使用网易云音乐官方接口解析VIP音乐 PHP获取QQ昵称和头像API 坦白说查发送人QQ新方法(已失效) 日常水一波 通过微博图片地址溯源上传者 WordPress 评论夜间自动改为必须审核 十步叫你如何无损修复硬盘锁(mbr病毒) [I Am I 年度简报] – 2017 密码破解与心理学 网页屏蔽各种按键的代码分享 网络安全技术专业术语
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