头图

起源

  • 最近突然想尝试剪视频,所以就想先从动漫开始,二次元搞起来,剪视频就必须需要原视频,怎么找到这些资源呢,知乎一搜一大把
  • 我经常会上六DM 里面去看动漫,里面的动漫清晰度也还可以,所以就想怎么写个爬虫直接把喜欢的动漫下载下来,毕竟是干前端的,手动下载有点丢人把 😄😄😄

最终效果

效果图
效果图
效果图

下载后的文件名不是.mp4 怎么解决

比如说我下载的这个龙猫就是啥yum格式的,我直接后缀名改成.mp4 搞定,如果还不行,就上个格式工厂 应该就好了
效果图
效果图
效果图

作者在写这篇文章的技能和环境

  • 前端略懂一二
  • nodejs 一窍不通
  • window系统
  • puppeteer 版本: 14.3.0
  • node版本: 16.1.0

开始分析网站, 随便搜一个喜欢的动漫

介绍页
效果图

随便点击一个播放地址,F12 搞起来,分析页面

可以,这个网站还是会玩的,调试工具开起来就给我一直debugger
效果图

如何跳过debugger死循环

这种方式可以方便跳过死循环,并且我们还可以继续调试
效果图
效果图

找到页面播放地址

这个网站还是很简单的, 直接把资源地址丢到iframe里面而已,难度降低了,怪不得不给别人调试,😄😄
效果图

分析播放地址由来

思路1:通过接口请求分析,是否存在共通点

经过抓包,发现第一集和第二集的播放资源路径没有共同性可言,放弃o(╥﹏╥)o
效果图

思路2:直接看播放器的源码逻辑,找到url的拼接逻辑

找出播放器源码,直接通过调试工具找到所有的js文件,然后随便看看

效果图

分析播放器源码
读取播放器源码发现,这个网站会在页面里面存入一个全局变量
效果图
全局变量 player_aaaa
  • 效果图
  • 效果图
会做赋值存储,然后会引入一个js文件,文件名:/static/player/parse.js
  • 效果图
打开调试工具,找到/static/player/parse.js文件,
  • 效果图
/static/player/parse.js 文件内容
  • 效果图

    浏览器控制台输入:MacPlayer.Parse + MacPlayer.PlayUrl
    因为播放器的源码是一个自执行函数,然后我们又看到这个parse.js 文件里面的资源拼接方式,所以我们可以在浏览器的控制台里面直接把这个资源给拼出来
  • 效果图
右键保存?
把上面的地址放到浏览器里面访问,发现就是我们想下载的资源了, 到了这一步骤,我们就可以右键保存了,当然作为一名合格的前端,我们怎么可能会去右键保存呢,接下来我们就准备上大杀器,puppeteer 配合nodejs 来帮我们实现自动下载
资源demo
  • 效果图
如何做自动化?
  • 通过上面的连接,会进入到一个解析页面,因为我们要做成自动下载的,必须要找到视频源连接,否则不行,o(╥﹏╥)o
  • 查找元素页面,发现了最终的资源地址
  • 最终的资源地址
    效果图

使用puppeteer解析页面,获取到视频资源地址,然后使用nodejs自动下载视频

思路1:遍历出播放列表,然后开始一个任务,依次打开页面,找到资源地址,然后收集到所有的播放资源地址,使用nodejs下载到本地,

效果图

为啥不用上面那个思路,因为那个思路我把代码写完,测试了一下,发现他的服务器扛不住哈,所以还是保险点,一次一个操作

思路2:使用puppetee 自动触发右键下载,并保存到我们想要下载的地方(目前没有尝试这个方法)

思路3:遍历出播放列表,然后开始一个任务,从第一个开始,打开页面,找到资源地址,使用nodejs下载到本地,下载完成,开始下一个就这样

思路3 难点分析

pupeteer 如何获取元素的属性,别问我,反正我不懂, 堆栈溢出大佬告诉我的
// 获取单个
await page.evaluate('document.querySelector("span.styleNumber").getAttribute("data-Color")')
// 获取多个
const attr = await page.$$eval("span.styleNumber", el => el.map(x => x.getAttribute("data-Color")));
nodejs下载远端视频,并显示进度
const fs = require('fs');
const https = require('https')
// 我的demo使用的是axios 来下载
const axiosRequest = require('./utils/request');
// 这是一个axios实例
axiosRequest.get('https://media.w3.org/2010/05/sintel/trailer.mp4', {
  responseType: 'stream'
}).then(response => {
// 返回头里面的content-length字段,会告诉我们这个视频有多大
//  获取视频总长度 byte为单位
const totalLength = response.headers['content-length']
// 当前数据的总长度
let totalChunkLength = 0
// 当前读取的流
const readSteam = response.data
// 读取流会触发的事件
readSteam.on('data', (chunk) => {
totalChunkLength += chunk.length
console.log('数据传输中,当前进度==>', ((totalChunkLength / totalLength) * 100).toFixed(2) + '%')
  });
// 读取完成的时间
readSteam.on('end', (chunk) => {
console.log('获取远端数据完毕')
  });
// 读取错误会触发的事件
readSteam.on('error', (err) => {
console.log('获取远端数据完毕,发生了错误,错误信息==>', err)
  });
// 写入本地的文件名
const fileName = 67.mp4
// 调用nodejs写入文件方法
const writeFile = readSteam.pipe(fs.createWriteStream(fileName))
// 写入完成事件
writeFile.on("finish", () => {
writeFile.close();
console.log("恭喜大哥,本地数据写入完成");
    });
// 写入错误触发的事件
writeFile.on("error", (err) => {
console.log("不好意思,写入本地文件发生异常,错误信息==>", err);
  });
});
//axios 代码如下
const axios = require('axios')

// 创建axios实例
const service = axios.create({
 baseURL: '', // api 的 base_url
 // 永不凋谢,真男人 就是这么持久 😄😄
 timeout: 90000000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
 config => {

   return config
 },
 error => {
   // Do something with request error
   console.log(error) // for debug
   Promise.reject(error)
 }
)

// 响应拦截器
service.interceptors.response.use(
 response => {
   
     return response
 },
 error => {
     return Promise.reject(error)
 }
)


module.exports = service

完整代码

完整代码

免责声明

  • 首先 非常感谢这个网站让我这个老二次元能够找到喜欢的片源 😄😄😄
  • 本项目只是学习使用,无意对此网站进行爬虫等操作
  • 希望想用的同学自己玩玩就行,树大招风
  • 侵权请联系,立马删除
  • 就这些吧

参考资料

破解视频的另一个思路
puppeterr 获取元素属性
puppeterr 下载文件


qinyuanqiblog
20 声望3 粉丝

擅长摸鱼~