Golang爬取网站视频,音乐等m3u8格式媒体文件

Golang爬取网站视频,音乐等m3u8格式媒体文件

远航
2022-05-06 / 0 评论 / 251 阅读 / 正在检测是否收录...
1.获取m3u8文件的下载地址

使用基础的http请求获取页面内容: Golang发起http请求
异步加载的网站通过这个获取: Golang爬取异步加载渲染的Html内容
然后根据页面内容各种截取获取到m3u8的下载地址
可能会用到: 在Golang中运行JavaScript
每个网站都不一样,这里就不乱指挥了

2.开始干正事情,先来个main方法
package main

// 一个谷歌浏览器的UA
var googleUa = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36"

func main() {

}
3.封装一个简单的http请求
// HttpGet 发起http请求 reqObj包看其他文章
func HttpGet(url string, ua string) string {
    reqObj := httpReq.HttpReq()
    reqObj.SetHeader(map[string]string{
        "User-Agent": ua,
    })
    res, _ := reqObj.Get(url)
    return res
}
4.一个简单的工具函数:判断文件夹是否存在
// IsDir 判断目录是否存在
func IsDir(fileAddr string) bool {
    s, err := os.Stat(fileAddr)
    if err != nil {
        log.Println(err)
        return false
    }
    return s.IsDir()
}
5.另一个简单的工具函数,字符串转数组
// StringToArray 字符串转数组
func StringToArray(str string, sep string) []string {
    if str == "" {
        return []string{}
    }
    return strings.Split(str, sep)
}
6.还是简单的工具函数,获取当前文件位置
// GetCurrentPath 获取当前文件位置
func GetCurrentPath() (string, error) {
    file, err := exec.LookPath(os.Args[0])
    if err != nil {
        return "", err
    }
    path, err := filepath.Abs(file)
    if err != nil {
        return "", err
    }
    i := strings.LastIndex(path, "/")
    if i < 0 {
        i = strings.LastIndex(path, "\\")
    }
    if i < 0 {
        return "", errors.New(`error: Can't find "/" or "\".`)
    }
    return string(path[0 : i+1]), nil
}
7.最后两个简单的工具函数
// createFile 递归创建文件夹
func createFile(filePath string) error {
    if !IsDir(filePath) {
        err := os.MkdirAll(filePath, os.ModePerm)
        return err
    }
    return nil
}

// ReplenishStr 保存输出的名字长度,如1.ts 改成 00001.ts
func ReplenishStr(str interface{}, repStr string, amount int) string {
    newStr := gconv.String(str)
    strLen := len(newStr)
    if strLen < amount {
        for i := 0; i < amount-strLen; i++ {
            newStr = repStr + newStr
        }
    }
    return newStr
}
8.开始写main方法里的正式代码
// 获取系统文件路径分隔符
sysType := runtime.GOOS
DS := "/"
if sysType == "windows" {
    // windows系统
    DS = "\\"
}
// 需要爬取的视频或者音乐m3u8地址
url := "https://不方便暴露的某网站地址.m3u8"
// 获取地址里的内容
content := HttpGet(url, googleUa)

// 这一部分根据要爬取的网站自己更改 START
// 根据浏览器控制台ts地址整理m3u8文件里ts的下载地址
lastIndex := strings.LastIndexAny(url, "/")
tsUrl := gstr.SubStr(url, 0, lastIndex) + "/"
// m3u8内容转数组
cArr := StringToArray(content, "\n")
// 整理ts完整下载地址到数组
tsArr := make([]string, 0)
for _, y := range cArr {
    if y != "" {
        if find := strings.Contains(y, "#"); !find {
            tsArr = append(tsArr, y)
        }
    }
}
// 这一部分根据要爬取的网站自己更改 END

