《高阶前端指北》之JavaScript爬虫速成-视频下载(第三弹)

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

视频存储

当前各大视频厂商都在升级自己的视频存储架构,对于多数开发者来说不像以前那么容易Get了。视频常规的存储方式有两种:

云端加密

阿里云和腾讯云都提供了对应的加密模式,甚至还提供了管道流的处理任务。你可以将录制好的视频上传到云端,云端可以办你实现:清晰分割,压缩,切割更易保存的ts文件,加密等服务。

以阿里云加密局里,此类加密最难破解,每个文件都有独立的加密钥匙,通过子账号+播放凭证管理权限,并且它的key存储在内存中。

image.png

m3u8

通过此种方式可以存储ts片段,分段下载,每个ts都拥有同一个解密key。此类加密方式破解相对容易一些,曲折点在于寻找m3u8的地址,然后通过视频处理工具ffmpeg下载并合并即可。

视频商转码技术

类似爱奇艺,优酷这些视频商,为了稳住饭碗,一般会通过转码方式加密。设计特定的格式和密钥技术,阻止破解。并且只有通过特定的客户端才能解密和解码视频。 非重点,不多说。

视频破解

我们以常见的m3u8的加密为例,刨析其核心技术点。通过观察m3u8文件,我们可以看到它存储了每个ts的文件名称,通过目标地址拼接就可以下载。

image.png

有很多直接解析m3u8的在线网站,甚至你在夜晚看的Video也是这样的地址。

你以为如此简单就太小看视频的攻防战了,多数情况下你看到的是这样的文件内容

image.png

这是因为他们对ts文件做了加密,需要在m3u8和JS代码中找到解密的key和方法。具体的需要根据每个站点不同来寻找。有兴趣可以看我之前基于python开发的Lagou爬虫库,不过目前他们也升级了加密算法。

解析m3u8

const fs  = require("fs");
var source = fs.readFileSync("aslkdas.m3u8","utf-8"); //读取 m3u8
var arr  = source.split("
");
arr = arr.filter((item)=>{
	return item.match(/.ts$/);
});
复制代码

down.js

const request = require("request");
const fs  = require("fs");
const path  = require("path");
const child_process = require('child_process');
const fsextra = require('fs-extra');

module.exports = function(opt){
	opt = opt || {};
	var arr = opt.arr || []; //所有 ts的文件名或者地址
	var host = opt.host || ""; //下载 ts 的 域名,如果 arr 里面的元素已经包含,可以不传
	var outputName = opt.name ||  `output${(new Date()).getTime()}.mp4`; //导出视频的名称
	
	const tsFile = path.join(__dirname,`./source/${arr[0].split(".")[0]}`,);
	createDir(tsFile);//递归创建文件
	console.log("本次资源临时文件:",tsFile);
	
	const resultDir = path.join(__dirname,"./result");
	createDir(resultDir);//递归创建文件
	const resultFile = path.join(resultDir,outputName);
	
	var localPath = [] ; //下载到本地的路径
	//开始下载ts文件
	load();
	function load(){
		if(arr.length > 0){
			var u =  arr.shift();
			var url = host + u;
			console.log("progress---:",url);
			down(url);
		}else{
			//下载完成
			console.log("下载完成--开始生成配置");
			localPath.unshift("ffconcat version 1.0");
			try{
				fs.writeFileSync(path.join(tsFile,"./input.txt"), localPath.join("
") , undefined, 'utf-8')
			}catch(e){
				console.log("写入配置出错--",e);
				return ;
			}
			
			//开始依赖配置合成
			console.log("开始合成-----");
			child_process.exec(`cd ${tsFile} &&  ffmpeg -i input.txt -acodec copy -vcodec copy -absf aac_adtstoasc ${resultFile}`,function(error, stdout, stderr){
				if(error){
					console.error("合成失败---",error);
				}else{
					console.log("合成成功--",stdout);
					//删除临时文件
					fsextra.remove(tsFile, err => {
					  if (err) return console.error("删除文件是失败",err)
					  console.log('删除文件成功!')
					});
				}
			});
		}
	}
	
	//下载 ts 文件
	function down(url){
		var p = url.split("?")[0];
		var nm = path.parse(p);
		var nme = nm["name"] + nm["ext"];
		rpath = path.join(tsFile,nme);
		
		localPath.push(`file ${nme}`); //缓存本地路径,用来合成
		
		request({
		    url:url,
		    headers:{
		        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
		        'X-Requested-With': 'XMLHttpRequest'
		    }
		},function (err, response, body) {
	        if (!err && response.statusCode == 200) {
				load();
	        }else{
				console.log("错误",err)
			}
	    }).pipe(fs.createWriteStream(rpath));
	}
	
	

	//递归的创建文件夹
	function mkdirs(dirpath) {
	    if (!fs.existsSync(path.dirname(dirpath))) {
	      mkdirs(path.dirname(dirpath));
	    }
	    fs.mkdirSync(dirpath);
	}
	   
	function createDir(myPath){
	    fs.existsSync(myPath) == false && mkdirs(myPath);
	}
}

// ffmpeg -i input.txt -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4

复制代码

最后我们调用即可。

image.png

总结

本节课有些含蓄,毕竟学习为主很多地方只能看破不说破,大家都是搞研发的。只要知其一便能破其百,只要理解了和技术点,对于研发来说语言只是一个工具而已。

下节课,我会空开一个基于js的爬虫脚手架,便于各位同学学习和研究爬虫的原理。

如果喜欢我的文章,麻烦点个赞评个论收个藏关个注

手绘图,手打字,纯原创,摘自未发布的书籍:《高阶前端指北》,转载请获得本人同意。

猜你喜欢

转载自juejin.im/post/7133537085582999565