一、echo
Echo回声.记得14年的时候就有了,它家最出名的就是独家首创3D音乐、和立体环境音.
好久没听了,刚好下几首歌到HIFI清一波耳朵.
找了半天找不到下载口,外放可没有推耳屎感.
没关系我们切换到移动端下载,再导入到hifi中.
好家伙一首歌5个币.
本想以普通人的身份相处,没办法
只能把发际线露出来了.
echo回声搜索 凳子骑,随便点一首歌进去
点击播放按钮加载音频文件
F12开发者模式
找到Network下选项卡Media
找到较大size文件 打开新的窗口
右键另存为
搞定
本次教程结束
开玩笑的...
我还没敲代码呢
如果只是少量歌曲需要的话以上步骤就够了
先上成果展示图
只需键入
1.歌曲名/作者名
2.加载的页数(1页大概8~9首歌)
即可实现批量下载
二、思路
逆推
首先查看最里面详情页body代码
信息少的可怜
页面是由前端渲染完成的
查看xhr请求
这里省略一段时间
·
·
·
不费吹灰之力找到了.............
还是比鹅厂的简单的多
在info?id=749415&comment=1响应结果,info下的source
观察一下请求头
再随便打开一首歌,查看query
接下来就是大家来找茬时间.......
很简单query中有id,comment两个参数
comment是评论的意思
试着把comment去掉直接打开这段链接
果然,依然可以获得数据,并且有我们想要的source
所以ID起请求数据的决定性因素
继续逆推回到搜索页
我的眼睛........
找到了
在sound?keyword=******响应中
data列表下面有歌曲id
拼接id和链接可以发送请求获得歌曲的source下载
http://www.app-echo.com/api/sound/info?id=
那id列表是怎么获得的呢?
观察sound?keyword***请求头
http://www.app-echo.com/api/search/sound?keyword=%E9%82%93%E7%B4%AB%E6%A3%8B&page=1&limit=10&src=0
下拉网页列表点击更多
网页又发送了一次ajax请求
查看请求头
http://www.app-echo.com/api/search/sound?keyword=%E9%82%93%E7%B4%AB%E6%A3%8B&page=2&limit=10&src=0
观察时间..........
把keyword参数后的编码%E9%82%93%E7%B4%AB%E6%A3%8B
用UrlEncode解码
好的,结束了
keyword参数为搜索内容
page 为搜索页面
通过url
http://www.app-echo.com/api/search/sound?
keyword=encodeURI(邓紫棋)
&page=?&limit=10&src=0
即可获取歌曲id,通过歌曲id获取source,下载音乐
三、代码实现
只需一个axios库即可完成简单的爬取音乐
注意事项
- try-catch捕获加载失败发出的异常
- await等待数据写入完成
- 正则过滤歌曲名的特殊符号
- 避免使用forEach迭代,unexpected error
- axios 设置timeout,也可配置代理
const axios = require("axios")
const fs = require("fs")
const path = require("path")
const readline = require("readline")
async function echo() {
// 获取页面列表
const pagesList = await getPagesList()
const songsList = await getSongsList(pagesList)
download(songsList)
}
// 创建readline 接口实例
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
async function getPagesList() {
const pagesList = []
const songTitle = await readLine("请输入歌曲名称/歌手?\n")
const pages = await readLine("请输入要爬取的页面数?\n")
// 关闭 readline
rl.close()
// 创建 music目录
fs.mkdir("./music", (err) => {})
// 创建 分类目录
fs.mkdir(`./music/${songTitle}`, (err) => {})
for (let i = 1; i <= pages; i++) {
const url = `http://www.app-echo.com/api/search/sound
?keyword=${encodeURI(songTitle)}
&page=${i}&limit=10&src=0`.replace(/[\s\n]/gis, "")
pagesList.push(url)
}
return pagesList
}
async function getSongsList(pagesList) {
const songsList = []
for (let i = 0; i < pagesList.length; i++) {
// 捕获请求列表异常
try {
const res = await axios({
url: pagesList[i],
timeout: 5000,
// 加速请求代理,可不设置
// proxy:{
// host:"112.74.200.147",
// port:80
// }
})
const keyword = res.data.keyword
const list = res.data.data
const obj = {
keyword,
list,
}
songsList.push(obj)
console.log(`get Pages url${i + 1} Successful\n`)
} catch (err) {
console.log(`get Pages url${i + 1} Failure\n`)
continue
}
}
return songsList
}
async function download(list) {
const keyword = list[0].keyword
for (let c of list) {
const songsList = c.list
for (let j = 0; j < songsList.length; j++) {
// filter 歌曲名中特殊符号
const reg = /[/\\:*?"<>|\s]/gis
const songTitle = songsList[j].name.replace(reg, "")
// get 音频文件url
const songSource = songsList[j].source
// 捕获歌曲source异常
try {
await downloadSongs(songSource, keyword, songTitle)
console.log(`${songTitle} Download Successful\n`)
} catch (err) {
console.log(`${songTitle} Download Failure\n`)
}
}
}
}
async function downloadSongs(songSource, keyword, songTitle) {
// get 音频文件后缀名
const _reg = /[^?]*/
const extName = _reg.exec(path.extname(songSource))[0] || ".mp3"
const res = await axios({
url: songSource,
timeout: 3000,
responseType: "stream",
})
// build read stream
const rs = res.data
// build write stream
const ws = fs.createWriteStream(`./music/${keyword}/${songTitle}${extName}`, {
emitClose: true,
autoClose: true,
})
rs.pipe(ws)
// 等待歌曲写入完成
await new Promise((resolve) => {
rs.on("end", () => {
ws.end()
resolve()
})
})
}
async function readLine(question) {
return new Promise((resolve) => {
rl.question(question, (res) => {
resolve(res)
})
})
}
echo()