// 获取当前运行文件所在路径,打包二进制后的二进制文件命令
path, _ := GetCurrentPath()
// 方便调试更改到桌面
path = "/Users/yuanhang/Desktop/"
// 建个文件夹保存下载的文件
path += "downFile" + DS
// 判断文件夹是否存在,不存在就创建
_ = createFile(path)
// 根据顺序给ts文件命名方便后续处理
fileName := make([]string, 0)
for k, v := range tsArr {
    fileArr := StringToArray(v, "?")
    fn := fileArr[0]
    fn = ReplenishStr(k, "0", len(gconv.String(len(tsArr)))) + ".ts"
    fileName = append(fileName, path+fn)
}
9.一大堆准备工作后开始下载
for k, v := range tsArr {
    // 这里根据需求自己处理 START
    fileArr := StringToArray(v, "?")
    fn := fileArr[0]
    fn = ReplenishStr(k, "0", len(gconv.String(len(tsArr)))) + ".ts"
    fmt.Println(tsUrl + v)
    // 这里根据需求自己处理 START
    // 下载文件 方法在其他文章
    _ = down.DownloadFile(path+fn, tsUrl+v)
}
10.一个一个下载很慢,go怎么能没有协程呢,我们改一下批量下载
// 创建一个等待组
syncTime := sync.WaitGroup{}
// 给等待组增加计数 按长度来添加
syncTime.Add(len(tsArr))
// 循环下载ts文件
for k, v := range tsArr {
    // 启动协程批量同步下载
    go func(k int, v string) {
        // 这里根据需求自己处理 START
        fileArr := StringToArray(v, "?")
        fn := fileArr[0]
        fn = ReplenishStr(k, "0", len(gconv.String(len(tsArr)))) + ".ts"
        fmt.Println(tsUrl + v)
        // 这里根据需求自己处理 START
        // 下载文件 方法在其他文章
        _ = down.DownloadFile(path+fn, tsUrl+v)
        // 下载完减少计数
        syncTime.Done()
    }(k, v)
}
// 阻塞等待,直到计数为0全部下载完,不然协程一启动就退出了都没下载完
syncTime.Wait()
11.下载完后合并文件
// 给新文件起个名字保存起来
saveFile := path + gconv.String(grand.N(1000, 9999)) + ".mp3"
// 循环合并ts文件,按fileName的顺序合并,不是合并后播放顺序也是乱的
for _, f := range fileName {
    mergeFile(0, f, saveFile)
}
// 输出结果
fmt.Println("文件合并完毕")
12.控制台执行go run main.go后的结果,然后去桌面找到文件就可以播放啦

iShot_2022-05-06_15.31.39.png

13.完整代码
package main

import (
    "errors"
    "fmt"
    "github.com/gogf/gf/text/gstr"
    "github.com/gogf/gf/util/gconv"
    "github.com/gogf/gf/util/grand"
    "io"
    "log"
    "notes/down"
    "notes/httpReq"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"
    "strings"
    "sync"
)

// 一个谷歌浏览器的UA
var googleUa = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36"

func main() {
    // 获取系统文件路径分隔符
    sysType := runtime.GOOS
    DS := "/"
    if sysType == "windows" {
        // windows系统
        DS = "\\"
    }
    // 需要爬取的视频或者音乐m3u8地址
    url := "https://不方便暴露的某网站地址.m3u8"
    // 获取地址里的内容
    content := HttpGet(url, googleUa)

    // 这一部分根据要爬取的网站自己更改 START
    // 根据浏览器控制台ts地址整理m3u8文件里ts的下载地址
    lastIndex := strings.LastIndexAny(url, "/")
    tsUrl := gstr.SubStr(url, 0, lastIndex) + "/"
    // m3u8内容转数组
    cArr := StringToArray(content, "\n")
    // 整理ts完整下载地址到数组
    tsArr := make([]string, 0)
    for _, y := range cArr {
        if y != "" {
            if find := strings.Contains(y, "#"); !find {
                tsArr = append(tsArr, y)
            }
        }
    }
    // 这一部分根据要爬取的网站自己更改 END

    // 获取当前运行文件所在路径,打包二进制后的二进制文件命令
    path, _ := GetCurrentPath()
    // 方便调试更改到桌面
    path = "/Users/yuanhang/Desktop/"
    // 建个文件夹保存下载的文件
    path += "downFile" + DS
    // 判断文件夹是否存在,不存在就创建
    _ = createFile(path)
    // 根据顺序给ts文件命名方便后续处理
    fileName := make([]string, 0)
    for k, v := range tsArr {
        fileArr := StringToArray(v, "?")
        fn := fileArr[0]
        fn = ReplenishStr(k, "0", len(gconv.String(len(tsArr)))) + ".ts"
        fileName = append(fileName, path+fn)
    }
    // 创建一个等待组
    syncTime := sync.WaitGroup{}
    // 给等待组增加计数 按长度来添加
    syncTime.Add(len(tsArr))
    // 循环下载ts文件
    for k, v := range tsArr {
        // 启动协程同步下载
        go func(k int, v string) {
            // 这里根据需求自己处理 START
            fileArr := StringToArray(v, "?")
            fn := fileArr[0]
            fn = ReplenishStr(k, "0", len(gconv.String(len(tsArr)))) + ".ts"
            fmt.Println(tsUrl + v)
            // 这里根据需求自己处理 START
            // 下载文件 方法在其他文章
            _ = down.DownloadFile(path+fn, tsUrl+v)
            // 下载完减少计数
            syncTime.Done()
        }(k, v)
    }
    // 阻塞等待,直到计数未0全部下载完,不然协程一起动就退出了都没执行完
    syncTime.Wait()
    // 给新文件起个名字保存起来
    saveFile := path + gconv.String(grand.N(1000, 9999)) + ".mp3"
    // 循环合并ts文件,按fileName的顺序合并,不是合并后播放顺序也是乱的
    for _, f := range fileName {
        mergeFile(0, f, saveFile)
    }
    // 输出结果
    fmt.Println("文件合并完毕")
}

