携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情
事件篇
之前关注的一个B站up主,由于视频总喜欢“开团”,导致视频经常失效,常常是点开B站消息,发现大大的“视频已失效”,因此,我用nodejs写了个定时任务,每半分钟跑一遍他的视频列表接口,一旦发现就用you-get下载下来(顺便存进数据库里):
// 部分代码,这个今天不是重点就不全部展示了
// 查新视频的方法
function getVideoInfo(mid, pn = 1, ps = 30) {
return new Promise(resolve => {
connectDB().then(async() => {
var config = {
method: 'get',
url: `https://api.bilibili.com/x/space/arc/search?mid=${mid}&ps=${ps}&tid=0&pn=${pn}&keyword=&order=pubdate`,
headers: {}
};
queryDocument(BILIBLI_DATABASE, VIDEO_COLLECTION, {mid: mid}).then(vlist => {
axios(config)
.then(function (response) {
var newVlist = response.data.data.list.vlist.map(item => {
return {
mid: mid,
title: item.title,
description: item.description,
pic: item.pic,
aid: item.aid,
bvid: item.bvid,
length: item.length,
created: new Date(item.created * 1000),
cache: false, // 新加入的默认没缓存
}
});
if (!newVlist.length) {
logInfo(`${mid} 妹有视频`)
closeDB();
return;
}
var videoToWrite = [];//记录需要写入的新纪录
newVlist.forEach(item => {
// 根据时间判断记录是否重复
if (!vlist.find(e => e.created.getTime() == item.created.getTime())) {
videoToWrite.push(item);
}
})
if (videoToWrite.length) {
addManyDocument(BILIBLI_DATABASE,VIDEO_COLLECTION,videoToWrite).then(() => {
closeDB();
resolve();
});
}
else {
logInfo(`${mid} 无新视频`)
closeDB();
}
})
.catch(function (error) {
logError(error);
});
}).catch(err => {
logError({message:"**数据库又连不上了",err})
})
}).catch(err => {
logError({message:"**数据库又连不上了",err})
});
})
}
// 使用you-get下载的方法
function downloadBilibiliVideo(bvid, timeStamp) {
return new Promise(function (resolve, reject) {
var cmd = `you-get -o "${DOWNLOAD_PATH}" -O ${bvid}-${timeStamp || new Date().getTime()} "https://www.bilibili.com/video/${bvid}" "--no-caption"`;
exec(cmd, {
maxBuffer: 1024 * 8000
}, function (err, stdout, stderr) {
if (err) {
console.log(err);
reject(err);
} else if (stderr.length > 0) {
reject(new Error(stderr.toString()));
} else {
resolve();
}
});
});
};
我把这个程序跑在我的ECS上,那个幼小的、硬盘只有60G的ECS终于受不了这庞大的数据,吃不消了(那个up投稿数快500了):
瘦身势在必行!
解决篇
定期手动上传到百度网盘?不行:
- 太笨了
- 一不小心还给你整失效了
- 太慢了,不仅是下载还有上传(没会员没人权)
- 不支持外链,我特意建了数据库是为可以随时通过http访问的
既然要支持上面几个条件,那么解决这个问题的选择就挥之欲出了:
OSS对象存储
经过比较,我选了七牛云,支持cdn,也支持自定义域名,那么,接下来就是将缓存的文件自动上传到OSS了,这里用到了七牛云的nodejsAPI,文档在此
由于程序都运行在我自己后台的node服务中,不担心ak泄露的问题,因此我选择了服务器直传的方式(否则就只能采用服务端生成凭证,客户端使用凭证进行上传的流程):
首先,完成鉴权,使用从控制台中获取的accessKey和secretKey生成鉴权对象mac:
var accessKey = '';
var secretKey = '';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
之后,传入参数,一个最简单的参数就是存储桶名称scope:
var options = {
scope: "bilibili_video",
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken = putPolicy.uploadToken(mac);
继续,传入配置,构建文件上传对象:
var config = new qiniu.conf.Config();
// 空间对应的机房,具体变量名和机房对应可以去文档找
config.zone = qiniu.zone.Zone_z3;
// 是否使用https域名
config.useHttpsDomain = true;
// 上传是否使用cdn加速
config.useCdnDomain = true;
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();// 这个用来设置额外参数,比如mimeType之类的,类似于用form-data方式上传文件
最后,使用构建好的上传对象上传文件即可:
formUploader.putFile(uploadToken, key, filePath, putExtra, function (respErr,
respBody, respInfo) {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
resolve(respBody)
logInfo(respBody, "OSS")
} else {
// console.log(respInfo.statusCode);
// console.log(respBody);
reject();
logError(respInfo.statusCode, "OSS")
logError(respBody, "OSS")
}
});
整个代码的全貌如下:
const qiniu = require("qiniu");
const { logInfo, logError } = require("./log");
const accessKey = '';
const secretKey = '';
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
function uploadFile(filePath, key, bucket = "bilibili-video", rewrite = false) {
return new Promise((resolve, reject) => {
var options = {
scope: `${bucket}${rewrite ? (":" + key) : ""}`,
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken = putPolicy.uploadToken(mac);
var config = new qiniu.conf.Config();
// 空间对应的机房
config.zone = qiniu.zone.Zone_z3;
// 是否使用https域名
config.useHttpsDomain = true;
// 上传是否使用cdn加速
config.useCdnDomain = true;
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
// 文件上传
formUploader.putFile(uploadToken, key, filePath, putExtra, function (respErr,
respBody, respInfo) {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
resolve(respBody)
logInfo(respBody, "OSS")
} else {
// console.log(respInfo.statusCode);
// console.log(respBody);
reject();
logError(respInfo.statusCode, "OSS")
logError(respBody, "OSS")
}
});
});
}
module.exports = { uploadFile }
值得一提的是,要覆盖文件的时候,可以在scope参数中在原来存储桶名的结尾加上冒号:和文件的key,即可实现上传覆盖原文件。