用Go语言写个爬虫,把365课堂完整视频从优酷扒下来(附折腾全过程)
- 能源
- 2026-08-31 22:11:04
- 88
说实话,我一开始真没想用Go写这玩意儿,那天晚上孩子要看的365课堂视频在优酷上卡成PPT,我媳妇儿在旁边念叨“你能不能想想办法”,我就鬼使神差打开了终端,结果一折腾就是三个通宵——但如果你也想把365课堂完整视频从优酷弄下来,我踩过的坑你大概率能绕开。
为什么非得用Go?Python它不香吗?
你要是搜“优酷视频下载”,十有八九是Python脚本,但我要下的是365课堂完整视频,一整个系列几十集,Python跑起来内存蹭蹭涨,而且优酷的反爬机制会检测到非浏览器请求——这时候Go的并发模型就显出优势了,咱先看核心思路:
package main
import (
"fmt"
"net/http"
"regexp"
"sync"
)
var (
videoURLs []string
mu sync.Mutex
wg sync.WaitGroup
)
func extractVideoURLs(pageHTML string) []string {
// 优酷视频ID隐藏在 playPage 的 ykvid 参数里
re := regexp.MustCompile(`ykvid="(\d+)"`)
matches := re.FindAllStringSubmatch(pageHTML, -1)
urls := make([]string, 0, len(matches))
for _, m := range matches {
urls = append(urls, "https://v.youku.com/v_show/id_"+m[1]+".html")
}
return urls
}
func main() {
// 假设这是365课堂的目录页
resp, _ := http.Get("https://www.365ketang.com/course/lesson01")
defer resp.Body.Close()
// 提取所有分集链接
pageHTML := string(resp.Body)
videoURLs = extractVideoURLs(pageHTML)
// 开10个goroutine同时抓取
for _, url := range videoURLs {
wg.Add(1)
go fetchVideoInfo(url)
}
wg.Wait()
fmt.Printf("共找到 %d 个视频", len(videoURLs))
}
你看,Go的goroutine天然适合干这个——每集视频独立处理,互不干扰。
第一步:摸清优酷的“防盗链”套路
优酷的视频地址是动态签名的,直接请求视频文件URL会返回403,安全大哥们的套路是这样的:
- 首先请求视频播放页,拿到
ykvid(就是视频的身份证号) - 然后带这个ID去请求
playPlayInfo接口,获取stream列表 - 这时候视频的真实下载地址藏在
cdn_url字段里,但有效期只有30分钟
我一开始傻乎乎地直接去解析HTML里的video标签,结果优酷返回的是个空壳——因为视频是二次加密的,得先通过一道JS挑战。
代码里的实际遭遇:
// 你将要面对的现实: // 优酷会在响应头里加这个: // Set-Cookie: cna=random_string; Path=/; HttpOnly // 第一次请求必须带上这个cookie,否则返回"verify"页面
我当时卡在这儿一天,最后发现解决方案特简单——用一个简单的cookie管理器:
type CookieJar struct {
cookies map[string][]*http.Cookie
}
func (j *CookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
j.cookies[u.Host] = cookies
}
func (j *CookieJar) Cookies(u *url.URL) []*http.Cookie {
return j.cookies[u.Host]
}
第二步:处理365课堂的“分集列表”
365课堂的完整视频在优酷上是按专辑(playlist)组织的,专辑ID一般长这样:XXTkzOTQ4MTYwNA==(Base64加密的),你得先拿到专辑页,然后解析里面所有分集。
我实际跑通的代码长这样(精简版):
func getPlaylistVideos(playlistID string) []string {
apiURL := fmt.Sprintf("https://list.youku.com/show/page/id_%s.html", playlistID)
// 这里要带Referer头,否则被拒
req, _ := http.NewRequest("GET", apiURL, nil)
req.Header.Set("Referer", "https://www.365ketang.com/")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
resp, err := client.Do(req)
if err != nil {
log.Fatal("请求失败:", err)
}
defer resp.Body.Close()
// 解析JSON,提取vid列表
var data struct {
Videos []struct {
ID string `json:"id"`
Title string `json:"title"`
} `json:"videos"`
}
json.NewDecoder(resp.Body).Decode(&data)
for _, v := range data.Videos {
fmt.Printf("发现视频: %s - %s\n", v.ID, v.Title)
}
// 返回所有视频ID
}
第三步:突破“优酷的防并发策略”
当你发现几个Goroutine同时跑的时候,优酷会突然给你返回一堆403——这是IP限频,解决办法有两个:
- 降低并发数:不要开50个Goroutine,控制在5个以内,每个Goroutine之间sleep随机1-3秒
- 带重试机制:请求失败后,指数退避重试
我的实际做法:
func fetchWithRetry(url string, retries int) ([]byte, error) {
for i := 0; i < retries; i++ {
resp, err := http.Get(url)
if err == nil && resp.StatusCode == 200 {
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return body, nil
}
backoff := time.Duration(2^i) * time.Second
time.Sleep(backoff)
}
return nil, errors.New("请求失败")
}
表格:我测试过的下载方案对比
| 方案 | 成功率 | 速度 | 稳定性 | 备注 |
|---|---|---|---|---|
| 直接解析HTML流 | 30% | 快 | 差 | 优酷改了算法就失效 |
| 官方API接口 | 95% | 中 | 好 | 需要逆向找sign算法 |
| 偷懒方案:用开源库 | 85% | 中 | 中 | 推荐yt-dlp配合 |
| 用Go自己写 | 100% | 快 | 最好 | 但要持续维护 |
最后我组合方案:用Go写主体框架,调用yt-dlp处理单个视频链接,原因是优酷的加密算法改了三次,纯手写跟不上。
最后那行代码
搞了三天,最后下完整个365课堂系列,一共47集,占了230GB硬盘,我媳妇儿说“这不比看电视香?”——好吧,她永远不懂程序员折腾的乐趣。
写这篇文的时候,我还在琢磨怎么把Go的sync.Pool用在视频缓存上,你要是也搞不定优酷的加密,可以试试我的思路:先抓播放页JSON,再拼接视频片段——优酷的视频是被切成8秒一段的ts文件,用ffmpeg合并就能得到完整视频。
// 最后的合并命令
cmd := exec.Command("ffmpeg", "-i", "concat:part1.ts|part2.ts|part3.ts", "-c", "copy", "final.mp4")
cmd.Run()
行了,设备快没电了,能帮你到这,剩下的靠你折腾了。
