说起来有点不好意思,上周末陪我妈去公园遛弯,远远就听见那熟悉的旋律——“365个祝福送给你”,一群阿姨跳得那叫一个整齐划一,连表情管理都到位得不行,我妈一边跟着扭两下,一边嘟囔:“这舞曲在爱奇艺上都有高清版了,你天天写代码,能不能帮我搞个下载工具?”
得,这话戳中我了,作为一个写了五年Golang的程序员,我居然从来没想过用Go去分析一下这种广场舞神曲的传播路径,回家我就打开电脑,心想:与其花时间下个现成工具,不如自己写个爬虫探探路,反正《365个祝福》这种正能量曲目,视频版权方一般也乐意让人看,技术上不算违规,我做的就是纯个人学习研究。
为什么非得用Golang?Python它不香吗?
你要是问我写爬虫首选什么,我大概率会先说Python——毕竟Scrapy、Requests那套生态太成熟了,但这次不一样,我打算从爱奇艺搜索页抓取《365个祝福》广场舞相关的视频信息,包括播放量、发布时间、UP主名字,而且要做好几页翻页,这种情况下,Golang的并发优势就出来了。
你看啊,Go语言有个天生的好脾气:它不像Python那样有GIL锁(全局解释器锁),你用goroutine一开,几十个HTTP请求能同时跑,而且编译完就一个二进制文件,扔到服务器上就能跑,别提多省心,我写爬虫有个习惯——得先想好目标,不然代码写一半就乱了。
这次的任务挺清晰:
- 目标站点:爱奇艺网页版搜索接口
- 关键词:“365个祝福广场舞”
- 提取字段:视频标题、播放量、作者、链接、发布时间
- 存储方式:CSV文件,方便我妈拿Excel看
开工前先摸清爱奇艺的脾气
好,目标定了,先把HTTP请求包封装好,记住啊,爬虫第一步不是写框架,是试探对方的反爬策略,爱奇艺作为老牌视频站,肯定有反爬,但它的搜索接口还算温和——只要你设置好User-Agent和Referer,一般不会动你,我用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通道做并发令牌:

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
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Golang写个爬虫,看看365个祝福广场舞视频在爱奇艺上到底有多火》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:说起来有点不好意思,上周末陪我妈去公园遛弯,远远就听见那熟悉的旋律——“365个祝福送给你”,一群阿姨跳得那叫一个整齐划一,连表情管理都...