Nodejs中使用无头浏览器批量下载音频

背景

本来我在A平台听书,到了后面的章节,便只能去B平台。
操作繁琐,体验极差,还不支持缓存,流量超贵的(上个月套餐外流量消费60多)
于是乎,我只能把音频手动下载起来,但操作同样繁琐。

需求

下载一个音频的操作如下

  1. 进入列表页,可能需要滚动加载
  2. 进入播放页面,打开了一个新的标签,点击"播放按钮"
  3. 打开发者工具,找到audio标签,不点"播放按钮"audio标签不出来
  4. 复制链接到新的标签页打开
  5. 通过浏览器自带的功能下载,需要点两下
  6. 等待下载完成
  7. 去另一个标签页复制音频的名字,
  8. 进入下载内容的文件夹,然后修改名字,不然都是一个名字,没法区分

这个过程还是很繁琐的,下载五六个之后已经很痛苦了,还有两百多个章节....

实现

我首先想到了无头浏览器,一番折腾后,断断续续后,终于解决,具体的音频地址便不透露了,大同小异。

const puppeteer = require("puppeteer");
const axios = require("axios");
const querystring = require("querystring");
const fs = require("fs");

puppeteer 可以理解为一个"无头浏览器",通过代码的形式完成人机交互。
中文文档: http://www.puppeteerjs.com/#?...

(async () => {
  const audioInfoList = [];
  const [
    reqUrl,
    search,
  ] = "分页获取列表信息的接口地址".split(
    "?"
  );

"分页获取列表信息的接口地址" 是借助开发者工具拿到的,尽量减少对人机交互的模拟,不然这里还要鼠标滚动来触发上拉加载。

  let params = Object.assign({}, querystring.parse(search), {
    begin: 20,
    count: 10,
  });

参数的替换

  const audioRsp = await axios.post(
    reqUrl + "?" + querystring.stringify(params)
  );

  const audioInfoList = audioRsp.data.appmsg_list.map((item) => ({
    name: item.title,
    url: item.link,
  }));

发起ajax请求,记录音频的名字以及音频播放页地址

  for (let i = 0; i < audioInfoList.length; i++) {
    const audioItem = audioInfoList[i];
    try {
      const browser = await puppeteer.launch();

      const page = await browser.newPage({});
      await page.goto(audioItem.url, {
        waitUntil: "networkidle0",
      });
  1. 创建一个浏览器
  2. 创建一个标签页
  3. 跳转到音频播放页

       page.waitForSelector(".audio_card_switch");
       await page.click(".audio_card_switch");

    等待 .audio_card_switch 代表的"播放按钮"出现再点击,因为"播放按钮"需要某个网络请求才会出现。

      const downloadSrc = await page.evaluate(() => {
        return document.querySelector("audio").src;
      });
      const audioContent = await axios.get(downloadSrc, {
        responseType: "arraybuffer",
      });
      fs.writeFileSync(`download/${audioItem.name}.mp3`, audioContent.data);
      console.log(
        `${i}/${audioInfoList.length - 1}  ${audioItem.name} : ${downloadSrc}`
      );
      await browser.close();
    } catch (error) {
      console.log(
        "下载失败:" + i + " " + audioItem.name + "  " + audioItem.url
      );
    }
  }
  1. 获取音频地址
  2. 通过ajax请求获取音频内容,注意 responseType: "arraybuffer",是必须的,否则音频无法播放,你可能得到"此文件无法播放。这可能是因为文件类型不受支持、文件扩展名不正确或文件已损坏。0xc00d36c4“的错误
  3. 将音频文件存储到本地。

下载方式

for (let i = 0; i < audioInfoList.length; i++) {,下载完一个再去下载下一个,这样一次性可以把当前页的音频下载完,经实验证明,这样要好于一次下载多个 audioInfoList.forEach(async (audioItem, audioIndex) => { ,后者很吃"资源",下载顺序也是乱的,出现下载失败的几率高,一旦出现也不好排查是哪个出了问题,目录中有的就不是音频。

0xc00d36fa

"找不到音频设备。请确保耳机或扬声器已连接。有关更多信息,请在设备中搜索“管理音频设备"。

不要把这个错误当成下载的音频文件有问题。

你可能感兴趣的:(前端node.js爬虫)