用Go语言写一个365天抖音视频分析器,从数据采集到可视化的完整实践

前两天刷抖音,突然想看看自己一年到底刷了多少条视频——但抖音不给你导出数据啊,正好最近在学Go,那就自己动手写个工具呗,这篇文章就是记录...

前两天刷抖音,突然想看看自己一年到底刷了多少条视频——但抖音不给你导出数据啊,正好最近在学Go,那就自己动手写个工具呗,这篇文章就是记录我怎么用Go语言,把一年365天的抖音观看记录(假设你能导出JSON)变成有用的分析报告,别指望一步到位,咱们边写边想,就像你真的在电脑前折腾一样。

为什么选Go而不是Python?

说实话,Python处理这种小数据量更顺手,但Go有几个点特别吸引我:

  • 编译成单个可执行文件——拷到任何机器都能跑,不装环境
  • 并发处理——如果数据量大(比如几十万条记录),Go的goroutine能快好几倍
  • 标准库强大——encoding/jsonnet/httpsort这些,写分析工具够用了

我的数据长这样(抖音的“我的收藏”导出,或者你自己抓的接口数据):

[
  {
    "id": "738291018394",: "如何用Go写并发爬虫",
    "author": "码农翻身",
    "category": "编程",
    "duration": 185,
    "watchTime": "2024-03-15T21:34:00",
    "liked": true,
    "commentCount": 328
  }
]

第一步:定义数据结构

写代码第一件事不是写逻辑,是定义数据结构,这决定了后面所有代码怎么走。

type DouyinVideo struct {
    ID           string    `json:"id"`       string    `json:"title"`
    Author       string    `json:"author"`
    Category     string    `json:"category"`
    Duration     int       `json:"duration"` // 秒
    WatchTime    time.Time `json:"watchTime"`
    Liked        bool      `json:"liked"`
    CommentCount int       `json:"commentCount"`
}

这里有个小坑:抖音返回的时间是"2024-03-15T21:34:00",Go的time.Time能直接解析,但字段标签必须写对。

第二步:读取和解析数据

假设你有douyin_2024.json这个文件(没有?那就自己写个假数据测试)。

func loadData(filename string) ([]DouyinVideo, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    var videos []DouyinVideo
    err = json.Unmarshal(data, &videos)
    if err != nil {
        return nil, err
    }
    return videos, nil
}

这段代码看起来简单,但实际跑起来你会发现几个问题,比如空数据——文件是而不是null,Go会报错,解决办法是先检查长度。

第三步:核心分析——按天统计观看时长

最想知道的肯定是:我一年哪天刷得最多?

用Go语言写一个365天抖音视频分析器,从数据采集到可视化的完整实践

func analyzeByDay(videos []DouyinVideo) map[string]time.Duration {
    daily := make(map[string]time.Duration)
    for _, v := range videos {
        day := v.WatchTime.Format("2006-01-02") // 只取日期部分
        daily[day] += time.Duration(v.Duration) * time.Second
    }
    return daily
}

这个"2006-01-02"是Go的独特日期格式,不是随便写的——它恰好是Go语言诞生的时间(2006年1月2日),刚学的时候老记不住,现在肌肉记忆了。

然后排序输出前10天:

type dayStat struct {
    Day   string
    Hours float64
}
func topDays(daily map[string]time.Duration, n int) []dayStat {
    stats := make([]dayStat, 0, len(daily))
    for day, d := range daily {
        stats = append(stats, dayStat{Day: day, Hours: d.Hours()})
    }
    sort.Slice(stats, func(i, j int) bool {
        return stats[i].Hours > stats[j].Hours
    })
    if len(stats) > n {
        stats = stats[:n]
    }
    return stats
}

等等,这里有个逻辑漏洞——daily里每天的时长类型是time.Duration,但排序后转成float64的小时数,精度会丢一点,不过对于分析来说够了。

第四步:分类统计——你最沉迷哪个领域?

func categoryAnalysis(videos []DouyinVideo) map[string]int {
    cat := make(map[string]int)
    for _, v := range videos {
        if v.Category != "" {
            cat[v.Category]++
        }
    }
    return cat
}

这个简单,但统计“观看时长”比“视频数量”更合理——因为有些视频30秒,有些30分钟,改一下:

func categoryAnalysis(videos []DouyinVideo) map[string]time.Duration {
    cat := make(map[string]time.Duration)
    for _, v := range videos {
        cat[v.Category] += time.Duration(v.Duration) * time.Second
    }
    return cat
}

第五步:生成可视化报告

光有数据不行,得让结果好看,我用柱状图字符画——不用装第三方库,纯控制台输出。

func printBarChart(title string, data map[string]float64, maxWidth int) {
    fmt.Printf("\n=== %s ===\n", title)
    // 找出最大值用于归一化
    var maxVal float64
    for _, v := range data {
        if v > maxVal {
            maxVal = v
        }
    }
    if maxVal == 0 {
        return
    }
    // 按值排序,从大到小
    keys := make([]string, 0, len(data))
    for k := range data {
        keys = append(keys, k)
    }
    sort.Slice(keys, func(i, j int) bool {
        return data[keys[i]] > data[keys[j]]
    })
    for _, k := range keys {
        barLen := int(data[k] / maxVal * float64(maxWidth))
        fmt.Printf("%-15s |%s %.1f\n", k, strings.Repeat("█", barLen), data[k])
    }
}

输出长这样:

