node 网易云音乐榜单爬虫

关于网易云爬虫的一点总结

    这几天因为工作需要爬取网易云音乐各个榜单的歌单。之前使用过python写爬虫,这一次使用的是node,因为工作需要。

    说简单简单,加上注释也就200多行,不加注释100行的样子吧;说不简单也不简单,踩了三四天的坑。希望我的这篇文章能让你少踩一些坑。

1. 确定网站的反爬虫策略

    所谓知己知彼,方能百战不殆。我在这个地方吃了很大亏。之前写的python爬虫都是爬豆瓣。豆瓣比较好爬,只需要注意访问频率不被封IP即可,即使被封了,再一次登录即可解封。而这次的网易云爬虫有点不一样,直接下载网页是得不到你所需要的歌单信息的。

首先贴一个关于反爬虫策略的链接 https://blog.csdn.net/miner_zhu/article/details/81093616

网易云反爬虫属于第四类,之前我也就知道ajax动态渲染,后面发现榜单网页的XHR文件里面包含的数据只有评论的,并没有歌单,所以我们需要采取的对策是selenium,导致在这里浪费太多时间。

2. 确定我们需要使用的方法

    上文以及提及到了,selenium 模块。这个模块无论是python还是node都是支持的。

    node下: npm install selenium-webdriver

3. selenium遇到的一点问题

    selenium 是需要驱动的,而且需要驱动暴露在环境路径下。这里的坑主要是

    1) firefox 的驱动

    2)Chrome的驱动

        提示是

    按照提示的信息是说,这个版本只支持 76 的版本,而我使用的一直是 76 的版本,而 76 的版本总共就两个,我都尝试了,但是这个error 报了一下午,而Firefox的驱动也用不了,在这里浪费了大量时间;后面心想是不是只有76的版本不支持,然后下载了75 的驱动,然后这个error消失了,WTF! 真的,我那时很高兴又很不高兴,高兴的是error消失了,不高兴的是这个提示信息误导我太久,我也曾一度怀疑是不是我英语理解错误,然后找了外语系的同事问了一下,得到答复后我只想锤写这个错误信息的人一顿。

    selenium 能用了之后,还有就是怎么得到完整的网页的问题。

是上图这样的网页,而不是下面这样的


最开始的想到的是需要找到JS,然后driver运行js。后面发现只需要switchTo().frame('frame')就可以了。

4. 分析得到的完整的网页

cheerio 加正则表达式

5. 爬取所有的榜单网页

6. 格式化保存

很晚了,不写了:)但是最后贴上代码以及git-hub地址,代码注释比较详细

SongScrapy.js

``` node.js

const chrome = require('selenium-webdriver/chrome');

const firefox = require('selenium-webdriver/firefox');

const {Builder, By, Key, until} = require('selenium-webdriver');

const fs = require('fs')

const cheerio = require('cheerio')

const Entities = require('html-entities').XmlEntities

const entities =new Entities();

const width =1080;

const height =1920;

module.exports = SongScrapy;

/**

* 用于爬取特定歌单页

* @param href

* @param name

* @constructor

*/

function SongScrapy(href, name) {

this.url = href;

    this.name = name;

    this.self =this;

    // 使用Chromedriver, 只支持 75 及以下的版本

// 运行如果报错说缺少驱动,按着他提供的链接下载即可

// 不过driver需要暴露在系统路径下

    this.driver =new Builder()

.forBrowser('chrome')

.setChromeOptions(

new chrome.Options().headless().windowSize({width, height}))

.setFirefoxOptions(

new firefox.Options().headless().windowSize({width, height}))

.build();

}

/**

* 将前面获得榜单名字以及 href

* 继续抓取,得到mei每个歌单的网页

* @param href

* @param name

*/

SongScrapy.prototype.getSource =function(){

this.driver.get(this.url)

.then(_ => {

//switchTo().frame 是必须的操作

//如果没有这一步,很可能得到的是没有渲染的网页

            this.driver.switchTo().frame('g_iframe');

            this.driver.wait( () => {

this.driver.getPageSource().then( (source) => {

this.getTopList(source, this.name);

                });

return true;

            },5000);

        })

}

SongScrapy.prototype.getTopList =function (html, name) {

let $ = cheerio.load(html);

    let topList = $('#song-list-pre-cache');

    /**

* 直接查找的话,转为text中间会

* 夹杂一些无关字符

*/

    // topList.find('tr').each(function (songItem) {

//    let songName = $(this).find('.txt').find('')

//    console.log('txt', songName.text())

// })

//使用正则表达式来查找歌名

    /**

* 得到的item类似这样

*

* data-res-action="share" data-res-name="山上雪"

* data-res-author="万象凡音/黄诗扶"

* data-res-pic="http://p2.music.126.net/8C0LCrwm-BG3iNtWNFHqFw==/109951164212877338.jpg"

* class="icn icn-share" title="分享">分享

*/

        // entities 的作用是将中文乱码重新编码

// 出现中文乱码的原因不是网页编码集不是'utf-8'

// 而是我们直接对topList 直接使用了 ToString 方法

    let listString = entities.decode(topList.toString());

    //正则提取

    let songs = listString.match(/<span data-res-id="\d{0,40}" data-res-type=".{0,20}" data-res-action=".{0,20}" data-res-name=".{0,100}" data-res-author=".{0,100}".{0,300}" class=".{0,70}" title.{0,100}<\/span>/g)

//对于每首歌提取出我们需要的信息

    console.log('length', songs.length)

// 得到真正的文件名

    let filename =`${this.name}-${songs.length}.json`;

    // 如果文件已经存在,则删除文件

    if(fs.existsSync(filename)){

fs.unlink(filename, (err) => {

if(err){

return console.log(err);

            }else{

}

})

}

for(let songof songs){

let songItem = $(song);

        //将我们需要的信息提取出来

        let songName = songItem.attr('data-res-name')

let songAuthor = songItem.attr('data-res-author')

let songId =songItem.attr('data-res-id');

        let songObj = {

name: songName,

            author: songAuthor,

            id: songId,

        }

//转为json

        let jsonStr = JSON.stringify(songObj) +',';

        //console.log(jsonStr)

        fs.appendFileSync(filename, jsonStr);

    }

}

//

// function test() {

//    let songScrapy = new SongScrapy('https://music.163.com/#/discover/toplist?id=19723756', './netEaseLists/云音乐飙升榜');

//    songScrapy.getSource();

// }

//

// test();

```

