使用 puppeteer + nodejs 爬取喜歡的動漫資源

qinyuanqiblog發表於2022-06-11

起源

  • 最近突然想嘗試剪視訊,所以就想先從動漫開始,二次元搞起來,剪視訊就必須需要原視訊,怎麼找到這些資源呢,知乎一搜一大把
  • 我經常會上六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 下載檔案

相關文章