专栏首页京程一灯用 Node.js 爬虫下载音乐

用 Node.js 爬虫下载音乐

每日前端夜话第312篇

翻译:疯狂的技术宅

作者:Sam Agnew

来源:twilio.com

互联网上有许多可供人类消费的信息。但是如果这些数据不是以专用的 REST API 的形式出现,通常很难以编程方式对其进行访问。使用 jsdom 之类的 Node.js 工具,你可以直接从网页上抓取并解析这些数据,并用于你自己的项目和应用。

让我们以用 MIDI 音乐数据来训练神经网络 来生成听起来经典的任天堂音乐【https://www.twilio.com/blog/generating-nintendo-music-over-the-phone-with-magenta-and-twilio】为例。我们需要一套来自旧任天堂游戏的 MIDI 音乐。通过使用 jsdom 可以从视频游戏音乐档案(https://vgmusic.com/music/console/nintendo/nes/)中抓取这些数据。

入门和依赖项设置

在继续之前,你需要确保自己有 Node.js 和 npm 的最新版本。

切换到你希望此代码存在的目录,并在终端中运行以下命令创建项目的程序包:

npm init --yes

--yes 参数可以忽略所有你必须填写或跳过的提示。现在我们的程序有了 package.json

为了通过发出 HTTP 请求从网页获取数据,我们将使用 Got 库,对于 HTML 的解析,我们将用 Cheerio。

在终端中运行以下命令安装这些库:

npm install got@10.4.0 jsdom@16.2.2

jsdom 是大量 Web 标准的纯 JavaScript 实现,也是许多 JavaScript 开发人员熟悉的工具。让我们深入了解该如何使用它。

用 Got 检索要与 jsdom 一起使用的数据

首先让我们编写一些从网页中获取 HTML 的代码,然后看看如何开始解析。以下代码将向我们想要的网页发送一个 GET 请求,并使用该页面的 HTML 创建一个 jsdom 对象,我们将其命名为 dom

const fs = require('fs');
const got = require('got');
const jsdom = require("jsdom");
const { JSDOM } = jsdom;

const vgmUrl= 'https://www.vgmusic.com/music/console/nintendo/nes';

got(vgmUrl).then(response => {
  const dom = new JSDOM(response.body);
  console.log(dom.window.document.querySelector('title').textContent);
}).catch(err => {
  console.log(err);
});

当向构造函数 JSDOM 传递一个字符串时,将返回一个 JSDOM 对象,你可以从中访问许多可用的属性,例如 window。如该代码所示,你可以用查询选择器(query selector)。

例如 querySelector('title').textContent 将获取页面上 <title> 标记内的文本。如果将此代码保存到名为 index.js 的文件并用命令 node index.js 运行,它会把网页的标题记录到控制台。

通过 jsdom 使用 CSS 选择器

如果你想在查询中获得更具体的信息,可以用 HTML 解析器(https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)进行解析。最常见的两个方法是按 class 或 ID 获取。如果要获取 ID 为 “menu” 的div,则可以用 querySelectorAll('#menu'),并且如果要获取 VGM MIDI 表格中的所有标题列,则可以执行 querySelectorAll('td.header')

我们在此页面上想要的是我们需要下载的所有 MIDI 文件的超链接。可以用 querySelectorAll('a')开始获取页面上的每个链接。在 index.js 中的代码中添加以下内容:

got(vgmUrl).then(response => {
  const dom = new JSDOM(response.body);
    dom.window.document.querySelectorAll('a').forEach(link => {
    console.log(link.href);
  });
}).catch(err => {
  console.log(err);
});

此代码记录页面上每个链接的 URL。可以用 forEach 函数浏览给定选择器中的所有元素。遍历页面上的每个链接都很棒,但是如果要下载所有 MIDI 文件,则需要更具体一些。

通过 HTML 元素过滤

在编写更多代码去解析所需的内容之前,先来看一下浏览器渲染出来的 HTML。每个网页都是不同的,有时从其中获取正确的数据需要一些创造力、模式识别和实验。

网页上的MIDI文件

我们的目标是下载许多 MIDI 文件,但是这个网页上有很多重复的曲目以及歌曲的混音。我们只希望下载重复歌曲中的一首,并且因为我们的最终目标是用这些数据来训练神经网络以生成准确的 Nintendo 音乐,所以我们不想在用户创建的混音上对其进行训练。

当你编写代码解析网页时,通常可以用现代浏览器中的开发者工具。如果右键单击你感兴趣的元素,则可以检查该元素后面的 HTML 并获取更多信息。

检查元素

你可以编写过滤器函数来微调所需的选择器数据。这些函数遍历给定选择器的所有元素,并根据是否应将它们包含在集合中而返回 true 或 false。

如果查看了上一步中记录的数据,可能会注意到页面上有很多链接没有 href 属性,因此无处可寻。可以确定它们不是我们要寻找的 MIDI,所以需要写一个简短的函数来过滤掉那些 MIDI,并包含确实能够链接到 .mid 文件的 href 元素:

const isMidi = (link) => {
  // Return false if there is no href attribute.
  if(typeof link.href === 'undefined') { return false }

  return link.href.includes('.mid');
};

现在有一个问题,我们不想下载重复项或用户生成的混音。可以用正则表达式来确保仅获取文本中不带括号的链接,因为只有重复项和混音项包含括号:

const noParens = (link) => {
  // Regular expression to determine if the text has parentheses.
  const parensRegex = /^((?!\().)*$/;
  return parensRegex.test(link.textContent);
};

试着将它们添加到你的 index.js 中的代码中,通过从 querySelectorAll 返回的 HTML 元素节点集合中创建一个数组,然后把过滤器函数应用到其中:

got(vgmUrl).then(response => {
  const dom = new JSDOM(response.body);

  // Create an Array out of the HTML Elements for filtering using spread syntax.
  const nodeList = [...dom.window.document.querySelectorAll('a')];

  nodeList.filter(isMidi).filter(noParens).forEach(link => {
    console.log(link.href);
  });
}).catch(err => {
  console.log(err);
});

再次运行代码,它仅应打印 .mid 文件,而不复制任何特定歌曲。

从网页下载我们想要的 MIDI 文件

现在我们有了遍历所需的每个 MIDI 文件的工作代码,必须编写代码来下载所有这些文件。

在用于遍历所有 MIDI 链接的回调函数中,添加以下代码以将 MIDI 下载流式传输到本地文件,并进行错误检查:

  nodeList.filter(isMidi).filter(noParens).forEach(link => {
    const fileName = link.href;
    got.stream(`${vgmUrl}/${fileName}`)
      .on('error', err => { console.log(err); console.log(`Error on ${vgmUrl}/${fileName}`) })
      .pipe(fs.createWriteStream(`MIDIs/${fileName}`))
      .on('error', err => { console.log(err); console.log(`Error on ${vgmUrl}/${fileName}`) })
      .on('finish', () => console.log(`Downloaded: ${fileName}`));
  });

从要保存 MIDI 文件的目录中运行代码,从终端屏幕上能够看到下载的所有 2230 个 MIDI 文件(在编写此代码时)。这样我们就完成所有需要的 MIDI 文件的抓取了。

Logging the results of the file downloads

现在可以仔细倾听并欣赏任天堂音乐了!

浩瀚的万维网

你可以通过编程的方式从网页上获取内容,无论你需要什么项目,都可以访问大量的数据源。要记住的一件事是,被更改过网页的 HTML 可能会破坏你的代码,所以如果你要在此基础上构建应用程序,请确保所有内容保持最新。

如果你正在寻找与刚刚从视频游戏音乐档案库中获取的数据有关的内容,则可以尝试使用 Python 库,例如 Magenta to train a neural network with it(https://www.twilio.com/blog/training-a-neural-network-on-midi-music-data-with-magenta-and-python)。

原文链接

https://www.twilio.com/blog/web-scraping-and-parsing-html-in-node-js-with-jsdom

本文分享自微信公众号 - 前端先锋(jingchengyideng),作者:疯狂的技术宅

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript 解构的5个有趣用法

    在本文中,除了基本用法之外,我还将会介绍在 JavaScript 中 5 种有趣的解构用法。

    疯狂的技术宅
  • 内置于浏览器中的国际化API[每日前端夜话0xBA]

    你的程序很有可能需要支持多种语言。其中包括对语言敏感的日期处理。一个广受欢迎的库Moment.js【https://www.npmjs.com/package/...

    疯狂的技术宅
  • 正则表达式在 ES2018 中的新写法 [每日前端夜话0x25]

    摘要:如果你曾用 JavaScript 做过复杂的文本处理和操作,那么你将会对 ES2018 中引入的新功能爱不释手。 在本文中,我们将详细介绍第 9 版标准如...

    疯狂的技术宅
  • 12-初识OpenStack中的网络

    这个网络属于哪个项目,这里我们有云计算和网络安全两个比赛项目,所以待会我们去创建这两个项目

    小朋友呢
  • 云计算、IoT和SDN为企业网带来最大的问题

    根据Kentik发布的一份新报告,云计算的采用仍然是造成网络复杂性的最令人烦恼的因素。该调查报告是基于参加Cisco Live 2017大会的203名IT专业人...

    SDNLAB
  • 十分钟一起学会ResNet残差网络

    【磐创AI导读】:本文主要带大家一起剖析ResNet网络,查漏补缺。想要学习更多的机器学习、深度学习知识,欢迎大家点击上方蓝字关注我们的公众号:磐创AI。

    磐创AI
  • 物联网是如何驱动网络变革的?——上

    ---导读--- 软件定义网络( SDN)已成为管理和维护企业网络安全的新途径。这是自互联网引入以来,企业网络最重大的变化。它不是一种单一的技术,而是一种涵盖各...

    企鹅号小编
  • 云数据中心网络运维的苦与乐

    前几年大家讲 SDN 比较多的是怎样利用控制器,像 OpenDayLight、ONOS 这些东西,其实在讲怎样做一个 Driver、怎样做控制。大概从去年开始,...

    SDNLAB
  • 数据分析师不是数羊的

    用户1756920
  • 李国杰:未来网络并不遥远

    第五届中国未来网络发展与创新论坛今天在南京盛大开幕,来自国内外的近百位专家学者齐聚一堂,共同交流未来网络技术。中国工程院院士李国杰发表精彩演讲。 各位来宾上午好...

    SDNLAB

扫码关注云+社区

领取腾讯云代金券