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后的结果,然后去桌面找到文件就可以播放啦
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格式媒体文件
评论 (0)