用Python构建GitHub风格阅读热力图:从数据到自动更新
之前在整理个人阅读记录时,总觉得白纸黑字的笔记缺乏直观反馈。每天读了多少、连续打卡多少天、哪几周明显松懈,这些信息很难一眼看清。后来看到 GitHub 的贡献图(绿点矩阵),突然觉得这种“按日期上色”的方式非常适合做阅读统计。于是我用 Python 脚本把每天的阅读时长转成一张类似 GitHub Contribution Graph 的热力图,再配合 GitHub Actions 自动更新,效果比预想中好很多。本文会把整套做法完整拆开,从原理、数据集设计、SVG 生成,到定时任务和页面展示,逐步演示一个可复用的 GitHub Heatmap for Reading 项目。
无论你是想给读书笔记加一个可视化面板,还是想学习如何解析 GitHub 贡献图数据、如何用 GitHub Actions 做定时任务,这篇文章都值得收藏。
1. 背景与核心概念
1.1 什么是 GitHub 贡献热力图
GitHub 个人主页上的 contribution graph 是一个以日历形式展示提交记录的热力图。它按周排列,每列代表一周,每行代表一周中的某一天,格子颜色越深表示当天提交越多。这个设计最初只是让开发者直观看到自己的代码活跃度,后来逐渐演变成了一种“打卡可视化”的通用范式。
GitHub Heatmap for Reading 就是把这种热力图思想迁移到阅读场景:横轴仍然按时间推进,纵轴仍然是星期几,但每个格子的数据不再是 commit 次数,而是当天的阅读分钟数。颜色深浅对应阅读时长,连续深色的区域就是阅读状态最稳定的阶段。
这种可视化的价值在于:
- 把抽象的阅读习惯变成可观测的数据。
- 通过颜色分布快速发现“哪些周比较自律,哪些周完全放空”。
- 适合放入个人博客、GitHub 首页 README 或 Notion 类页面。
1.2 阅读记录为什么要用热力图
传统阅读记录通常是一个表格,例如“日期 + 书名 + 章节 + 阅读时长”。表格的问题在于不方便整体观察趋势。你很难一眼看出上周、上上月、去年同期的状态差异。
热力图则把时间维度压缩到一张宽图中,让趋势变得非常直观。比如 6 月连续 20 天绿色块,8 月却一大片空白,说明假期阅读中断严重。这种视觉冲击比文字记录更有提醒价值。
此外,热力图和 GitHub 的“绿点矩阵”天然兼容。如果你的博客或个人主页嵌入了 GitHub 热门图,读者已经养成“颜色越深 = 越活跃”的认知,阅读热力图几乎不需要额外解释成本。
1.3 相似概念区分
阅读热力图和下面几种东西容易混淆:
| 名称 | 数据来源 | 展示形式 | 用途 |
|---|---|---|---|
| GitHub 贡献图 | GitHub 提交记录 | 绿点矩阵 | 展示代码活跃度 |
| Reading Heatmap | 自定义阅读数据 | 彩色点矩阵 | 展示阅读习惯 |
| 打卡日历 | 任意打卡数据 | 日历打勾 | 记录是否完成 |
| 年度统计图表 | 书籍数量、时长 | 柱状图、环形图 | 总结年度成果 |
阅读热力图更强调“按日连续展示”,而不是汇总统计。如果你只需要年度总时长,用一个饼图或柱状图会更合适;如果你关心“每天是否有规律的阅读行为”,热力图是最优选择。
2. 环境准备与任务拆解
2.1 项目目标与功能范围
本文要构建一个个人阅读热力图系统,核心功能如下:
- 用一个
data.json文件存储每天的阅读分钟数。 - 用 Python 脚本读取数据,生成一张 SVG 格式的年度热力图。
- 用 GitHub Actions 定时执行脚本并自动提交生成结果。
- 通过 GitHub Pages 展示最终效果。
- 可选:通过 GitHub GraphQL API 拉取真实提交记录,生成代码贡献热力图。
整个项目不需要数据库,不依赖第三方云服务,只需要 GitHub 仓库即可完成闭环。
2.2 开发环境准备
建议使用以下环境,但版本可以根据本地实际情况调整:
- 操作系统:Windows / macOS / Linux 均可。
- Python:3.9 及以上。
- Git:任意较新版本。
- 编辑工具:VS Code 或任意文本编辑器。
- GitHub 账号:用于创建仓库、配置 Actions、开启 Pages。
Python 依赖只需要标准库json、datetime、pathlib,所以不需要主动安装第三方包。只有拉取 GitHub API 时才需要requests,这个会在后面的可选示例中单独说明。
检查 Python 是否安装:
python --version检查 Git 是否安装:
git --version2.3 项目目录设计
为了方便维护,建议按下面的目录组织项目:
heatmap-for-reading/ ├── .github/ │ └── workflows/ │ └── update-heatmap.yml # GitHub Actions 定时任务 ├── data/ │ └── reading.json # 阅读记录数据 ├── output/ │ └── reading-heatmap.svg # 生成的 SVG 热力图 ├── scripts/ │ └── generate_heatmap.py # 热力图生成脚本 ├── index.html # GitHub Pages 展示页面 ├── .gitignore └── README.md目录结构不是强制要求,但把脚本、数据、产物分开,后续维护会轻松很多。
3. 核心知识点拆解
在写完整脚本之前,有几个关键技术点需要先理解:日期偏移计算、SVG 网格布局、颜色分级、数据文件格式。这些概念搞明白之后,代码会非常容易读。
3.1 日期偏移与热力图时间轴
GitHub 贡献图的时间轴并不是从 1 月 1 日到 12 月 31 日,而是从今天往前推若干周。常见做法是让整个热力图覆盖最近 52 周或 53 周,使最后一列正好是当前周。
SVG 中的每个格子按“列 + 行”定位:
- 列索引从 0 到 52,共 53 列。
- 行索引从 0 到 6,对应周日到周六。
- 第一列前面的空白格子,用来补齐该周第一天不是周日的情况。
所以生成热力图要先计算“起始日期”,也就是今天往前推 52 周再加偏移量,确保起始日是一个周日。
核心算法如下:
from datetime import datetime, timedelta today = datetime.now().date() # 当前日期是星期几,周一=0 ... 周日=6 weekday = today.weekday() # 本周周日(假设周日是一周的第一天): # 如果今天是周一,需要往前推 1 天到周日 # 如果今天是周日,weekday() 为 6,需要往前推 0 天 offset_to_sunday = (weekday + 1) % 7 this_sunday = today - timedelta(days=offset_to_sunday) start_date = this_sunday - timedelta(weeks=52)这样start_date就是热力图第一列的周日。之后每个格子的日期都可以根据start_date + timedelta(days=7 * col + row)计算。
3.2 SVG 网格布局原理
SVG 支持矩形元素,一个热力图本质上就是 53×7 个<rect>小矩形排成网格。关键参数:
- 每个格子宽 11px,高 11px。
- 格子间距 3px。
- 列与列之间再加 3px 的列间距,模仿 GitHub 的风格。
- 整体画布宽度约
53 * (11 + 3) + 40像素,高度约7 * (11 + 3) + 60像素。
单个格子的 x 坐标:
x = 40 + col * (11 + 3)y 坐标:
y = 20 + row * (11 + 3)左侧留 40px 是为了显示“Mon”“Wed”“Fri”等星期缩写,顶部留 20px 可以放月份缩写。
3.3 颜色等级映射
热力图的颜色不能随意变化,需要定义若干等级。以阅读分钟数为例,可以分成 5 个等级:
| 等级 | 阅读时长 | 颜色 |
|---|---|---|
| 0 | 0 分钟 | #ebedf0(浅灰) |
| 1 | 1~30 分钟 | #dcf8e4 |
| 2 | 31~60 分钟 | #9be9a8 |
| 3 | 61~120 分钟 | #40c463 |
| 4 | 120 分钟以上 | #1d7a35 |
如果希望颜色统一使用 GitHub 风格,可以直接使用 GitHub 的#ebedf0、#9be9a8、#40c463、#30a14e、#216e39这一组色值。
在脚本中用函数实现等级计算,而不是把颜色写死在数据里:
def get_color(minutes): if minutes <= 0: return "#ebedf0" if minutes <= 30: return "#dcf8e4" if minutes <= 60: return "#9be9a8" if minutes <= 120: return "#40c463" return "#1d7a35"3.4 数据集格式设计
阅读热力图的数据文件可以很简单,只存日期和分钟数:
{ "reading_minutes": { "2025-01-01": 45, "2025-01-02": 30, "2025-01-03": 90 }, "books": { "2025-01-01": ["《深入理解计算机系统》", "第 3 章"], "2025-01-03": ["《代码大全》", "第 11 章"] } }reading_minutes是热力图的核心数据,books是可选的补充信息,可以用来生成 Tooltip 或在页面下方展示阅读详情。
使用 JSON 而不是 CSV 的原因是:JSON 天然支持嵌套结构,后续要增加“笔记链接”“心情标记”等字段时不需要改解析代码。
4. 完整实战:从零构建 GitHub Heatmap for Reading
这一部分是全文重点。我会按顺序创建项目文件并解释每个文件的作用。
4.1 初始化项目与数据文件
先在任意目录创建项目文件夹:
mkdir heatmap-for-reading cd heatmap-for-reading创建目录结构:
mkdir -p data scripts output .github/workflows创建数据文件data/reading.json,先放几条示例数据:
{ "reading_minutes": { "2025-01-01": 45, "2025-01-02": 30, "2025-01-03": 90, "2025-01-04": 0, "2025-01-05": 120, "2025-01-06": 60 }, "books": { "2025-01-01": { "name": "《深入理解计算机系统》", "chapter": "第 3 章" }, "2025-01-02": { "name": "《代码大全》", "chapter": "第 11 章" } } }实际使用中,建议你每周或每天手动更新一下这个文件。也可以后面写一个 CLI 小工具,通过命令
python add_record.py "2025-01-07" 45 "书名" "章节"来追加数据。
4.2 编写热力图生成脚本
接下来写核心脚本scripts/generate_heatmap.py。这个脚本负责:
- 读取
data/reading.json。 - 计算最近 53 周的日期矩阵。
- 根据阅读分钟数映射颜色。
- 生成 SVG 字符串。
- 保存到
output/reading-heatmap.svg。
先看完整代码:
# 文件路径:scripts/generate_heatmap.py import json from datetime import datetime, timedelta from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent DATA_FILE = BASE_DIR / "data" / "reading.json" OUTPUT_FILE = BASE_DIR / "output" / "reading-heatmap.svg" # 颜色配置 COLOR_EMPTY = "#ebedf0" COLORS = ["#dcf8e4", "#9be9a8", "#40c463", "#1d7a35"] CELL_SIZE = 11 CELL_SPACING = 3 COL_SPACING = 3 LEFT_PADDING = 40 TOP_PADDING = 30 BOTTOM_PADDING = 20 def load_reading_data(): """从 JSON 文件读取阅读记录,返回一个 {date: minutes} 字典。""" with open(DATA_FILE, "r", encoding="utf-8") as f: data = json.load(f) return data.get("reading_minutes", {}) def get_color(minutes): """根据阅读时长返回对应颜色。""" if minutes <= 0: return COLOR_EMPTY if minutes <= 30: return COLORS[0] if minutes <= 60: return COLORS[1] if minutes <= 120: return COLORS[2] return COLORS[3] def get_month_labels(start_date, total_weeks=53): """生成月份标签,例如 Jan、Feb、Mar。""" labels = [] for i in range(total_weeks): d = start_date + timedelta(weeks=i) month = d.strftime("%b") if not labels or labels[-1]["month"] != month: labels.append({"col": i, "month": month}) return labels def generate_svg(reading_data): today = datetime.now().date() weekday = today.weekday() offset_to_sunday = (weekday + 1) % 7 this_sunday = today - timedelta(days=offset_to_sunday) start_date = this_sunday - timedelta(weeks=52) total_cols = 53 total_rows = 7 # SVG 画布尺寸 width = LEFT_PADDING + total_cols * (CELL_SIZE + CELL_SPACING) height = TOP_PADDING + total_rows * (CELL_SIZE + CELL_SPACING) + BOTTOM_PADDING svg_parts = [] svg_parts.append( f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" ' f'viewBox="0 0 {width} {height}" font-family="Arial, sans-serif">' ) # 背景 svg_parts.append( f'<rect x="0" y="0" width="{width}" height="{height}" fill="#ffffff"/>' ) # 月份标签 for label in get_month_labels(start_date, total_cols): x = LEFT_PADDING + label["col"] * (CELL_SIZE + CELL_SPACING) svg_parts.append( f'<text x="{x}" y="22" font-size="10" fill="#767676">' f'{label["month"]}</text>' ) # 星期标签 day_labels = ["", "Mon", "", "Wed", "", "Fri", ""] for row, label in enumerate(day_labels): y = TOP_PADDING + row * (CELL_SIZE + CELL_SPACING) + 9 svg_parts.append( f'<text x="2" y="{y}" font-size="9" fill="#767676">{label}</text>' ) # 绘制热力格子 for col in range(total_cols): for row in range(total_rows): current_date = start_date + timedelta(days=col * 7 + row) date_str = current_date.isoformat() minutes = reading_data.get(date_str, 0) color = get_color(minutes) x = LEFT_PADDING + col * (CELL_SIZE + CELL_SPACING) y = TOP_PADDING + row * (CELL_SIZE + CELL_SPACING) title = f"{date_str}: {minutes} 分钟" svg_parts.append( f'<rect x="{x}" y="{y}" width="{CELL_SIZE}" height="{CELL_SIZE}" ' f'rx="2" ry="2" fill="{color}">' f'<title>{title}</title>' f'</rect>' ) # 汇总信息 total_minutes = sum(reading_data.values()) active_days = len(reading_data) svg_parts.append( f'<text x="40" y="{height - 8}" font-size="11" fill="#333333">' f'总阅读时长:{total_minutes // 60} 小时 {total_minutes % 60} 分钟 | ' f'有记录天数:{active_days} 天' f'</text>' ) svg_parts.append("</svg>") return "\n".join(svg_parts) def main(): reading_data = load_reading_data() svg_content = generate_svg(reading_data) OUTPUT_FILE.parent.mkdir(exist_ok=True, parents=True) OUTPUT_FILE.write_text(svg_content, encoding="utf-8") print(f"热力图已生成:{OUTPUT_FILE}") print(f"共处理 {len(reading_data)} 天阅读记录") if __name__ == "__main__": main()这段代码里有几个值得说明的点:
start_date通过“本周周日往前推 52 周”计算,保证热力图最后一列是当前完整的一周。- 每个格子用
<rect>绘制,内部嵌套<title>用于鼠标悬停提示。 - 月份标签只在新月的第一列输出,避免重复。
- 底部汇总信息显示总阅读小时数和有记录天数。
4.3 运行脚本生成 SVG
在项目根目录执行:
python scripts/generate_heatmap.py预期输出:
热力图已生成:.../output/reading-heatmap.svg 共处理 6 天阅读记录生成的 SVG 可以直接用浏览器打开,也可以嵌入 HTML。
如果你用 VS Code,建议安装一个 SVG 预览插件,实时查看效果。下面是一个只用浏览器验证 SVG 是否正确的命令(Windows 下可用):
start output/reading-heatmap.svgmacOS 可以使用:
open output/reading-heatmap.svg4.4 自动更新:GitHub Actions 定时任务
手动运行脚本只能生成一次热力图。为了让热力图每天自动更新,可以借助 GitHub Actions 的定时任务。
创建文件.github/workflows/update-heatmap.yml:
# 文件路径:.github/workflows/update-heatmap.yml name: update-reading-heatmap on: schedule: - cron: "0 22 * * *" workflow_dispatch: permissions: contents: write jobs: update-heatmap: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Generate heatmap run: python scripts/generate_heatmap.py - name: Auto commit uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "chore: auto update reading heatmap" file_pattern: "output/reading-heatmap.svg"说明:
schedule中的 cron 表达式0 22 * * *表示每天 UTC 22:00 运行一次,对应北京时间的次日 06:00。workflow_dispatch允许你在 GitHub 网页上手动触发任务,方便测试。git-auto-commit-action会在有变更时自动提交并推送,不需要额外配置用户名和邮箱。
注意:GitHub Actions 的 cron 调度不保证精确到分钟,实际执行时间可能有几分钟到十几分钟的延迟,这是正常现象。
4.5 可选:使用 GitHub GraphQL API 拉取真实贡献数据
如果你不仅想记录阅读热力图,还想拉取自己真实的 GitHub 提交数据,生成代码贡献热力图,可以通过 GitHub GraphQL API v4 来实现。
GraphQL 查询贡献日历的核心代码如下:
query($username: String!) { user(login: $username) { contributionsCollection { contributionCalendar { weeks { contributionDays { date contributionCount } } } } } }使用 Python 调用:
# 需要安装 requests:pip install requests import requests API_URL = "https://api.github.com/graphql" TOKEN = "你的 Personal Access Token" USERNAME = "your-github-username" query = """ query($username: String!) { user(login: $username) { contributionsCollection { contributionCalendar { weeks { contributionDays { date contributionCount } } } } } } """ headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } response = requests.post( API_URL, json={"query": query, "variables": {"username": USERNAME}}, headers=headers, ) data = response.json()["data"]["user"]["contributionsCollection"]["contributionCalendar"] days_data = {} for week in data["weeks"]: for day in week["contributionDays"]: days_data[day["date"]] = day["contributionCount"] print(days_data)生成 Personal Access Token 时,不需要授予仓库读写权限,只要读取公开用户数据即可。更安全的做法是只给
read:user权限,并在 GitHub Actions Secrets 中保存 token。
如果你不想暴露自己的真实提交数据,也可以不执行这一节,继续使用手写的reading.json数据。两条路线可以并存:一个用于展示代码活跃度,一个用于展示阅读状态。
4.6 通过 GitHub Pages 展示热力图
打开 GitHub Pages 后,访问https://<你的用户名>.github.io/<仓库名>/即可看到聚合页面。要让页面展示热力图,需要先创建index.html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>我的阅读热力图</title> <style> body { font-family: "PingFang SC", "Microsoft YaHei", Arial, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; color: #24292f; } h1 { font-size: 24px; margin-bottom: 8px; } .description { color: #57606a; margin-bottom: 24px; } .heatmap-container { background: #ffffff; border: 1px solid #d0d7de; border-radius: 8px; padding: 16px; overflow-x: auto; } .heatmap-container svg { max-width: 100%; height: auto; } </style> </head> <body> <h1>阅读热力图</h1> <p class="description">颜色越深,代表当天阅读时间越长。</p> <div class="heatmap-container"> <img src="output/reading-heatmap.svg" alt="Reading Heatmap" /> </div> </body> </html>这里使用<img>标签引用 SVG 文件。如果你希望支持“鼠标悬停显示日期和时长”,就需要把 SVG 内容直接内嵌到 HTML 中,而不是通过<img>加载。我的示例脚本里已经给每个格子写了<title>,所以你可以选择直接输出index.html时把 SVG 内容内嵌进去。
如果你想做内嵌版本,可以在 Python 脚本的main()函数里同时生成一个index.html,把svg_content插入模板。这样 GitHub Pages 显示的就是完整交互版本。
4.7 完整运行闭环演示
假设你现在已经把这个项目推送到 GitHub 仓库,完整的工作流程如下:
- 每天阅读后,手动编辑
data/reading.json,添加当天的记录。 - 推送代码,或直接等待 GitHub Actions 每天 22:00 UTC 运行任务。
- Actions 执行
python scripts/generate_heatmap.py,生成新的 SVG。 git-auto-commit-action检测到 SVG 文件变化后,自动提交。- 浏览器访问 GitHub Pages 页面,看到最新的热力图。
如果你不想每天手动改 JSON,也可以写一个简单的 CLI 脚本,再配合手机或电脑端命令快速追加记录。下面是一个追加记录的命令:
python scripts/add_record.py "2025-01-07" 45 "《代码整洁之道》" "第 2 章"由于篇幅限制,这个附加脚本不展开,逻辑很简单:读取 JSON、更新对应日期、写回 JSON。
5. 常见问题与排查思路
5.1 常见报错表格
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 生成的 SVG 全是灰色 | reading.json中日期格式不对 | 确保日期格式为YYYY-MM-DD |
| 热力图只有最近几周有颜色 | start_date计算偏移错误 | 检查星期日偏移算法 |
| GitHub Actions 没有自动更新 | 仓库没有启用 Actions | 到仓库 Actions 页面查看失败日志 |
| git-auto-commit-action 报错 | 工作流权限不足 | 在工作流中添加permissions: contents: write |
| 浏览器中文显示为方块 | 字体未指定中文字体 | 在 SVG 根节点设置 font-family |
| 读取 JSON 报 UnicodeDecodeError | 文件编码不是 UTF-8 | 使用 UTF-8 保存 JSON 文件 |
5.2 脚本运行时报 FileNotFoundError
如果提示找不到data/reading.json,可能是工作目录问题。脚本中使用Path(__file__).resolve().parent.parent定位项目根目录,所以应该从项目根目录执行:
python scripts/generate_heatmap.py不要进入scripts目录后直接python generate_heatmap.py。
5.3 GitHub Actions 执行失败
最常见原因是上传代码时没有把.github/workflows目录一并推送。可以先在本地检查:
git status如果 workflow 文件没有进入暂存区,需要手动添加:
git add .github/workflows/update-heatmap.yml git commit -m "ci: add heatmap update workflow" git push origin main然后到 GitHub 仓库的 Actions 标签页查看运行记录,点击失败的 job 查看日志。
5.4 访问 GitHub 或拉取代码较慢
如果你在git push或git clone时遇到网络超时,通常和本地网络、DNS、代理配置有关。建议先确认自己的网络可以正常访问 github.com,再检查 Git 代理设置:
git config --global --list | grep -i proxy如果公司或校园网络提供合法的开发代理,可以在环境变量中临时配置 HTTPS 代理,但不要使用来源不明的加速工具。安全底线是只使用合规可信的网络通道。如果只是个人学习,直接把仓库推送到 GitHub 即可,不需要依赖额外服务。
6. 最佳实践与工程建议
6.1 数据文件是核心资产
reading.json是你整个热力图系统的数据核心。建议遵守以下原则:
- 每天只维护一个 JSON 文件,避免散落在多个文档中。
- 使用 Git 管理数据文件,这样能看到“哪一天补录了阅读记录”。
- 不要把
output/目录加入.gitignore,否则 Actions 生成的 SVG 无法被提交。正确做法是让 output 目录参与版本控制。 - 如果某天忘记记录,允许后补,但尽量在一周内补齐,避免数据失真。
6.2 Actions 定时任务要注意提交频率
热力图脚本如果每天都跑,那么即使数据没变,SVG 文件也会重新生成。虽然 git-auto-commit-action 会检测到文件无变化而跳过提交,但为了减少无效提交,可以在脚本中判断内容是否变化后再决定是否覆盖文件。
更稳妥的做法是:
- 在生成 SVG 前先读取旧文件内容。
- 如果新内容和旧内容完全一致,就不写文件。
- 否则再覆盖写入。
这样 Actions 的自动提交会非常干净,不会产生大量无意义的 commit。
6.3 前端展示优化
从工程角度看,SVG 本身已经足够展示热力图,但如果你希望页面更专业,可以做这些优化:
- 将 SVG 内嵌到 HTML 中,而不是用
<img>引用。 - 在 SVG 的
<title>中同时显示书名和章节。 - 在最下方增加按书籍分类的统计表。
- 使用 CSS 变量控制主题色,方便切换深色模式。
- 为移动端增加横向滚动容器,因为 53 列 SVG 在手机上显示会比较窄。
6.4 隐私与安全边界
阅读记录属于个人行为数据,虽然不像密码那么敏感,但也不建议公开展示太多细节。如果你打算把热力图放到公开 GitHub 仓库,建议:
- 只公开“每日阅读分钟数”,不要把书籍笔记、划线内容全部公开。
- 如果确实需要展示详细读书笔记,请单独使用私有仓库,只把聚合后的 SVG 输出到公开仓库。
- GitHub Personal Access Token 永远不要直接写在代码或 workflow 文件中,必须通过仓库的 Secrets 注入:
env: GH_TOKEN: ${{ secrets.GH_TOKEN }}6.5 扩展方向
这个项目有很强的扩展性,下面是几个值得继续尝试的方向:
| 扩展方向 | 实现思路 |
|---|---|
| 多维度热力图 | 在 JSON 中增加type字段,分别统计技术书、小说、论文 |
| 年度对比 | 生成多张热力图,用下拉框切换年份 |
| 自动记录 | 基于 Kindle 或微信读书的导出数据,自动解析阅读时间 |
| GitHub 首页嵌入 | 将 SVG 作为图片嵌入 README.md |
| 依赖其他数据源 | 支持 Notion API、WakaTime API 等 |
7. 总结与下一步
通过这篇文章,我们从零实现了一个 GitHub Heatmap for Reading 项目,核心内容覆盖了:
- 理解贡献热力图的原理和 SVG 网格布局。
- 设计
reading.json数据结构。 - 用 Python 生成仿 GitHub 风格的阅读热力图。
- 通过 GitHub Actions 实现每天自动更新。
- 使用 GitHub GraphQL API 拉取真实提交数据。
- 将热力图部署到 GitHub Pages。
整个项目的技术栈很轻,不依赖数据库,也不需要部署服务器,只要一个 GitHub 仓库就能跑通。你可以直接复制本文代码,把data/reading.json替换成自己的真实数据,然后推送仓库,就能拥有一张专属阅读热力图。
下一步建议先从手动维护数据开始,连续打卡一周后观察热力图的变化;等到数据量积累起来,再考虑接入 GitHub Actions 自动更新。如果你有 Kindle 或微信读书导出数据,也可以尝试写一个解析脚本,把真实阅读记录自动写入reading.json,这是一个很不错的练手项目。
动手写起来吧,把阅读这件小事变成一张持续变绿的地图。
