当前位置:首页 > 能源 > 正文

用Go语言折腾一整年,我如何用365天视频读完红楼梦

  • 能源
  • 2026-07-07 22:42:13
  • 263
摘要: 我从没想过自己会和《红楼梦》扯上关系,更没想过会用Go语言去“读”它,这事儿得从去年元旦说起,那天我瘫在沙发上刷手机,刷到一条推...

我从没想过自己会和《红楼梦》扯上关系,更没想过会用Go语言去“读”它,这事儿得从去年元旦说起。

那天我瘫在沙发上刷手机,刷到一条推送:“一年365天,每天一分钟,看完《红楼梦》”,我点进去一看,是个视频合集,每天更新一小段,配上原文朗读和画面,我当时想,这主意不错啊,反正我也没耐心翻那厚厚一本,结果看了三天就忘了。

但后来发生了一件事,让我彻底入了坑——我发现自己用Go写的一个小工具,居然能帮我“自动”看这些视频。

为什么是Go?为什么是《红楼梦》?

先说视频,那系列视频叫“一年365天视频红楼梦”,就是每天发一个一分钟左右的短视频,按原著章节顺序走,比如第一回“甄士隐梦幻识通灵”能拆成七八个视频,每个讲一个小场景,365天刚好能覆盖全书一百二十回,外加一些背景解读。

这形式其实挺聪明的——每天只占用你一分钟,压力小,容易坚持,但我这人有个毛病,一旦觉得某件事有规律可循,就忍不住想用代码去优化它,比如我就想:能不能每天自动下载当天的视频?能不能把视频里的音频转成文字?能不能把这些文字按日期整理成笔记?

Go语言这时候就派上用场了,它编译快、部署简单、并发处理能力强,最关键的是,Go的生态里有很多现成的库,拿来做视频下载、音频转文字、文本处理这些事,特别顺手。

我第一个想法是写个定时任务,每天自动抓取视频,但很快发现这事没那么简单。

第一版:用Go写个“视频下载器”

我一开始的思路是这样的:每天凌晨用Go写个cron定时任务,去那个视频平台检查有没有新视频更新,如果有就下载下来。

我用net/http包发请求,用regexp包解析HTML,市面上很多视频网站的反爬措施都不弱,但那个“一年365天视频红楼梦”系列的视频是公开可看的,而且视频地址直接写在页面源码里,没加密,这让我省了不少事。

// 伪代码,实际写起来比这长十倍
func fetchDailyVideo() {
    url := "https://xxx.com/daily/hongloumeng"
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    // 用正则找到视频地址
    re := regexp.MustCompile(`"videoUrl":"(.*?)"`)
    matches := re.FindStringSubmatch(string(body))
    // 下载视频
    downloadVideo(matches[1], time.Now().Format("2006-01-02"))
}

第一版跑起来后,我发现了一个问题——视频是能下载了,但我根本没时间看,每天一分钟的视频,我下载了五十多个,一个都没打开过。

这不就是典型的“下载癖”吗?下载即收藏,收藏即吃灰。

第二版:把视频变成文字,用Go做音频转写

我决定换个思路,既然视频是讲《红楼梦》的,核心内容是音频里的旁白和字幕,那为什么不直接转成文字呢?

Go语言没有原生的语音识别能力,但可以用第三方的API,我接入了阿里云的通义听悟(当时还叫“语音识别”),把视频里的音频轨提取出来,转成文本。

音频提取这块,Go的ffmpeg绑定库特别好用,我写了个函数,把MP4文件转成16kHz的PCM音频,然后分段传给识别接口。

