前言
最近发现一些比较有意思的视频,于是便想着将视频下载下来,虽然B站客户端有提供缓存,但是会在视频中加入一些不必要的元素
因此就敲了一个爬取工具
Dev
大概的流程是这样的
- 通过 API 拿到视频源地址
- 下载 flv 到服务器 (需要处理防盗链
- 通过 ffmpeg 将 flv 转成 mp4
- 再将 mp4 写入 response 进行回传
ffmpeg 需要提前安装好 https://ffmpeg.org/
保存视频时的命名使用到了B站的标题,因此可能会存在名字包含特殊字符的情况,比如说 '/' 会被识别成目录,对于这种情况则需要将其转换成其它字符
且不支持空格(空格大概是因为 ffmpeg 的缘故,因为win下正常,win和linux下的 ffmpeg 貌似也有所区别 💦
Go实现
package main
import (
"github.com/W1llyu/ourjson"
"github.com/kataras/iris/v12"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
)
// CidApi 通过此API获取cid
var CidApi = "https://api.bilibili.com/x/player/pagelist"
// VideoUrlApi 通过此API获取视频Url,格式为flv
var VideoUrlApi = "https://api.bilibili.com/x/player/playurl"
// VideoInfoApi 视频信息
var VideoInfoApi = "https://api.bilibili.com/x/web-interface/view"
// RootPath 项目路径
var RootPath, _ = filepath.Abs(path.Dir(os.Args[0]))
// BiliVideoStorageOriginal 存放原始视频 - .flv
var BiliVideoStorageOriginal = RootPath + "/BiliVideoStorageOriginal/"
// BiliVideoStorage 存放转格式后的视频 - .mp4
var BiliVideoStorage = RootPath + "/BiliVideoStorage/"
type VideoInfo struct {
Title string
Auth string
Uid string
}
func BiliVideo(ctx iris.Context) {
defer ErrorHandle(ctx)
//pwd := ctx.URLParam("pwd")
//if pwd != "147526" {
// panic("密码有误喵!")
//}
// BV号
bvCode := ctx.URLParam("bv")
if bvCode == "" {
panic("BV号有误喵!")
}
log.Warning("开始下载喵 - " + bvCode)
cid := GetCid(bvCode)
if cid == "" {
panic("BV号有误喵!")
}
url := GetVideoUrl(bvCode, cid)
if url == "" {
panic("BV号下无视频喵!")
}
videoInfo := GetVideoInfo(bvCode)
log.Info("【视频标题】 " + videoInfo.Title)
log.Info("【视频作者】 " + videoInfo.Auth)
log.Info("【作者UID】 " + videoInfo.Uid)
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Referer", "https://www.bilibili.com/")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
timeStr := strconv.FormatInt(time.Now().Unix(), 10)
// 创建flv文件
fileName := videoInfo.Title + "【" + videoInfo.Auth + "】" + "【" + videoInfo.Uid + "】" + "【" + timeStr + "】"
originalPath := BiliVideoStorageOriginal + fileName + ".flv"
original, _ := os.Create(originalPath)
err := original.Close()
if err != nil {
return
}
// 写入
originalFile, _ := os.OpenFile(originalPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
defer func(originalFile *os.File) {
err := originalFile.Close()
if err != nil {
}
}(originalFile)
_, err = originalFile.Write(body)
if err != nil {
return
}
filePath := BiliVideoStorage + fileName + ".mp4"
// 使用ffmpeg转换格式
// win
cmd := exec.Command("ffmpeg", "-i", originalPath, filePath)
// linux
// cmd := exec.Command("/bin/sh", "-c", "ffmpeg -i " +originalPath+ " -c:v copy -c:a copy "+filePath)
//执行
err = cmd.Run()
if err != nil {
return
}
// 返回.mp4
file, err := os.Open(filePath)
defer func(file *os.File) {
err := file.Close()
if err != nil {
}
}(file)
fileContent, _ := ioutil.ReadAll(file)
ctx.ContentType("video/mp4")
ctx.Header("Content-Disposition", "attachment; filename="+videoInfo.Title+".mp4")
_, err = ctx.Write(fileContent)
if err != nil {
return
}
log.Info("下载完成喵!")
log.Warning("============= ✨ ============")
}
func GetCid(code string) string {
api := CidApi + "?bvid=" + code + "&jsonp=jsonp"
res, _ := http.Get(api)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
}(res.Body)
body, _ := ioutil.ReadAll(res.Body)
jsonStr := string(body)
jsonObject, _ := ourjson.ParseObject(jsonStr)
data := jsonObject.GetJsonArray("data")
videoData := data.GetJsonObject(0)
cid, _ := videoData.GetInt("cid")
return strconv.Itoa(cid)
}
func GetVideoInfo(code string) VideoInfo {
var info VideoInfo
api := VideoInfoApi + "?bvid=" + code
res, _ := http.Get(api)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
}(res.Body)
body, _ := ioutil.ReadAll(res.Body)
jsonStr := string(body)
jsonObject, _ := ourjson.ParseObject(jsonStr)
data := jsonObject.GetJsonObject("data")
info.Title, _ = data.GetString("title")
if strings.Contains(info.Title, "\\") || strings.Contains(info.Title, "/") {
info.Title = strings.ReplaceAll(info.Title, "\\", "💢")
info.Title = strings.ReplaceAll(info.Title, "/", "💢")
}
owner := data.GetJsonObject("owner")
info.Auth, _ = owner.GetString("name")
uid, _ := owner.GetInt("mid")
info.Uid = strconv.Itoa(uid)
return info
}
func GetVideoUrl(bvCode, cid string) string {
api := VideoUrlApi + "?avid=&cid=" + cid + "&bvid=" + bvCode + "&qn=120&type=&otype=json"
res, _ := http.Get(api)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
}(res.Body)
body, _ := ioutil.ReadAll(res.Body)
jsonStr := string(body)
jsonObject, _ := ourjson.ParseObject(jsonStr)
data := jsonObject.GetJsonObject("data")
durl := data.GetJsonArray("durl")
durl0 := durl.GetJsonObject(0)
url, _ := durl0.GetString("url")
return url
}
Comments | NOTHING