之前买了很多语音课,快要过期了,然而。。。我还听完,=。=,只能先下载,然后再听了。
我爬虫技术弱的很,试了几次,纯代码层面下载不行,只能手动+代码的形式下载,以后会了,可能就不会这么麻烦了。
于是,一波三折的下载了语音课。
主要以下步骤:
- 找到列表接口
- 手动在浏览器里翻页,将每页的数据粘贴在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