起因
最近抽空捣腾 GitHub 个人主页,发现不少人挂着 Spotify 的听歌排行组件,类似 spotify-recently-played-readme 这种。效果挺好看的,一张卡片展示最近在听什么歌,给主页多了点个人气息。
但是------我用的是网易云啊。
搜了一圈,GitHub 上没有现成的网易云版本。行吧,自己写一个。
效果展示
先看成品。在你的 GitHub README 里加一行:
markdown

就能得到这样一张卡片:

带封面图、歌名、艺术家、播放次数,还有逐行淡入动画。
10 款主题
做了 10 套配色,5 暗色 + 5 亮色,基本覆盖了程序员群体里最受欢迎的几种风格:
暗色: dark / nord / dracula / monokai / ocean
亮色: light / sakura / mint / sunset / sky
几个我个人比较喜欢的:




切换主题只需要改 URL 参数 theme=dracula,零成本。
技术实现
简单聊聊怎么做的,整体架构不复杂:
css
用户 README <img> 请求
→ Vercel Serverless Function
→ 调网易云 API 拿听歌数据 + 用户信息
→ 并发请求所有封面图 → 转 Base64 内嵌
→ 拼装 SVG 返回
为什么用 SVG 而不是截图?
GitHub README 里的 <img> 标签只能加载图片,不能跑 JS。所以需要服务端直接返回一张图。
选 SVG 而不是 PNG 是因为:
- 矢量无损,Retina 屏幕不糊
- 体积小,文本 + 路径比光栅图小很多
- 支持动画 ,SVG 原生
<animate>可以做淡入效果,不需要 JS
封面图怎么处理?
GitHub 的 CSP 策略不允许 SVG 内部引用外部图片(<image href="https://..."> 会被拦)。解决方案是在服务端把封面图 fetch 下来转成 Base64,直接内嵌到 SVG 里:
typescript
export async function imageToBase64(url: string): Promise<string> {
const res = await fetch(url + '?param=80y80', { headers: HEADERS })
const buffer = await res.arrayBuffer()
const base64 = Buffer.from(buffer).toString('base64')
return `data:image/jpeg;base64,${base64}`
}
?param=80y80 是网易云 CDN 的缩略图参数,只请求 80x80 的小图,节省带宽。
网易云 API
直接调的网易云官方 Web API,不依赖第三方库:
/api/v1/play/record?uid=xxx&type=1--- 获取听歌排行(type=1 周榜,type=0 总榜)/api/v1/user/detail/xxx--- 获取用户昵称
需要带上 User-Agent 和 Referer 头伪装浏览器请求,否则会被拦。
缓存策略
typescript
res.setHeader('Cache-Control', 's-maxage=300, stale-while-revalidate=600')
Vercel CDN 缓存 5 分钟,过期后允许 stale 响应 10 分钟内异步刷新。既保证数据相对实时,又不会对网易云 API 造成过大压力。
所有参数
| 参数 | 说明 | 默认值 | 可选值 |
|---|---|---|---|
id |
必填 网易云用户 ID | --- | --- |
type |
时间范围 | week |
week / all |
count |
歌曲数量 | 5 |
1 -- 10 |
theme |
主题 | dark |
上面 10 种 |
show_rank |
显示排名 | true |
true / false |
怎么用
1. 找到你的网易云用户 ID
打开网易云音乐网页版 → 个人主页 → URL 里那串数字就是:
2. 确保听歌排行是公开的
网易云 App → 设置 → 隐私设置 → 把「我的听歌排行」设为公开。
3. 加到你的 GitHub README
markdown

搞定。
4.(可选)自行部署
不想用我的服务也可以自己部署,点一下就行:

最后
项目开源,MIT 协议:github.com/HappySean28...
Spotify 用户早就有这个了,网易云用户不应该没有。中国程序员的听歌品味,也该被看见。
欢迎 Star、提 Issue、PR。如果有想要的主题配色也可以在 Issue 里提。