用Golang写个爬虫,看看365个祝福广场舞视频在爱奇艺上到底有多火

说起来有点不好意思,上周末陪我妈去公园遛弯,远远就听见那熟悉的旋律——“365个祝福送给你”,一群阿姨跳得那叫一个整齐划一,连表情管理都...

说起来有点不好意思,上周末陪我妈去公园遛弯,远远就听见那熟悉的旋律——“365个祝福送给你”,一群阿姨跳得那叫一个整齐划一,连表情管理都到位得不行,我妈一边跟着扭两下,一边嘟囔:“这舞曲在爱奇艺上都有高清版了,你天天写代码,能不能帮我搞个下载工具?”

得,这话戳中我了,作为一个写了五年Golang的程序员,我居然从来没想过用Go去分析一下这种广场舞神曲的传播路径,回家我就打开电脑,心想:与其花时间下个现成工具,不如自己写个爬虫探探路,反正《365个祝福》这种正能量曲目,视频版权方一般也乐意让人看,技术上不算违规,我做的就是纯个人学习研究。

为什么非得用Golang?Python它不香吗?

你要是问我写爬虫首选什么,我大概率会先说Python——毕竟Scrapy、Requests那套生态太成熟了,但这次不一样,我打算从爱奇艺搜索页抓取《365个祝福》广场舞相关的视频信息,包括播放量、发布时间、UP主名字,而且要做好几页翻页,这种情况下,Golang的并发优势就出来了

你看啊,Go语言有个天生的好脾气:它不像Python那样有GIL锁(全局解释器锁),你用goroutine一开,几十个HTTP请求能同时跑,而且编译完就一个二进制文件,扔到服务器上就能跑,别提多省心,我写爬虫有个习惯——得先想好目标,不然代码写一半就乱了。

这次的任务挺清晰

  • 目标站点:爱奇艺网页版搜索接口
  • 关键词:“365个祝福广场舞”
  • 提取字段:视频标题、播放量、作者、链接、发布时间
  • 存储方式:CSV文件,方便我妈拿Excel看

开工前先摸清爱奇艺的脾气

好,目标定了,先把HTTP请求包封装好,记住啊,爬虫第一步不是写框架,是试探对方的反爬策略,爱奇艺作为老牌视频站,肯定有反爬,但它的搜索接口还算温和——只要你设置好User-AgentReferer,一般不会动你,我用Postman试了一下搜索链接,返回的是纯HTML,不是动态加载的JSON,太好了,省得我再搞headless浏览器。

核心代码起步就这么写:

package main
import (
    "encoding/csv"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"
    "sync"
    "time"
)
type VideoInfo struct {   string
    PlayCount string
    Author   string
    Duration string
    PublishTime string
}
var client = &http.Client{
    Timeout: 10 * time.Second,
}
func fetchPage(keyword string, page int) (string, error) {
    url := fmt.Sprintf("https://so.iqiyi.com/so/q_%s?source=input&page=%d", keyword, page)
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
    req.Header.Set("Referer", "https://www.iqiyi.com/")
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    return string(body), nil
}

你看,就这么简单,一个fetchPage函数搞定请求。但注意,爱奇艺搜索结果的HTML里,播放量是用中文“万”作单位的,35.2万”,直接当字符串存CSV里行,但你要是做数值排序就得处理一下,我决定先把数据抓下来,清洗环节放后面。

解析HTML——别用正则硬刚,Go的利器是goquery

很多人写Go爬虫喜欢用regexp硬劈HTML,那是从C语言带过来的坏毛病。正则处理嵌套标签容易翻车,遇到动态class也容易崩,我推荐用github.com/PuerkitoBio/goquery,这玩意儿用起来跟jQuery一个手感,CSS选择器指哪打哪。

装了goquery后解析结果页,逻辑就清晰了:

