我为什么要写这个?
事情是这样的,我朋友大伟,一个普通的上班族,去年开始搞了个《365生活日记》视频系列,每天拍点日常:早上挤地铁、中午吃啥、晚上遛狗……本来挺无聊的,但他坚持了快300天,粉丝居然涨到5万多。
前几天他半夜打电话给我:“老哥,你搞技术的,能不能帮我把视频数据理一理?我每天手动统计播放量、弹幕数、点赞率,眼睛都快瞎了。”
我本来想说“用Excel不就完了”,但转念一想——作为Golang爱好者,这机会多好!既能帮兄弟忙,又能练手,于是我在周末窝在沙发上,边喝可乐边写了个小工具,今天就把整个过程写出来,带点生活气的技术记录,可能不太完美,但绝对真实。
核心需求:大伟到底想要什么?
我们先梳理一下大伟的需求,他每天发一条视频,大概17~20点更新,他想要一个能自动拉取数据、每天生成报告的东西。
- 数据拉取:从B站API拿播放量、点赞、投币、收藏、弹幕数
- 日报生成:每天看几个关键指标,比如环比昨天涨了多少
- 趋势分析:按周、按月看变化,最好能导出CSV
我当时第一反应:这需求用Python写不更简单吗?但大伟说了一句:“你上次不是吹Golang性能好吗?这次就试试呗。”好家伙,这就给自己挖坑了。
| 需求名 | 说明 | 优先级 |
| 数据拉取 | 按视频ID获取B站API数据 | 高 |
| 日报生成 | 每日自动输出Markdown或HTML报告 | 高 |
| 趋势分析 | 周/月统计,图表用文字版 | 中 |
| 导出功能 | CSV、JSON格式导出 | 低 |
开工:Golang项目的结构怎么搭?
我一开始没正儿八经设计,main.go 起手,写着写着发现不对——代码越来越乱,后来才老老实实分模块:
cmd/:主入口internal/api/:B站API的封装internal/model/:数据结构internal/report/:报告生成逻辑pkg/storage/:数据存储(我先用了SQLite,方便本地跑)
这里我想吐槽一下:Golang的包管理其实挺好的,但第一次用的人容易迷路,比如我一开始把 model 放到了 api 包里,结果后面存数据时循环引用,只好重构了一次,这就是我开头说的——不完美的真实感,哈哈。
关键代码:数据拉取的那点事
大伟的视频在B站,每个视频有独立的 aid(稿件ID),B站公开API不需要鉴权就能拿基础数据,这对我们来说很友好。
我写了个 FetchVideoStat 函数,用 net/http 发请求,返回的是JSON,再用 encoding/json 反序列化,代码大概长这样(伪代码):
type VideoStat struct {
Aid int64 `json:"aid"`
View int64 `json:"view"`
Like int64 `json:"like"`
Coin int64 `json:"coin"`
Favorite int64 `json:"favorite"`
Danmu int64 `json:"danmu"`
}
func FetchVideoStat(aid int64) (*VideoStat, error) {
url := fmt.Sprintf("https://api.bilibili.com/x/web-interface/archive/stat?aid=%d", aid)
resp, err := http.Get(url)
if err != nil { return nil, err }
defer resp.Body.Close()
// 解析...
}
这里要注意几个坑:B站的API挺稳定的,但偶尔会限制频率,我第一次写的时候没加限流,结果跑了200个视频后被封IP了十几分钟,后来加了个 time.Sleep(1 * time.Second) 才解决,其实更好的做法是用 rate.Limiter,但我就图省事儿了。
日报生成:让数据“说话”
数据拿到了,怎么呈现给大伟?我选了Markdown格式,然后在终端直接打印出来,大伟是Mac用户,平时用Typora看Markdown很方便。
报告包含了:
- 总播放量、粉丝转化率、弹幕密度等核心指标
- 环比昨日变化(用红色箭头⬆️⬇️表示)
- 一周内播放量最高的3条视频
- 建议:今天弹幕比昨天少了20%,可以尝试在视频最后提个互动问题”
生成报告的核心逻辑用到了 html/template 包,但输出了Markdown,这里其实有个小技巧:既然我们已经有结构体数据,用模板渲染比字符串拼接要优雅得多。
tpl := `# 365生活日记 - 日报
更新日期:{{.Date}}
总播放:{{.TotalView}}
点赞率:{{.LikeRate}}%
{{.DiffMark}}
### 今日Top 3视频
{{range .TopVideos}}} | 播放: {{.View}} | 弹幕: {{.Danmu}}
{{end}}
`
说实话,这报告挺朴素的,没有花哨的图表,但大伟说“够用了,比手动记强一百倍”,你看,用户要的往往是实用性,不是技术多炫酷。

