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

    这几天因为工作需要爬取网易云音乐各个榜单的歌单。之前使用过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;

/**

<ul>
<li>用于爬取特定歌单页

<ul>
<li>@param href</p></li>
<li><p>@param name</p></li>
<li><p>@constructor</p></li>
</ul>

<p>*/</p></li>
</ul>

<p>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();

}

/**

<ul>
<li>将前面获得榜单名字以及 href</p></li>
<li><p>继续抓取,得到mei每个歌单的网页

<ul>
<li>@param href</p></li>
<li><p>@param name</p></li>
</ul></li>
</ul>

<p>*/

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');

    /**

<ul>
<li>直接查找的话,转为text中间会</p></li>
<li><p>夹杂一些无关字符</p></li>
</ul>

<p>*/

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

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

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

// })

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

    /**

<ul>
<li>得到的item类似这样</li>
</ul>

*

<ul>
<li>data-res-action="share" data-res-name="山上雪"</p></li>
<li><p>data-res-author="万象凡音/黄诗扶"</p></li>
<li><p>data-res-pic="http://p2.music.126.net/8C0LCrwm-BG3iNtWNFHqFw<span class="text-highlighted-inline" style="background-color: #fffd38;">/109951164212877338.jpg"</span></p></li>
<li><p>class="icn icn-share" title="分享">分享</p></li>
</ul>

<p>*/

        // 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}" class=".{0,70}">/g)</span>

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

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

// 得到真正的文件名

    let filename =<code>{this.name}-{songs.length}.json</code>;

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

    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;

/**

<ul>
<li>baseUrl 是获取榜单的入口</p></li>
<li><p>baseDir 是存取文件的base地址</p></li>
<li><p>refer 是 netEase 的网址</p></li>
<li><p>因为这个爬虫只能爬取网易云</p></li>
<li><p>所以就写死了</p></li>
<li><p>songLists 保存获取的榜单名以及 href

<ul>
<li>@param baseUrl</p></li>
<li><p>@param baseDir</p></li>
<li><p>@constructor</p></li>
</ul>

<p>*/</p></li>
</ul>

<p>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();

}

/**

<ul>
<li>将得到的网页 source 交给 getLists 处理</p></li>
<li><p>得到榜单的名字以及 href</p></li>
</ul>

<p>*/

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);

        })

}

/**

*

<ul>
<li>@param html

<ul>
<li>将传过来的网页解析</p></li>
<li><p>得到每一个榜单的名字以及地址</p></li>
</ul></li>
</ul>

<p>*/

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">/g);</p>

    //对每一个 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()

“`

文章来源于互联网:node 网易云音乐榜单爬虫

发表评论