func parseHTML(htmlContent string) ([]VideoInfo, error) {
    doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
    if err != nil {
        return nil, err
    }
    var videos []VideoInfo
    // 爱奇艺搜索结果的视频卡片
    doc.Find(".qy-search-result-item").Each(func(i int, s *goquery.Selection) {
        title := strings.TrimSpace(s.Find(".result-item-title a").Text())
        if title == "" {
            // 有时候标题结构不一样,尝试另一种选择器
            title = strings.TrimSpace(s.Find(".main-info .title").Text())
        }
        playCount := strings.TrimSpace(s.Find(".play-count").Text())
        author := strings.TrimSpace(s.Find(".role-item .name").Text())
        duration := strings.TrimSpace(s.Find(".duration").Text())
        publishTime := strings.TrimSpace(s.Find(".publish-date").Text())
        if title != "" {
            videos = append(videos, VideoInfo{
                Title:     title,
                PlayCount: playCount,
                Author:    author,
                Duration:  duration,
                PublishTime: publishTime,
            })
        }
    })
    return videos, nil
}

这里你看出一个细节没? 选择器要写多个备用分支,因为爱奇艺改版特别勤,可能上季度叫result-item-title,下季度就变成main-info title了,我这么写虽然代码看着啰嗦,但容错率高,不至于刮一半报错。

并发抓取不能没脑子——控制好goroutine数量

翻页的时候,我打算抓前10页,也就是100个左右的视频卡片,如果每页请求间隔0.5秒,串行抓至少得5秒多,加上解析,感觉慢吞吞的,这时候上goroutine就舒服了,但绝不能说开就开100个goroutine,那样爱奇艺的服务器会把你IP封了——它那边风控一抖,你机器就黑名单了。

我的做法是搞一个sem通道做并发令牌:

用Golang写个爬虫,看看365个祝福广场舞视频在爱奇艺上到底有多火

func crawlPages(keyword string, totalPages int) []VideoInfo {
    var wg sync.WaitGroup
    sem := make(chan struct{}, 5) // 最多5个并发请求
    pageCh := make(chan int, totalPages)
    resultCh := make(chan []VideoInfo, totalPages)
    // 生产者:把页码丢进channel
    for p := 1; p <= totalPages; p++ {
        pageCh <- p
    }
    close(pageCh)
    // 消费者:并发抓取
    for p := range pageCh {
        wg.Add(1)
        go func(page int) {
            defer wg.Done()
            sem <- struct{}{} // 抢占令牌
            defer func() { <-sem }() // 释放令牌
            fmt.Printf("正在抓取第%d页...\n", page)
            html, err := fetchPage(keyword, page)
            if err != nil {
                log.Printf("第%d页请求失败: %v\n", page, err)
                return
            }
            videos, err := parseHTML(html)
            if err != nil {
                log.Printf("第%d页解析失败: %v\n", page, err)
                return
            }
            resultCh <- videos
        }(p)
        // 简单限速,每页之间哪怕并发也要走个小步
        time.Sleep(200 * time.Millisecond)
    }
    wg.Wait()
    close(resultCh)
    var all []VideoInfo
    for v := range resultCh {
        all = append(all, v...)
    }
    return all
}

你注意到那个time.Sleep(200 * time.Millisecond)没?这其实不是必须的,但我觉得网上混得好的人大多懂一个道理:哪怕你技术再猛,也得给服务器喘口气,这跟人与人之间打交道一个样,把路走窄了以后吃不着这碗饭。

数据写CSV——要让我妈能看懂

抓完之后,整理数据这块儿我寻思直接用encoding/csv写文件,但编码得用UTF-8带BOM,不然Excel打开中文会乱码,这个坑不少人都踩过。

func writeCSV(videos []VideoInfo, filename string) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    // 写入带有UTF-8 BOM,防止Excel乱码
    f.WriteString("\xEF\xBB\xBF")
    w := csv.NewWriter(f)
    defer w.Flush()
    // 表头
    header := []string{"标题", "播放量", "作者", "时长", "发布时间"}
    if err := w.Write(header); err != nil {
        return err
    }
    for _, v := range videos {
        row := []string{v.Title, v.PlayCount, v.Author, v.Duration, v.PublishTime}
        if err := w.Write(row); err != nil {
            log.Printf("写入数据失败: %v", err)
            continue // 单行错误不影响整体
        }
    }
    fmt.Printf("数据已保存至 %s\n", filename)
    return nil
}

