尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗网易云音乐个人听歌统计:通过最近播放事件和每日累计快照生成本地记录,提供歌曲、歌手、专辑排行与播放趋势。
网易云的听歌排行接口提供 allData 累计计数和 weekData 最近一周计数,没有完整的逐次播放记录。music-record 默认每 60 秒轮询最近播放,按歌曲、播放时间和来源类型去重保存事件;每天采集累计快照,再将累计差分与事件计数按每首歌、每天取较大值,保存到本地 SQLite。
项目有两个直接运行时依赖(Fastify + Luxon);数据库、测试、.env 加载和 fetch 使用 Node.js 内置能力,无需编译。
≥ X 标注;趋势区分数据未知与已确认的零播放。flowchart LR
A["allData 每日快照<br/>snapshot / snapshot_item"] -->|累计差分| D["daily_play<br/>每歌每天取较大值"]
R["最近播放轮询"] --> E["recent_play_event"]
E -->|按日计数| D
E -->|按本地小时汇总| H["小时活跃度"]
D --> Q["概览、排行、趋势与日级分布"]
W["weekData 每日采集"] --> S["snapshot_week_item<br/>原始计数,不参与日级差分"]
T["今日足迹"] --> U["today_listen_snapshot / today_listen_item<br/>辅助记录"]
allData 存在传播延迟,因此默认在 04:00 采集,并以 ATTRIBUTION=prev 将差分归到快照日的前一天。若在当天 23:55 采集,可设为 same。但是,ATTRIBUTION 只决定差分增量归到哪一天,无法还原每次播放的实际时间;最近播放事件则按自身时间戳和统计时区归属。
[!NOTE] 第一张累计快照只建立差分基线。但是,首次采集也会读取最近播放;如果接口返回带有效时间戳的歌曲,事件计数可以立即产生统计。
最近播放接口仅提供每首歌最近一次播放的信息,因此轮询之间的重复播放仍可能遗漏。累计差分只能覆盖榜单中可见的歌曲,因此开始采集之前的逐日历史无法由这些数据恢复。
--experimental-sqlite,而 node:sqlite 自 22.13.0 起才默认启用;详见 Node.js 文档。git clone https://github.com/wjiajian/music_record.git
cd music_record
npm install
复制环境变量模板并填入你的信息:
cp .env.example .env
填写以下两项:
NETEASE_UID=你的网易云UID
NETEASE_COOKIE=MUSIC_U=xxxxxxxx...
[!IMPORTANT]
- 在网易云客户端将听歌排行设为「公开」,以便采集排行数据。
NETEASE_COOKIE填写登录后的MUSIC_U,可从网页版浏览器开发者工具获取。Cookie 缺失或失效时,可能无法读取有效计数或登录态数据。
npm run migrate
npm run collect
该命令采集累计快照、最近播放、今日足迹、歌单及曲目。首次累计快照用于建立基线;最近播放中的有效事件可以立即计入统计。
npm run api
访问 http://127.0.0.1:3000 查看前端,或访问 http://127.0.0.1:3000/api 查看端点列表。
[!TIP] API 启动后默认立即采集一次最近播放,然后按配置间隔继续轮询;每日完整采集在
COLLECT_AT指定的时间执行。因此,日常只需保持npm run api运行。COLLECT_IN_API=0和REALTIME_IN_API=0分别关闭这两项任务。
配置通过环境变量提供,src/config.js 启动时自动加载项目根目录的 .env:
| 变量 | 默认值 | 说明 |
|---|---|---|
NETEASE_UID | (必填) | 你的网易云 UID,听歌排行需设为公开 |
NETEASE_COOKIE | (必填) | 登录 Cookie,只需 MUSIC_U 一项 |
TZ_NAME | Asia/Shanghai | 统计时区;趋势周桶采用 ISO 周 |
ATTRIBUTION | prev | 差分归属:prev=归前一天 / same=归当天 |
DB_PATH | data/music.db | SQLite 数据库路径 |
COVER_CACHE_DIR | 数据库目录下的 cover-cache | 封面磁盘缓存目录;默认是 data/cover-cache |
COVER_CACHE_TTL_MS | 2592000000(30 天) | 服务端缓存有效期;最小 1 小时,过期后在下一次请求时刷新 |
COVER_CACHE_MAX_BYTES | 3145728(3 MiB) | 单张封面大小上限;最小 64 KiB |
API_PORT | 3000 | API 服务端口 |
API_HOST | 127.0.0.1 | 绑定地址 |
COLLECT_IN_API | 1 | 是否在 API 进程内执行每日完整采集及首次歌单同步;设 0 关闭 |
COLLECT_AT | 04:00 | 按 TZ_NAME 时区执行每日完整采集的时刻,格式为 HH:mm |
COLLECT_ON_START | 0 | 设 1 后,若当天没有累计快照,则启动时立即执行完整采集;依赖 COLLECT_IN_API=1 |
REALTIME_IN_API | 1 | 是否在 API 进程内运行最近播放计数器 |
REALTIME_INTERVAL_MS | 60000 | 最近播放轮询间隔(最低 15000ms) |
REALTIME_LIMIT | 300 | 定时轮询每次请求的条数,取值限制为 1–300;客户端实际最多请求 100 条 |
REALTIME_LIMIT 控制定时轮询;collect 和 collect:realtime 使用各自的默认请求条数,同样受客户端 100 条上限限制。COLLECT_ON_START=1 可能在白天采集快照,因此启用前应确认采集时刻与 ATTRIBUTION 的日期归属一致。
[!WARNING] 本服务无鉴权,提供个人听歌与歌单数据。直接运行时保持
API_HOST=127.0.0.1;需要公网访问时,通过反向代理配置 HTTPS 和鉴权。Compose 在容器内绑定0.0.0.0,宿主端口仍只绑定127.0.0.1。.env含登录 Cookie,应保持私密。
| 命令 | 作用 |
|---|---|
npm run migrate | 建表 / 迁移数据库结构 |
npm run collect | 采集累计与一周快照,计算差分,并采集最近播放、今日足迹、歌单及曲目 |
npm run collect:realtime | 只采集实时接口(今日足迹 / 最近播放) |
npm run api | 启动只读 API + 前端(含进程内调度器) |
npm run rebuild | 清空 daily_play,依次重放累计快照差分与最近播放事件,重新合并日级计数 |
npm test | 运行测试(Node 内置 node:test) |
npm run probe | 运行探针,实测 allData 更新延迟(见下) |
所有端点使用 GET。统计与歌单查询读取本地数据库;/api/netease/* 实时请求网易云,/api/cover 返回缓存或新获取的图片。
| 端点 | 说明 |
|---|---|
GET /api/health | 最近快照、最近轮询时间、采集缺口与可用统计窗口 |
GET /api/overview | 总览:总播放、时长估算、Top 歌/歌手/专辑、周环比 |
GET /api/ranking | 多维排行(dimension × metric × period) |
GET /api/trend | 趋势时序(可按歌曲 / 歌手 / 专辑过滤) |
GET /api/hourly-activity | 默认最近 30 个完整自然日的小时事件分布,始终排除今天 |
GET /api/daily-top-songs | 默认最近 7 天、包含结束日期当天的每日歌曲排行与封面 |
GET /api/detail/:dimension/:id | 详情抽屉:dimension 为 song / artist / album,返回统计窗口(period,默认 month)与全部记录的次数、名次、近 30 天小时分布;歌曲另含最近几次播放与所在歌单,歌手 / 专辑另含最常听的歌 |
GET /api/calendar | GitHub 贡献图式的日历热力 |
GET /api/weekday | 周几听歌分布,排除日期归属不确定的增量 |
GET /api/playlists · GET /api/playlists/:id/tracks | 本地保存的用户歌单与歌单曲目,支持分页 |
GET /api/cover?url=... | 网易云封面缓存,url 为经过 URL 编码的图片地址 |
GET /api/netease/record/recent/song | 最近播放,支持 limit,默认 30、最多 100 条 |
GET /api/netease/listen/data/today/song | 网易云今日足迹 |
GET /api/netease/song/detail | 歌曲详情,使用 ids 或 id,多个 ID 以逗号分隔 |
overview 和 ranking 的 period 支持 day / week / month / year / all,默认 all;date 为窗口结束日期,默认今天。日 / 周 / 月 / 年对应包含结束日期在内的滚动 1 / 7 / 30 / 365 天;all 表示本地已记录历史。排行的 dimension 支持 song / artist / album,metric 支持 plays / duration,默认按歌曲播放次数排序。
trend 使用 from / to 指定日期范围,或用 last 指定截至 to 的时间桶数量;granularity 支持 day / week / month / year,默认 day。周桶采用 ISO 周,月桶与年桶按自然月、自然年划分。省略 from 时,默认范围不会早于本地记录起点。
# 最近 7 天歌曲排行,按播放次数排序,返回前 20 首
curl 'http://127.0.0.1:3000/api/ranking?dimension=song&metric=plays&period=week&limit=20'
# 最近 30 天按日汇总的播放趋势
curl 'http://127.0.0.1:3000/api/trend?granularity=day&last=30'
# 最近 30 个完整自然日的小时分布,不含今天
curl 'http://127.0.0.1:3000/api/hourly-activity?days=30'
# 第一页本地歌单
curl 'http://127.0.0.1:3000/api/playlists?limit=30&offset=0'
时长字段按歌曲时长乘以记录次数估算,因此不等同于实际收听时长。
各端点的字段结构不同:
| 端点 | 范围、质量与新鲜度字段 |
|---|---|
overview | meta.period_resolved、meta.data_quality;实际查询范围在顶层 range,新鲜度在顶层 freshness |
ranking | meta.period_resolved、meta.data_quality、meta.freshness |
trend | meta.range、meta.data_quality、meta.freshness,并在每个时间桶标注质量状态 |
daily-top-songs | meta.range、meta.data_quality、meta.freshness,并提供逐日下界状态 |
hourly-activity | meta.range、meta.timezone、meta.data_quality、meta.lower_bound 及事件覆盖信息 |
detail | meta.period_resolved、meta.data_quality(统计窗口)、meta.all_data_quality(全部记录)、meta.data_first_date;小时分布带 located_plays / unlocated_plays |
calendar | meta.year、meta.metric、meta.max,逐日提供 estimated / missing;没有 data_quality 字段 |
weekday | meta.range、meta.excluded_estimated_plays;没有 data_quality 字段 |
data_quality.lower_bound=true 表示计数是已确认的最低值;可能原因包括历史覆盖不足、采集缺口或轮询数据过期。overview、ranking、trend、calendar 和 weekday 在数据不足时会返回 HTTP 200 及 insufficientData: true、haveDays、needDays,此时不返回常规的 meta 和统计结果。
趋势的每个时间桶还提供 lower_bound、has_gap、estimated、missing 和 is_current。有记录但覆盖不完整的桶保留计数并标为下界;未知的空桶标记 missing=true。前端以 ≥ 表示下界,未知数据处中断连线,虚线仅表示进行中的时间桶。
hourly-activity 固定返回 hour=0–23 共 24 个桶,只统计可定位到小时的真实事件。meta.located_plays 为可定位事件数,meta.ledger_plays 为日级总次数,meta.unlocated_plays 为按日比较后无法定位到小时的次数;无法定位的次数不会分摊到小时桶。days 支持 1–365,亦可使用 from / to;结束日期最晚为昨天。
听歌日历直接读取 trend?granularity=day&from=YYYY-01-01 的逐日序列:第一个有记录的日子之前视为还没开始记录,之后 missing=true 的日子画成虚线圈(数据未知),estimated=true 画成空心点(日期不确定)。详情抽屉的逐日柱同样来自 trend 的 song_id / artist_id / album_id 过滤。
每日完整采集和 npm run collect 都会同步歌单列表及曲目。歌单保存的是最近一次成功同步的状态,因此手机端的变更要等下一次同步后才会显示。启用 COLLECT_IN_API 且本地歌单表为空时,API 启动后会立即同步歌单,即使 COLLECT_ON_START=0 也会执行。读取本地歌单接口本身不会触发同步。
/api/cover 仅接受 music.126.net 及其子域名的图片地址,统一请求 HTTPS 和 160×160 尺寸。服务端缓存默认有效 30 天;过期刷新失败且已有缓存时,继续返回旧图。COVER_CACHE_MAX_BYTES 限制单张图片大小;缓存目录目前没有总容量限制,也不会自动清理过期文件。
| 数据 | 表与用途 |
|---|---|
| 累计与一周快照 | snapshot / snapshot_item / snapshot_week_item。每个日期保存一组快照,同日再次采集会覆盖;仅 allData 参与累计差分 |
| 最近播放事件 | recent_play_event。以 (song_id, play_time, source_type) 去重,作为按日计数和小时活跃度的数据来源 |
| 辅助采集记录 | recent_play_snapshot / recent_play_item 保存 collect 与 collect:realtime 请求到的最近播放列表;today_listen_snapshot / today_listen_item 保存今日足迹,今日足迹不参与日级计数 |
| 歌曲维度 | song / artist / album / song_artist,采集时新增或更新 |
| 日级统计 | daily_play,合并累计差分与事件计数;概览、排行、趋势、每日歌曲、日历和周几分布查询此表 |
| 歌单 | playlist / playlist_track,保存最近一次成功同步的歌单与曲目状态 |
| 采集状态 | counter_poll_gap / collection_log / meta,记录轮询缺口、执行日志与计数器状态 |
npm run rebuild 会在事务中清空 daily_play,按日期重放 allData 快照差分,再重放 recent_play_event 并取较大值。因此,完整重建需要保留累计快照和播放事件两类数据。weekData 与今日足迹不参与重建,小时活跃度直接查询播放事件。
完整结构见 src/db/schema.sql。
项目自带多阶段 Dockerfile 与 Compose 编排,使用 Node.js 22 镜像。采集由 API 进程调度,无需额外容器或 cron:
# 在项目根建好 .env(填 NETEASE_UID / NETEASE_COOKIE)
docker compose up -d --build
docker compose logs -f # 查看日志
docker compose down # 停止
容器入口会先执行数据库迁移,再启动 API。默认启动时立即轮询最近播放,并在歌单表为空时同步歌单;累计快照在下一次 COLLECT_AT 执行。需要立即建立首张累计快照时,可执行:
docker compose exec music-record npm run collect
数据库和默认封面缓存目录通过命名卷 music-data 挂载到 /app/data 持久化,容器重建后数据仍保留。端口默认只映射到宿主 127.0.0.1:3000。若修改 API_PORT,需要一并修改 Compose 的端口映射;若修改数据库或缓存路径,应确保新路径仍位于持久化卷内。
play/record 的 allData 从实际播放到接口可见存在传播延迟。src/probe/ 提供采样工具,用来测量该延迟并校准采集时间和 ATTRIBUTION 规则:
npm run probe # 采样一次
npm run probe:watch # 持续采样,已登记的实验窗口内自动提高采样频率
npm run probe:burst # 每次采样连抓 3 次(间隔 1.5 秒),缩小延迟的估计区间
npm run probe:analyze # 分析已采集的延迟数据
[!NOTE] 探针用于研发标定,日常采集无需启动。分析播放延迟前,需要先用
npm run stimulus登记播放实验;具体子命令见src/probe/stimulus.js。
src/
├── api/ Fastify 服务、统计与代理路由、封面缓存
├── collector/ 每日快照、播放事件、歌单同步与进程内调度
├── netease/ 网易云接口客户端与 weapi/eapi 加密
├── aggregate/ 周期解析(periods)与聚合 SQL(queries)
├── db/ schema / migrate / rebuild / 连接管理
├── probe/ allData 更新延迟标定探针
└── config.js 集中配置(环境变量覆盖)
public/ 前端页面、样式、脚本与站点图标(favicon.svg 为标志源文件)
scripts/ Windows .cmd 启动 / 采集 / 探针包装
test/ node:test 单元测试
数据口径与界面用语(统计窗口、趋势粒度、归属日等)的定义见 CONTEXT.md。