这篇文章,记录的是一个很完整的过程:
从最初的 weekly_rank.py,到一张“能用”的周榜海报,再到 Apple 风格的视觉系统,最后一步一步被部署到云服务器上,变成可以每周自动生成的「播放周榜(Weekly Playback Statistics)」系统。
它不是一个一蹴而就的脚本,而是一条慢慢长出来的产品线。
一、最初的 weekly_rank:先把“东西算出来”
故事从一个很朴素的需求开始:
想知道这周在 Jellyfin 上到底看了些什么?
能不能自动算一下 Top 3,然后推到手机上?
于是诞生了最初版本的 weekly_rank.py——它的职责非常直接:
从 Jellyfin 的
playback_reporting.db里读数据统计电影 / 剧集这周的播放次数与时长
做一个 文字版的 Top 榜单
最后通过 Server 酱推送到手机
这个阶段的关注点几乎全部在“数据正确”上:
怎么算这周?(一开始通常会用“今天往前推 7 天”,后来改成自然周)
SQL 写对了吗?有没有把 Episode / Movie 混一起?
排序逻辑是按时长还是按次数?
那时的脚本输出大概长这样(简化):
【NERV-BASE Jellyfin 播放周榜】
统计周期: 2025-12-15 ~ 2025-12-21
📽️ 电影 Top 3:
1. xxx
2. yyy
3. zzz
📺 电视剧 Top 3:
……那时候的目标非常简单:
先把“讲得清楚”这一步做好。
二、第一次给数据“穿衣服”:简单版海报出场
数据有了之后,很自然的下一个问题就是:
能不能把它们画成一张图?
方便转发、存档、发朋友圈。
于是,Pillow 登场了。脚本多了一个重要角色:
draw_poster:负责根据统计结果画一张海报
在这个阶段,版式是典型的“功能优先”风格:
左边大标题
右边是三列榜单
背景一个比较“热情”的红 / 粉 / 紫渐变
Top1 封面更大,配合圆形排名标
这种设计的优点是:一眼就有“榜单感”,缺点也很明显:
视觉上“喊得有点大声”
内容层级和留白不够克制
比起 Apple,更像某些游戏活动页
但它完成了一个非常重要的阶段目标:
输出不再是纯文本,而是一张可以公开展示的海报。
三、遇到的第一个瓶颈:重叠、拥挤与“主榜思维”
继续使用过程中,问题自然浮现出来:
海报元素重叠
斜向排布的封面在某些分辨率 / 排列下会互相盖住
需要调
diagonal_offset_x/diagonal_offset_y来避免重叠
版式的“主榜 / 副榜结构”不再适合
一开始的想法是:播放最多的分类上“主榜轨道”,其他略缩小当“副榜”
但慢慢会觉得:
电影、电视剧、番剧,其实在生活里 地位是等同的
不该在呈现上暗示“某个类型更高级”
于是我们做了第一次比较大的方向调整:
从「主榜 + 辅榜 + 信息列」
转向 「三分类等权」 的设计思路
这时候很重要的一点是:
不是马上改生产脚本,而是先写测试脚本来试版式。
因为版式这种东西,只有生成很多张“假海报”看一遍,才知道哪里舒服、哪里不顺眼。
四、Apple 风格的版式实验:test_poster 与 test_layout_eq
为了让版式更接近 Apple 的视觉语言,我们做了几件事情:
1. 从“装饰”回到“结构”
背景不再是高饱和的炫彩,而是低饱和、柔和的渐变
取消花哨的圆形徽章,改成:
排名数字嵌在卡片内部
小号、低对比度,放在左上角内边距位置
通过 留白和对齐 来建立秩序感,而不是靠重装饰
2. 三列等权的测试脚本:test_layout_eq.py
我们专门写了一个脚本,只干一件事:
用占位色块和方块来验证三列等宽栅格在各种数据组合下是否稳定。
关键参数大致如下:
画布宽度:1080
左右边距:50
列间距:30
三列列宽:
每列最多 3 张卡片
无数据时:
不消失
保留列标题
显示灰色提示文字:“本周暂无播放记录”
我们针对 7 种场景分别生成了测试图:
仅番剧有数据
仅电影有数据
仅电视剧有数据
电影 + 番剧
电视剧 + 番剧
电影 + 电视剧
三个分类都有数据
通过这些图,我们验证了一件事:
无论哪一列有数据,哪一列空,画面都不会显得单薄或失衡。
这意味着这个版式有潜力做成“长期模板”。
五、从版式实验到视觉系统:visual_system.py
在版式测试稳定之后,我们进一步把它抽象成一个「视觉系统测试脚本」:visual_system.py。
它和真实生产的差异在于:
不连数据库
不去 Jellyfin 拉封面
用统一尺寸的纯色块 + 模拟剧集名代替内容
但它严格遵守这几条核心规范:
三列等宽栅格
每列固定 3 张卡片位置
有数据:彩色卡片 + 排名 + 剧集名
无数据:灰色卡片 + 提示文案
顶部 Header + 底部 Footer 结构一致
这一步的意义在于:
把“这张海报”升级为“一个可以反复复用的视觉系统”。
任何后续的内容,只要遵守这个系统的栅格和层级,都能无痛接入。