存储:选SQLite还是直接写文件?
这里我犹豫过,大伟的视频一共就300多个,数据量不大,我最初想直接用JSON文件存每一天的拉取记录,但后来考虑到趋势分析需要按时间查询,还是老老实实用SQLite吧。
Golang操作SQLite我用的是 mattn/go-sqlite3,这是一个纯C实现的驱动,安装时需要gcc,稍微麻烦一点,如果你用Go 1.20以上,可以考虑 modernc.org/sqlite,它是纯Go实现的,免去了C编译的烦恼。
表结构很简单:
| 字段 | 类型 | 说明 |
| aid | INTEGER | 视频ID |
| fetch_date | TEXT | 拉取日期 yyyy-mm-dd |
| view | INTEGER | 播放量 |
| like | INTEGER | 点赞数 |
| danmu | INTEGER | 弹幕数 |
每天跑一次拉取任务,然后插入数据,查询时按 aid 和 fetch_date 组合查,就能得到趋势数据,这个视频发了一周后播放增长曲线是怎样的”,大伟看了后说:“原来我周三发的视频周末会有一波小高峰啊!”——这就是数据带来的洞察。
自动化:定时任务和部署
大伟不想每天手动跑程序,所以必须自动化,最简单的方案:用Crontab定时执行编译后的二进制文件,我在他Mac上写了个shell脚本:
#!/bin/bash
cd /Users/dawei/365diary
./diary report daily
./diary export csv
然后crontab写上 0 22 * * * /Users/dawei/365diary/run.sh,每晚10点跑一次,这里又踩了个坑:crontab的环境变量跟终端登录不一样,我的SQLite路径是相对路径,结果跑了半天没数据,排查了好久才发现是路径问题,后来改成绝对路径就好了。
如果你想要更高级的部署方式,可以用Docker,但我觉得对大伟这种个人项目来说,Crontab + 二进制文件是最轻量的方案,维护成本低,出了问题他也能自己重启。
一些遗憾:没做到的事情
这工具说到底还是个“半成品”。
- 没做可视化图表(大伟想要折线图,我拖了俩星期还没搞)
- 没做web界面(我本来想用Gin搭个简单的后台,但懒了)
- B站API偶尔返回空数据,错误处理还不够完善
- 没做多平台支持(大伟也在小红书发视频,但还没集成进来)
但这就是真实开发的常态吧,用户需求永远跑得比代码快,大伟前几天跟我说:“你那个日报挺好,但我现在想看看粉丝画像年龄分布……”我能咋办,只能告诉他“下周安排”。
—“365生活日记大伟视频”,其实就是用Golang写了个小工具,帮一个普通视频创作者解决数据焦虑,技术本身不复杂,但当你看到朋友因为你的代码而节省了时间,甚至发现了自己视频内容的规律,那种成就感是实实在在的。如果你们也有类似的个人项目需求,Golang真的是个不错的选择,它不像C++那么折磨人,也不像Python那么“拖沓”,编译成单一二进制,丢到服务器上就能跑,用费曼写作法来说就是:代码是写给人看的,先让逻辑跑通,再慢慢优化细节。
好了,我得去写那个可视化图表了,希望大伟的365日记能如期完成,到时候我请他吃顿火锅,顺便聊聊下一期功能怎么搞。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/jiankang/1791.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用 Golang 写365生活日记大伟视频,一个普通人的技术折腾手记》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:我为什么要写这个?事情是这样的,我朋友大伟,一个普通的上班族,去年开始搞了个《365生活日记》视频系列,每天拍点日常:早上挤地铁、中...