快过期的语音课,尽快听和下载...

255 阅读3分钟

之前买了很多语音课,快要过期了,然而。。。我还听完,=。=,只能先下载,然后再听了。

我爬虫技术弱的很,试了几次,纯代码层面下载不行,只能手动+代码的形式下载,以后会了,可能就不会这么麻烦了。

于是,一波三折的下载了语音课。

主要以下步骤:

  • 找到列表接口
  • 手动在浏览器里翻页,将每页的数据粘贴在js文件里
  • 合并每页的数据,从中过滤出有语音的数据
  • 用 node 进行批量下载

找到列表接口

  • 登录语音课账号,正序排列

  • network里找到列表接口(找了一会),/column_more_data_v2/column_more_data

  • network设置过滤关键词,column_more_data

列表数据复制到文件里

估测了下最多 22 页数据,所以需要新建 22 个 js 文件,方便后期管理。

批量生成 js 文件:

// node create-file.js
const pageCount = 22
const fileContent = 'module.exports = {}'
const createFile = (pageCount) => {
  const fs = require('fs')
  const arr = new Array(pageCount).fill(1)
  arr.forEach((item, index) => {
    fs.writeFileSync(`./data/p${index + 1}.js`, fileContent, 'utf8')
  })
}
createFile(pageCount)

这样data文件夹里,p1-p22 的文件就准备好了。

然后,手动在network里,点击接口,复制数据到相应的文件里,滚动页面,再重复上述操作。

合并所有页面的数据

data文件夹里,建一个index.js,将所有页数的数据,放进来。

// index.js
const p1 = require(p1);
// ...重复require
const p22 = require(p22);
// ...重复p2,p3
module.exports = [p1,...,p22]

require 语句和 exports 里导出的代码重复度高,用代码生成的方式

const pageCount = 22;
let arr = new Array(pageCount).fill(1);
let requireStr = arr.reduce(
  (acc, cur, index) =>
    (acc += `const p${index + 1} = require("./p${index + 1}");`),
  ""
);
let moduleStr = arr.reduce(
  (acc, cur, index) => (acc += `p${index + 1},`),
  "module.exports=["
);
moduleStr += "]";
let indexFileStr = requireStr + moduleStr;
// 这就是index.js文件里面的内容
console.log(indexFileStr);

过滤出带语音的数据

所有的数据都已经具备,然后将带语音的数据单独过滤出来。 其实也比较简单,这边只要有audio_compress_url就判断是语音课。

// audio-arr.js
const allNativeData = require("./index");
// 过滤出有效的页码数据。避免部分页码可能没有数据
let arrData = allNativeData.filter((item) => item.data);
// 将每页的数据列表拿出来,之前每页的数据结构是{code:1,data:{contentData:{contentInfo:[课的数据]}}}
arrData = arrData.map((item) => item.data.contentData.contentInfo);
// 页码之间的数据合并,[{id:'xx',title:'',audio_compress_url:''}]
arrData = arrData.reduce((acc, cur) => acc.concat(cur), []);
// 有audio的数据
const audioArr = arrData.filter((item) => item.audio_compress_url);
module.exports = audioArr;

写批量下载的方法

先建一个文件夹,npm init -y;npm i request async;mkdir dist

写一个下载单个文件的方法,主要用到request

const request = require('request')
const fs = require('fs')
// 文件的网上地址,文件的本地地址
const downloadSingle = (fileUrl, localPath, callback) => {
  localPath = localPath || fileUrl.split('/').slice(-1)[0]
  request.head(fileUrl, (err, res, body) => {
    if (err) {
      console.log(err)
      return
    }
    fileUrl &&
      request(fileUrl)
        .pipe(fs.createWriteStream(localPath))
        .on('close', () => {
          callback && callback(null, localPath)
        })
  })
}
// 下面是demo,打开注释之后,执行此文件 node download-single.js,便看到项目根目录有x.jpg了
// const fileUrl = 'https://article-fd.zol-img.com.cn/t_s640x2000/g1/M03/02/02/ChMljl2ENKuIV553AAKEYP9wZOQAAP23wGEctEAAoR4006.jpg'
// downloadSingle(fileUrl, 'x.jpg')
module.exports = downloadSingle