// mergeFile 合并切片文件 这里有兴趣的就自己研究
func mergeFile(i int, fileName, filePath string) {
    // 打开之前上传文件
    file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)
    defer file.Close()
    if err != nil {
        log.Fatal("打开下载文件不存在")
    }
    // 分片大小获取
    fi, _ := os.Stat(fileName)
    chunkSize := fi.Size()
    // 设置文件写入偏移量
    file.Seek(chunkSize*int64(i), 0)
    chunkFilePath := fileName
    chunkFileObj, err := os.Open(chunkFilePath)
    defer chunkFileObj.Close()
    if err != nil {
        log.Fatal("打开分片文件失败")
    }
    // 上传总数
    totalLen := 0
    // 写入数据
    data := make([]byte, 1024, 1024)
    for {
        tal, err := chunkFileObj.Read(data)
        if err == io.EOF {
            // 删除文件 需要先关闭改文件
            chunkFileObj.Close()
            err := os.Remove(chunkFilePath)
            if err != nil {
                fmt.Println("临时记录文件删除失败", err)
            }
            break
        }
        len, err := file.Write(data[:tal])
        if err != nil {
            fmt.Println(err.Error())
            log.Fatal("文件合并失败")
        }
        totalLen += len
    }
}

// HttpGet 发起http请求 具体包看其他文章
func HttpGet(url string, ua string) string {
    reqObj := httpReq.HttpReq()
    reqObj.SetHeader(map[string]string{
        "User-Agent": ua,
    })
    res, _ := reqObj.Get(url)
    return res
}

// IsDir 判断目录是否存在
func IsDir(fileAddr string) bool {
    s, err := os.Stat(fileAddr)
    if err != nil {
        log.Println(err)
        return false
    }
    return s.IsDir()
}

// StringToArray 字符串转数组
func StringToArray(str string, sep string) []string {
    if str == "" {
        return []string{}
    }
    return strings.Split(str, sep)
}

// GetCurrentPath 获取当前文件位置
func GetCurrentPath() (string, error) {
    file, err := exec.LookPath(os.Args[0])
    if err != nil {
        return "", err
    }
    path, err := filepath.Abs(file)
    if err != nil {
        return "", err
    }
    i := strings.LastIndex(path, "/")
    if i < 0 {
        i = strings.LastIndex(path, "\\")
    }
    if i < 0 {
        return "", errors.New(`error: Can't find "/" or "\".`)
    }
    return string(path[0 : i+1]), nil
}

// createFile 递归创建文件夹
func createFile(filePath string) error {
    if !IsDir(filePath) {
        err := os.MkdirAll(filePath, os.ModePerm)
        return err
    }
    return nil
}

// ReplenishStr 保存输出的名字长度,如1.ts 改成 00001.ts
func ReplenishStr(str interface{}, repStr string, amount int) string {
    newStr := gconv.String(str)
    strLen := len(newStr)
    if strLen < amount {
        for i := 0; i < amount-strLen; i++ {
            newStr = repStr + newStr
        }
    }
    return newStr
}
13.这样就完成了一个简单爬取

但是有很多网站ts文件是经过加密的且需要登录状态,合并后依旧无法使用
请移步另一篇文章: Golang爬取网站视频,音乐等(加密/登录)的m3u8格式媒体文件

4

评论 (0)

取消