=== 观看时长TOP类别 ===
编程         |█████████████████████████ 78.5
美食         |██████████████ 32.1
健身         |████████ 18.7
搞笑         |█████ 11.3

第六步:并发优化——当你有一年数据时

如果数据量像标题说的“365天”,每天可能200+条,总共7万+条,单线程解析没问题,但如果做复杂统计(比如按小时分布),并发更快。

func analyzeConcurrent(videos []DouyinVideo) {
    var wg sync.WaitGroup
    hourCount := make([]int, 24)
    var mu sync.Mutex
    for _, v := range videos {
        wg.Add(1)
        go func(v DouyinVideo) {
            defer wg.Done()
            hour := v.WatchTime.Hour()
            mu.Lock()
            hourCount[hour]++
            mu.Unlock()
        }(v)
    }
    wg.Wait()
    // hourCount[22] 就是晚上10点刷的视频数
}

但这里有个隐患——goroutine启动太多(7万个),调度开销反而大,更好的做法是用worker pool(固定N个goroutine处理channel里的任务)。

func analyzeWorkerPool(videos []DouyinVideo, workers int) []int {
    tasks := make(chan DouyinVideo)
    results := make(chan int, 24)
    var wg sync.WaitGroup
    hourCount := make([]int, 24)
    // 启动workers
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for v := range tasks {
                results <- v.WatchTime.Hour()
            }
        }()
    }
    // 发送任务
    go func() {
        for _, v := range videos {
            tasks <- v
        }
        close(tasks)
    }()
    // 收集结果
    go func() {
        wg.Wait()
        close(results)
    }()
    for hour := range results {
        hourCount[hour]++
    }
    return hourCount
}

别急着复制——这个代码有竞态条件results channel的接收没加锁,实际跑的时候得加mutex或者不用channel直接原子操作,我在这上面翻过车。

第七步:导出结果到CSV

最后一步,把统计结果存成CSV,方便Excel里做图。

func exportToCSV(filename string, daily map[string]time.Duration) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    w := csv.NewWriter(f)
    defer w.Flush()
    w.Write([]string{"日期", "观看时长(小时)"})
    for day, d := range daily {
        w.Write([]string{day, fmt.Sprintf("%.2f", d.Hours())})
    }
    return nil
}

注意:csv.WriterWrite方法有长度限制,如果一行超过65535个字符会出错(Go标准库的已知问题),不过我们的数据不会那么胖。

测试很重要——用假数据验证

写代码没人一次就对,我生成了随机假数据来测试:

func generateMockData(count int) []DouyinVideo {
    cats := []string{"编程", "美食", "健身", "搞笑", "旅行"}
    videos := make([]DouyinVideo, count)
    for i := 0; i < count; i++ {
        videos[i] = DouyinVideo{
            ID:       fmt.Sprintf("v%d", i),
            Title:    "测试视频" + strconv.Itoa(i),
            Author:   "作者" + strconv.Itoa(rand.Intn(5)),
            Category: cats[rand.Intn(len(cats))],
            Duration: rand.Intn(240) + 10, // 10~250秒
            WatchTime: time.Now().AddDate(0, 0, -rand.Intn(365)), // 最近365天
            Liked:    rand.Intn(2) == 0,
        }
    }
    return videos
}

然后跑一遍,看输出是否合理。

优化方向——还有啥可做的?

目前这个工具能跑,但距离“完整”还差很远,我列几个可以考虑的点:

  • 缓存中间结果——每次跑都重新解析JSON太慢,用gobCSV存中间态
  • Web界面——用net/http起个本地服务,浏览器里交互式查看
  • 时间序列分析——连续7天观看时长下降了吗?”,需要统计窗口函数
  • 用户行为序列——用顺序模式挖掘看你自己刷视频的路径(虽然有点自恋)

踩过的坑

写这个工具过程中,我实际遇到的几个坑:

  1. time.Parse的布局字符串——必须用"2006-01-02 15:04:05"而不是"YYYY-MM-DD ...",我第一次用了后者,解析全错。
  2. JSON字段大小写——抖音返回的是"watch_time"(下划线),而Go结构体用WatchTime,需要写json:"watch_time",不写就绑不上。
  3. sort.Slice是原地排序——会改变原切片顺序,记得先copy
  4. map的遍历顺序是随机的——输出报告前得先排序,不然每次结果不一样。

现在工具能跑,结果也挺有意思——我发现自己每周二晚上刷视频时长是其他日子的1.8倍,可能是因为那天项目例会后特别累。

其实我本想做更多(比如预测明天刷多久),但先这样吧——工具能帮你看清行为模式,就已经很有价值了,剩下的,等以后有兴致再写,用Go的好处就是,哪天想扩展,编译一下就能部署到任何地方跑,你可能会遇到自己那365天的数据,然后发现——原来我一年看了130小时的猫视频

本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://hljbesthome.com/keji/2183.html

(16)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-08-11

    我是be365的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-08-11

    希望本篇文章《用Go语言写一个365天抖音视频分析器,从数据采集到可视化的完整实践》能对你有所帮助!

  • kyadmin
    kyadmin 2026-08-11

    本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名

  • kyadmin
    kyadmin 2026-08-11

    本文概览:前两天刷抖音,突然想看看自己一年到底刷了多少条视频——但抖音不给你导出数据啊,正好最近在学Go,那就自己动手写个工具呗,这篇文章就是记录...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们