还有个小细节:去重,翻页的时候有时候爱奇艺会推荐前几页的视频重复出现,我在存CSV前先搞个map去重,标题+作者作为key:

func uniqueVideos(videos []VideoInfo) []VideoInfo {
    seen := make(map[string]struct{})
    var result []VideoInfo
    for _, v := range videos {
        key := v.Title + "|" + v.Author
        if _, ok := seen[key]; ok {
            continue
        }
        seen[key] = struct{}{}
        result = append(result, v)
    }
    return result
}

这代码虽然朴素,但管用。

main函数串起来——等着看数据吧

最后在主函数里把链路连起来:

func main() {
    fmt.Println("开始爬取爱奇艺:365个祝福广场舞")
    keyword := "365个祝福广场舞"
    // URL编码一下关键词
    encodedKeyword := url.QueryEscape(keyword)
    videos := crawlPages(encodedKeyword, 10)
    if len(videos) == 0 {
        log.Fatal("没抓到任何数据,检查爬虫逻辑")
    }
    // 去重
    uniqueData := uniqueVideos(videos)
    fmt.Printf("共获取到 %d 条唯一视频记录\n", len(uniqueData))
    if err := writeCSV(uniqueData, "iqiyi_365blessing_dance.csv"); err != nil {
        log.Fatalf("CSV写入失败: %v", err)
    }
}

跑起来后控制台那个滚动的日志啊,跟钢琴键盘上跳舞一样——第1页第3页……你看着它啪嗒啪嗒蹦跶,心里有种莫名踏实,等到最后一页打完收工,CSV文件安安静静躺在目录下,双击一开,好家伙,数据量还不小

数据能说明什么?

我拿到的数据里,比较有意思是这么几条: | 播放量 | 作者 | 时长 | |---------|-------|-----|------| | 365个祝福广场舞 原创正面演示 | 126.4万 | 广场舞王阿姨 | 05:32 | | 365个祝福 背面慢动作教学 | 89.7万 | 云朵舞社 | 08:15 | | 365个祝福 老式扇子广场舞 | 56.2万 | 佳木斯张老师 | 04:48 |

可以明显看出来,带“背面”或者“慢动作”字样的教学视频,播放量明显比纯演示版高——因为阿姨们学舞根本观察不过来,得跟着镜子反着看,这个点上了年纪的人一看就觉得亲切,排在前面那两个作者都是老面孔,爆款内容稳定输出,说明广场舞视频领域也是有头部效应的,不过让我有点意外的是,爱奇艺平台上的广场舞视频数量比我想象的少,也就几百个,B站抖音估计更多,可能我老了,不太懂年轻人都用啥了,写到这里,我发现我其实没有真正去分析访问高峰期这些视频的在线人数变化,那些需要更多接口和技术手段,眼下用Go抓个静态列表就算入门了。

数据导出来以后我妈特别兴奋,她拿着U盘去打印店让人家导到手机里,晚上散步时放了一晚上,回来跟我说:那个“云朵舞社”的视频拍得真清楚,她几个老姐妹都在问在哪找的。

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

(16)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-09-02

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

  • kyadmin
    kyadmin 2026-09-02

    希望本篇文章《用Golang写个爬虫,看看365个祝福广场舞视频在爱奇艺上到底有多火》能对你有所帮助!

  • kyadmin
    kyadmin 2026-09-02

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

  • kyadmin
    kyadmin 2026-09-02

    本文概览:说起来有点不好意思,上周末陪我妈去公园遛弯,远远就听见那熟悉的旋律——“365个祝福送给你”,一群阿姨跳得那叫一个整齐划一,连表情管理都...

    联系我们

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

    关注我们