ListScrapy.js

```node.js

const chrome = require('selenium-webdriver/chrome');

const firefox = require('selenium-webdriver/firefox');

const {Builder, By, Key, until} = require('selenium-webdriver');

const fs = require('fs')

const cheerio = require('cheerio')

const Entities = require('html-entities').XmlEntities

const entities =new Entities();

let SongScrapy = require('./SongScrapy')

//windows size 参数

const width =1080;

const height =1920;

module.exporpts = ListScrapy;

/**

* baseUrl 是获取榜单的入口

* baseDir 是存取文件的base地址

* refer 是 netEase 的网址

* 因为这个爬虫只能爬取网易云

* 所以就写死了

* songLists 保存获取的榜单名以及 href

* @param baseUrl

* @param baseDir

* @constructor

*/

function ListScrapy(baseUrl, baseDir) {

this.self =this;

    this.baseUrl = baseUrl;

    this.baseDir = baseDir;

    this.refer ='https://music.163.com/';

    this.songLists = [];

    // 使用Chromedriver, 只支持 75 及以下的版本

// 运行如果报错说缺少驱动,按着他提供的链接下载即可

// 不过driver需要暴露在系统路径下

    this.driver =new Builder()

.forBrowser('chrome')

.setChromeOptions(

new chrome.Options().headless().windowSize({width, height}))

.setFirefoxOptions(

new firefox.Options().headless().windowSize({width, height}))

.build();

}

/**

* 将得到的网页 source 交给 getLists 处理

* 得到榜单的名字以及 href

*/

ListScrapy.prototype.run =function () {

this.driver.get(this.baseUrl)

.then(_ => {

//switchTo().frame 是必须的操作

//如果没有这一步,很可能得到的是没有渲染的网页

            this.driver.switchTo().frame('g_iframe');

            this.driver.wait( () => {

this.driver.getPageSource().then( (source) => {

this.getLists(source);

                });

return true;

            },5000);

        })

}

/**

*

* @param html

* 将传过来的网页解析

* 得到每一个榜单的名字以及地址

*/

ListScrapy.prototype.getLists =function (html) {

let $ = cheerio.load(html);

    //所有榜单都在 class="name" 下

//lists 不能遍历,所以采用的是正则分割

    let lists = $('.name')

// entities 的作用是将中文乱码重新编码

// 出现中文乱码的原因不是网页编码集不是'utf-8'

// 而是我们直接对topList 直接使用了 ToString 方法

    let listsStr = entities.decode(lists.toString())

let listArr = listsStr.match(/<p class="name"><a .{0,80}p>/g);

    //对每一个 class="name" 的p标签进行遍历

//提取出 href 和 name

    for(listof listArr){

let listObj = {}

let elementA = $(list);

        listObj.href = elementA.find('a').attr('href');

        listObj.name = elementA.text();

        this.songLists.push(listObj);

        console.log(listObj)

let songScrapy =new SongScrapy(this.refer + listObj.href, this.baseDir + listObj.name);

        songScrapy.getSource();

    }

}

function test(){

let netEaseScrapy =new ListScrapy('https://music.163.com/#/discover/toplist', './netEaseLists/');

    netEaseScrapy.run()

}

test()

```

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容