批量下载用到async,主要遍历数组。

逻辑不复杂,直接写download-batch.js的代码了

const async = require('async')
const path = require('path')
const downloadSingle = require('./download-single')

// 根据文件的url地址得到后缀,就是.jpg .mp3之类的
const getSuffix = (str) => str.slice(str.lastIndexOf('.'))

// 批量下载文件,文件的数据格式是 [{url:'xx',title:'xx'}]
const downloadBatch = (
  fileJsonArr,
  localDirPath = './dist',
  urlKey = 'url',
  titleKey = 'title'
) => {
  async.mapSeries(fileJsonArr, (item, callback) => {
    const downFile = () => {
      let fileName = `${item[titleKey]}${getSuffix(item[urlKey])}`
      let filePath = `${localDirPath}/${fileName}`
      downloadSingle(item[urlKey], filePath, (err, data) =>
        err ? console.log(err) : console.log(path.resolve(data))
      )
      callback && callback(null, item)
    }
    // 一般接口需要加延迟,不然可能加大服务器压力
    setTimeout(downFile, 200)
  })
}
// 下面是demo,打开注释之后,执行此文件 node download-batch.js,便看到dist有两个图片了了
/*
const fileJsonArr = [
  {
    title: '这才是未来大屏该有的样子',
    url:
      'https://article-fd.zol-img.com.cn/t_s640x2000/g1/M03/02/02/ChMljl2ENKuIV553AAKEYP9wZOQAAP23wGEctEAAoR4006.jpg'
  },
  {
    title: '智慧屏',
    url:
      'https://article-fd.zol-img.com.cn/t_s640x2000/g1/M05/01/06/ChMljV1_QwqIEEeTACmpHidBeUkAAPz4QMudwgAKak2877.jpg'
  }
]

downloadBatch(fileJsonArr, './dist', 'url', 'title')
*/
module.exports = downloadBatch

批量下载语音

  • 之前的data文件夹拷贝过来
  • 新建exec-down.js,复制以下代码,然后执行此文件node exec.down.js
let fileDataArr = require('./data/audio-arr')
let downloadBatch = require('./downloadBatch')
downloadBatch(fileDataArr,'./dest','audio_compress_url')

然后就发现dest文件夹里有想要的语音或者其他文件了

使用dlfile

为了方便操作,上传了一个dlfile的npm包

npm i dlfile -g

可以直接命令行使用,dlfile https://article-fd.zol-img.com.cn/t_s640x2000/g1/M05/01/06/ChMljV1_QwqIEEeTACmpHidBeUkAAPz4QMudwgAKak2877.jpg

也可以 dlfile('https://article-fd.zol-img.com.cn/t_s640x2000/g1/M05/01/06/ChMljV1_QwqIEEeTACmpHidBeUkAAPz4QMudwgAKak2877.jpg')

这样批量下载可以变成


const async = require('async')
const downloadFile = require('dlfile')
const process = require('process')

const cwd = process.cwd()
// 批量下载文件,文件的数据格式是 [{url:'xx',title:'xx'}]
const downloadBatch = (
  data,
  dir = cwd,
  urlKey = 'url',
  titleKey = 'title'
) => {
  async.mapSeries(data, (item, callback) => {
    if (!item[urlKey]) {
      return
    }
    const fn = () => {
      downloadFile(item[urlKey], dir, item[titleKey])
      callback && callback(null, item)
    }
    // 一般接口需要加延迟,不然可能加大服务器压力
    setTimeout(fn, 200)
  })
}
// 下面是demo,打开注释之后,执行此文件 node download-batch.js,便看到dist有两个图片了了
// downloadBatch(data,'./dist') 
// $ node xx.js
module.exports = downloadBatch

在线将m4a转化成mp3