六、把视觉系统真正接上数据:weekly_rank_v2
有了视觉系统之后,下一步就是:
把生产脚本从旧版
draw_poster升级到新的三列等权版式。
于是诞生了 weekly_rank_v2.py。
1. 数据侧:依旧稳定
统计逻辑:
电影:
ItemType = 'Movie'剧集:
ItemType = 'Episode',再基于 API 信息分为电视剧 / 番剧
周期使用自然周,文本里会明确打印:
统计周期: 2025-12-15 ~ 2025-12-21
依旧会计算“本周片王”(观看时长最高用户),仅用于文字部分,不再硬塞进海报版式里
2. 视觉侧:完全换脑子
draw_poster_v2 里做了以下重要变更:
背景换成低饱和的暖灰 / 冷灰渐变
正中央结构是三列等宽的 Column:
电影 / 电视剧 / 番剧
中英文双标题
每列:
最多 3 条数据
真正有数据就去拿 Jellyfin 封面
拿不到封面时:
用分类颜色画卡片
卡片中间显示剧集名
所有卡片下方都有剧集名一行,最多显示 30 个字符,超出加
...排名数字:
小型数字落在卡片左上
白色、半透明、低对比度
最终效果是:
上映的内容不再被“激动的 UI”抢走镜头,而是安静地站在画布中间。
七、让它自动跑起来:从 Windows 计划任务到云服务器 cron
脚本本地跑得很稳定之后,会自然想到:
既然这是“每周榜”,那它最好每周自动来一次。
1. Windows 计划任务
在本机上,我们配置了一个简单的计划任务:
触发:每周一 10:00
操作:执行
python weekly_rank_v2.py工作目录:
c:\Users\zhanw\Desktop\weekly_rank
这解决了“电脑开着的时候每周自动跑一次”的问题。
2. 问题:那如果电脑关机呢?
答案很现实:
——那就这周没榜了。
为了不受本地电脑开关机影响,我们决定把整套脚本搬到云服务器上运行。
3. 部署脚本:deploy.py
为了避免每次登录服务器手动拷文件、改路径,我们写了一个 deploy.py,它自动做:
SSH 连接到服务器(Ubuntu)
创建远程目录
/home/ubuntu/weekly_rank上传:
weekly_rank_v2.py字体文件(从 Windows 拷贝过去)
用
sed把脚本里的字体路径从C:/Windows/Fonts/...改成
/home/ubuntu/weekly_rank/fonts/...
安装依赖:
Pillowrequestsparamiko(因为脚本本身还要从 NAS 拿数据库)
现场跑一次
python3 weekly_rank_v2.py验证一切正常写入 crontab:
0 10 * * 1 cd /home/ubuntu/weekly_rank && /usr/bin/python3 weekly_rank_v2.py >> /home/ubuntu/weekly_rank/cron.log 2>&1从此之后:
每周一 10:00
云服务器会自动:
从 NAS 拉最新数据库
统计这周播放数据
生成海报
上传到图床
通过 Server 酱推送一条消息
所有过程输出写入
cron.log,方便之后排错
八、回头看:脚本之外的收获
如果只看结果,现在你拥有的是:
一套 稳定的统计逻辑
一套 克制、Apple 风格的视觉系统
一条 从 NAS → 服务器 → 图床 → 推送 的自动化链路
一份 可以每周准点出现的“生活数据报告”
但从过程来看,更有意思的是这些“隐形的收获”:
从“一张海报”转变为“一个视觉系统”的思维变化
从“手动跑脚本”转变为“自动调度,异常可追踪”的工程习惯
从“先堆功能”到“先搭版式骨架,再慢慢填内容”的设计节奏
如果你有自己的媒体服务器、阅读记录、睡眠日志、运动数据……
完全可以照着这条路子,再做一套「属于自己的周报 / 月报系统」。
下一步你完全可以做的扩展:
月度榜 / 年度回顾海报(在同一栅格上换内容维度)
针对某一个分类(比如番剧)做「特别回顾」
把多年数据做一个「年度观影报告」(那种“你今年看了 123 部影片”的总结)
而你现在已经有了最难也最关键的两个东西:
一个可靠的数据 pipeline
一套足够克制、可以长久不腻的视觉语言
后面就是慢慢往里填故事了。