用 Node.js 爬蟲下載音樂

做者:Sam Agnew
翻譯:瘋狂的技術宅
原文: https://www.twilio.com/blog/w...
未經容許嚴禁轉載

互聯網上有許多可供人類消費的信息。可是若是這些數據不是以專用的 REST API 的形式出現,一般很難以編程方式對其進行訪問。使用 jsdom 之類的 Node.js 工具,你能夠直接從網頁上抓取並解析這些數據,並用於你本身的項目和應用。javascript

讓咱們以用 MIDI 音樂數據來訓練神經網絡生成聽起來經典的任天堂音樂爲例。咱們須要一套來自舊任天堂遊戲的 MIDI 音樂。經過使用 jsdom 能夠從視頻遊戲音樂檔案中抓取這些數據。html

入門和依賴項設置

在繼續以前,你須要確保本身有 Node.js 和 npm 的最新版本。前端

切換到你但願此代碼存在的目錄,並在終端中運行如下命令建立項目的程序包:java

npm init --yes

--yes 參數能夠忽略全部你必須填寫或跳過的提示。如今咱們的程序有了 package.jsonnode

爲了經過發出 HTTP 請求從網頁獲取數據,咱們將使用 Got 庫,對於 HTML 的解析,咱們將用 Cheerio。python

在終端中運行如下命令安裝這些庫:程序員

npm install got@10.4.0 jsdom@16.2.2

jsdom 是大量 Web 標準的純 JavaScript 實現,也是許多 JavaScript 開發人員熟悉的工具。讓咱們深刻了解該如何使用它。web

用 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 解析器進行解析。最多見的兩個方法是按 classID 獲取。若是要獲取 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。每一個網頁都是不一樣的,有時從其中獲取正確的數據須要一些創造力、模式識別和實驗。

image.png

咱們的目標是下載許多 MIDI 文件,可是這個網頁上有不少重複的曲目以及歌曲的混音。咱們只但願下載重複歌曲中的一首,而且由於咱們的最終目標是用這些數據來訓練神經網絡以生成準確的 Nintendo 音樂,因此咱們不想在用戶建立的混音上對其進行訓練。

當你編寫代碼解析網頁時,一般能夠用現代瀏覽器中的開發者工具。若是右鍵單擊你感興趣的元素,則能夠檢查該元素後面的 HTML 並獲取更多信息。

image.png

你能夠編寫過濾器函數來微調所需的選擇器數據。這些函數遍歷給定選擇器的全部元素,並根據是否應將它們包含在集合中而返回 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 文件的抓取了。

image.png

如今能夠仔細傾聽並欣賞任天堂音樂了!

浩瀚的萬維網

你能夠經過編程的方式從網頁上獲取內容,不管你須要什麼項目,均可以訪問大量的數據源。要記住的一件事是,被更改過網頁的 HTML 可能會破壞你的代碼,因此若是你要在此基礎上構建應用程序,請確保全部內容保持最新。

若是你正在尋找與剛剛從視頻遊戲音樂檔案庫中獲取的數據有關的內容,則能夠嘗試使用 Python 庫,例如 Magenta to train a neural network with it


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索