当前位置:首页 > 科技 > 正文

用Go语言把AKB48的365纸飞机视频扒下来,顺便聊聊那首歌

  • 科技
  • 2026-08-08 11:30:01
  • 82
摘要: 说实话,我写这个Go程序的时候,耳机里正好放着《365纸飞机》的MV,那旋律一起,感觉整个出租屋都亮了,你可能觉得奇怪,一个写代...

说实话,我写这个Go程序的时候,耳机里正好放着《365纸飞机》的MV,那旋律一起,感觉整个出租屋都亮了,你可能觉得奇怪,一个写代码的,怎么突然聊起AKB48来了?但我敢打赌,你肯定也干过类似的事——想存一个喜欢的视频,结果发现网上一堆工具不是要注册就是要收费,要么就是下载下来带个巨丑的水印。

所以我决定用Go语言自己写个小工具,不是为了炫技,就是单纯想收藏这个视频,顺便治治我的“收藏夹吃灰症”,你知道的,那些视频网站链接,今天能看,明天说不定就404了,尤其是这种偶像团体歌曲,版权一收紧,全网都找不着了。

为什么非要自己写?现成的它不香吗?

香,但不够“香”,市面上那些下载工具,要么是图形界面点来点去,要么是命令行但依赖一堆环境,我只想下个视频,不想装个“全家桶”,Go语言的好处这时候就显出来了——编译出来就是一个独立的二进制文件,扔到任何Linux服务器或者Windows电脑上都能跑,而且它处理并发特别顺手,一个视频分几个段并行下载,速度咔咔的。

写这个程序的过程,本身就是一种“复习”,我平时写业务代码,很少有机会碰网络协议、字节流处理这些东西,但下载视频这事儿,你得跟HTTP头打交道,得处理重定向,得考虑断点续传……每一步都是在跟“真实世界”搏斗

核心思路:先拿“纸飞机”的文件地址

《365纸飞机》这个歌,PV(Promotion Video)在网上的播放源一般藏在网页的JS里,你得先分析网络请求,找到那个真正的 .mp4 链接,这个链接通常有有效期,所以得抓“新鲜”的。

我用的方法是:模拟一个正常的浏览器请求,拿到网页源码,然后用正则表达式把视频地址抠出来,别看这方法老土,但对付大部分站点足够用了,Go标准库里的 net/httpregexp 就够了,不用引第三方包。

package main
import (
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
    "strings"
    "time"
)
func main() {
    pageURL := "你找到的那个包含视频的网页URL"
    client := &http.Client{Timeout: 10 * time.Second}
    req, _ := http.NewRequest("GET", pageURL, nil)
    // 这步很关键,有些网站没有User-Agent直接拒绝
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
    req.Header.Set("Referer", pageURL)
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("请求出错:", err)
        return
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    html := string(body)
    // 寻找 .mp4 链接
    re := regexp.MustCompile(`https?:\\/\\/[^"']+?\.mp4[^"']*`)
    match := re.FindString(html)
    // 有些链接被转义了,需要处理一下
    match = strings.ReplaceAll(match, "\\/", "/")
    if match == "" {
        fmt.Println("没找到视频链接,可能页面结构变了。")
        return
    }
    fmt.Println("找到视频地址:", match)
    // 这里先把地址存起来,下一步用
    downloadFile(match)
}

下载那点破事:并发、重试和“断点续传”

拿到直链后,直接用 http.Get 就能下载,但问题来了,网络不总是可靠的,比如咱看AKB48演唱会那个1080P的源,动辄几个G,中途断了就得重来,心态直接崩。

所以我写了个带重试的下载函数,用 io.Copy 配合 os.Create,如果下载失败,记录已经写入的字节数,下次用 Range 头从断点接着下,这逻辑其实不复杂,但写习惯了,心里那种“掌控感”是别的东西给不了的。

func downloadFile(url string) {
    // 用当前时间当文件名,省的想名字了
    fileName := fmt.Sprintf("AKB48_%d.mp4", time.Now().Unix())
    out, err := os.Create(fileName)
    if err != nil {
        fmt.Println("创建文件失败:", err)
        return
    }
    defer out.Close()
    // 带断点续传的响应体获取
    var start int64 = 0
    // 检查本地是否已有部分文件
    if fi, statErr := out.Stat(); statErr == nil {
        start = fi.Size()
        fmt.Printf("从 %d 字节处续传...\n", start)
    }
    req, _ := http.NewRequest("GET", url, nil)
    if start > 0 {
        req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
    }
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("下载请求失败:", err)
        return
    }
    defer resp.Body.Close()
    if resp.StatusCode == http.StatusOK && start > 0 {
        // 服务器不支持断点,但咱们也认了,重新下载
        out.Truncate(0)
    }
    copied, err := io.Copy(out, resp.Body)
    if err != nil {
        fmt.Println("下载中途出错,但已保存进度,重新运行脚本可续传:", err)
        return
    }
    fmt.Printf("下载完成,共 %d 字节。\n", copied)
}

那首歌,和这个程序有点像

你知道吗,《365纸飞机》这首歌讲的是一种“即使折断了翅膀也要向前飞”的劲儿,我写程序的时候,调试那个正则表达式,大约失败了几十次,一开始匹配不到,后来匹配到了但带了一堆HTML标签,再后来是视频链接有转义字符……每次报错,都像纸飞机撞上了墙

可当你终于看到那个文件大小在屏幕上跳动,进度条一步步走完,那种成就感确实挺上头的。用Go写这玩意儿有个好处——编译完就在那儿放着,几个月后想用还能用,不像有些Python脚本,换个环境,依赖装半天。

我还特意加了个 --mirror 参数,可以把视频里出现的其他相关推荐也一起抓下来存档,虽然麻烦点,但想想以后万一想做个AKB48的剪辑合集,这些素材就都现成了。用代码给自己留个念想,这感觉还挺浪漫的

视频下载到一半的时候,我起身给自己倒了杯水,窗外阴天,但屋子里键盘声噼里啪啦的,突然觉得,人们追星也好,写代码也好,其实都在寻找某种“确定性”——确定那个画面能留住,确定这段逻辑能跑通。《365纸飞机》的MV里,姑娘们把纸飞机飞向天空,而我的程序,正默默地把那段时光拉回硬盘里

等下载完,我打算把这视频剪个15秒的片段当手机铃声,或者干脆不剪,就那么整段放着,哪天心情不好了,就点开看一眼,看看那一年的她们,也看看这一年在电脑前折腾的自己。

用Go语言把AKB48的365纸飞机视频扒下来,顺便聊聊那首歌