// 提取音频并转写
func transcribeVideo(videoPath string) (string, error) {
    // ffmpeg提取音频
    cmd := exec.Command("ffmpeg", "-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "temp.wav")
    cmd.Run()
    // 读取音频文件
    audioData, _ := ioutil.ReadFile("temp.wav")
    // 调用语音识别API
    result, err := speechToText(audioData)
    if err != nil {
        return "", err
    }
    return result, nil
}

这样做的好处是,我每天只需跑一次脚本,就能得到当天的《红楼梦》知识点文字,但我很快发现,一分钟的音频转成文字后,只有两三百字,内容太少了。

第三版:用Go做内容聚合和结构化

只转文字还是不够,我想让这些文字变得更有用——比如按人物、情节、诗词分类。

《红楼梦》里人物众多,光是有名有姓的就有四百多个,我写了个简单的实体识别模块,用map做大词表匹配,比如在文本里找到“黛玉”、“宝钗”、“宝玉”、“凤姐”这些关键词,就自动打上标签。

// 简单的关键词匹配
var characterMap = map[string]string{
    "黛玉": "林黛玉",
    "宝钗": "薛宝钗",
    "宝玉": "贾宝玉",
    "凤姐": "王熙凤",
    // 更多...
}
func tagCharacters(text string) []string {
    var tags []string
    for keyword, fullName := range characterMap {
        if strings.Contains(text, keyword) {
            tags = append(tags, fullName)
        }
    }
    return tags
}

我还用Go的goquery库(类似jQuery的HTML解析器)去抓取维基百科和百度百科里关于《红楼梦》的条目,把这些视频文本和百科知识做关联,比如某天视频讲的是“刘姥姥一进荣国府”,我就自动把刘姥姥的百科词条摘要也拉进来,作为补充阅读。

第四版:做一个本地知识库,每天自动推送

到这一步,我已经有了一个每天更新的《红楼梦》小专栏了——每天的视频文字、人物标签、相关百科摘要,都按日期存成Markdown文件。

然后我用Go的embed功能(Go 1.16引入的),把整个文件夹嵌入到一个小型HTTP服务器里。

//go:embed notes/*
var notes embed.FS
func main() {
    http.Handle("/notes/", http.FileServer(http.FS(notes)))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

这样我就可以在浏览器里每天打开localhost:8080/notes/2024/01/01.md,看到当天的学习内容。

后来我又加了个小功能:用Go的gopls(语言服务器协议)做了一个简单的TUI界面,每天打开终端,输入honglou,就能看到当天更新的段落,还能按人物筛选。

比如我想看看“这周关于王熙凤的内容有哪些”,程序会扫描所有笔记,找出包含“王熙凤”或“凤姐”的条目,按时间排序显示。

踩过的坑和真实的体验

说句实话,这整个过程远没有我写得这么顺利,中间出了无数bug。

最大的坑是语音识别,方言、背景音乐、语速问题,都会导致转写错误,比如有一集视频里旁白说“贾母携众人拜佛”,结果识别成了“贾母携众人败毒”,类似的笑话很多,我后来不得不加了个校对步骤,把常见的错误词手动映射成正确的。

还有一个坑是视频平台改了接口,用了两个月后,对方更新了前端代码,视频地址的匹配规则变了,我花了两个小时重新调试正则表达式,当时真想把这项目删了拉倒。

但奇怪的是,正是这些“折腾”的过程,让我对《红楼梦》的内容记得特别牢,因为每天都要处理这些文本,我得反复去确认内容是否准确,比如有一回视频讲“晴雯撕扇”,文字转写出来是“晴雯撕伞”,我查了原文才发现是“扇子”,这种纠错的过程,反而加深了记忆。

结果怎么样?

到现在,365天过去了,我其实没完整“看”完那365个视频——大部分时间都是靠自动下载、转写、聚合来处理的,但最终我得到了: 类型 | 数量 | |---------|------| | 视频转写文字 | 365篇 | | 百科关联条目 | 120余篇 | | 人物标签数据 | 400+人物 | | 自用笔记文件 | 365个md文件 | | 核心诗句提取 | 80余首 |

这些文件加起来才几十兆,但里面包含了《红楼梦》全书一百二十回的梗概、重要对话、诗词赏鉴,还有我通过Go代码自动生成的交叉索引。

如果你问我还想不想翻那本纸质的《红楼梦》——当然想,但这个用Go语言搭建的“365天视频红楼梦”系统,让我在一年里至少能说出“元春省亲”发生在第几回、“宝钗扑蝶”和“黛玉葬花”哪个在前头,对于一个曾经连“金陵十二钗”都数不全的人来说,这已经值了。

现在每天早上一杯咖啡,打开终端看一眼那个程序自动准备的内容,突然觉得——写代码的人读《红楼梦》,可能比别人更快一点。

用Go语言折腾一整年,我如何用365天视频读完红楼梦