使用Puppeteer轻松爬取网易云音乐、QQ音乐的精品歌单

栏目: Node.js · 发布时间: 5年前

内容简介:最近在学习Puppeteer进行自动化操作,另一方面为了防止上班时间被打扰,是时候爬点歌单在上班的时候,用来抵抗外界的干扰了。项目完整代码地址:由于 Puppeteer 会下载一个 Chrome 浏览器到本地,所以可能较慢,可以使用

最近在学习Puppeteer进行自动化操作,另一方面为了防止上班时间被打扰,是时候爬点歌单在上班的时候,用来抵抗外界的干扰了。

地址

项目完整代码地址: github.com/BingKui/WeC…

工具

v10.*.*

目标

  • 爬取网易云音乐播放量超过 1000W 的歌单
  • 爬取 QQ 音乐播放量超过 1000W 的歌单
  • 把爬取的歌单保存到数据库
  • 创建定时任务,线上部署,每天定时爬取、保存

准备工作

  • 保证本地安装了 MongoDB 数据库,并能正常连接,具体请自行百度。
  • 安装所需的库文件
  • 编写数据库连接文件

安装库文件注意

由于 Puppeteer 会下载一个 Chrome 浏览器到本地,所以可能较慢,可以使用 cnpm 或者切换为 淘宝镜像。

数据库连接文件

const mongoose = require('mongoose');
// 数据库地址
const mongoDB = 'mongodb://127.0.0.1:27017/wechat';

// 链接数据库
mongoose.connect(mongoDB);

// 监听数据库事件
const db = mongoose.connection;

// 连接异常
db.on('error', function (err) {
    console.log('Mongoose connection error: ' + err);
});

// 连接断开
db.on('disconnected', function () {
    console.log('Mongoose connection disconnected');
});

// 连接成功
db.on('connected', function () {
    console.log('Mongoose connection open to ' + mongoDB);
});
复制代码

爬取网易云音乐歌单

创建浏览器对象

首先我们创建一个无头的浏览器对象。

const browser = await puppeteer.launch({timeout: 300000, headless: true, args: ['--no-sandbox']});
复制代码

具体参数含义请 点解这里查看

说明:args 参数为可选项,以上参数是为了兼容 CentOS ,添加的参数

打开页面

打开地址: https://music.163.com/#/discover/playlist

let url = 'https://music.163.com/#/discover/playlist';
// 创建
const page = await browser.newPage();
// 跳转到歌单页面
await page.goto(url);
复制代码

分析页面结构

使用Puppeteer轻松爬取网易云音乐、QQ音乐的精品歌单

网易云音乐的歌单页使用了 iframe 嵌套的方式,所以我们要在页面中获取到 iframe 中的内容,并提取我们需要的信息。

// 获取歌单的iframe
let iframe = await page.frames().find(f => f.name() === 'contentFrame');
复制代码

获取歌单数据的元素并处理

Puppeteer 提供了可以在 iframe 中执行js的方法,我们可以直接执行,通过原生js来获取想要的数据。

// 获取歌单
const result = await iframe.evaluate(() => {
    // 获取所有元素
    const elements = document.querySelectorAll('#m-pl-container > li');
    // 创建数组,存放获取的数据
    let res = [];
    for (let ele of elements) {
        let image = ele.querySelector('.j-flag').getAttribute('src');
        let name = ele.querySelector('.tit').innerText;
        let count = ele.querySelector('.nb').innerText;
        let author = ele.querySelector('.nm').innerText;
        let address = 'https://music.163.com/#' + ele.querySelector('.msk').getAttribute('href');
        const flag = (count.indexOf('万') > -1) && (parseInt(count.split('万')[0]) > 1000);
        if (flag) {
            res.push({
                image,
                name,
                count,
                author,
                address,
                from: 'netease',
            });
        }
    }
    // 返回数据
    return res;
});
复制代码

循环爬取所有热门歌单

通过分析页面可以看到,歌单一共 35 页,并且每页有 35 条数据,并且分页是通过 url 参数区分的,所以我们可以简单暴力一点,写个循环搞定(主要还是懒)。

高级操作:可以通过 Puppeteer 的方法,获取页面,然后点击下一页,判断是否能够点击下一页来确定是否存在下一页。需要了解的可以自行研究。

为了方便操作,我们把获取每页数据封装成一个方法: getOnePageData

const getOnePageData = async (page, pageNumber) => {
    const url = `https://y.qq.com/portal/playlist.html#t3=${pageNumber}&`;
    // 跳转到页面
    await page.goto(url);
    await page.setViewport({ 
        width: 1300, 
        height: 5227,
    });
    // 等待两秒,加载图片
    await page.waitFor(2000);
    // 获取歌单
    const result = await page.evaluate(() => {
        // 此处与上方方法一样,省略
        ...
    });
    return result;
}
复制代码

然后循环获取数据。

// 定于数组存储数据
let musicPlayList = [];
const page = await browser.newPage();
for (let i = 0; i < 1191; i += 35) {
    const item = await getOnePageData(page, i);
    console.log(`获取到数据${item.length}条。`);
    musicPlayList = musicPlayList.concat(item);
}
复制代码

保存数据到 MongoDB 数据库

定义数据模型。

// models/music.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const MusicSchema = new Schema({
    image: String,
    name: String,
    count: String,
    author: String,
    address: String,
    from: String,
    date: Date,
    show: Boolean, // 是否展示
});

const MusicModel = mongoose.model('playlist', MusicSchema);

module.exports = MusicModel;
复制代码

封装基本的添加方法。

// server/music.js
const MusicModel = require('../models/music.js');

const save = (item) => {
    findBuName(item.name, (obj) => {
        if (obj) {
            console.log('已经保存,数据');
            obj.remove();
        }
        const saveObject = new MusicModel(item);
        saveObject.save((err) => {
            if (err) return handleError(err);
        });
    });
}

const findBuName = (name, callback) => {
    MusicModel.findOne({name}, (err, item) => {
        if (err) {
            callback && callback(false);
        }
        callback && callback(item);
    });
};

module.exports = {
    save,
};
复制代码

由于爬取的数据存在重复的数据,为了减少不必要的资源浪费,保存前先进行数据的去重。

// 保存之前去重
let hash = {};
musicPlayList = musicPlayList.reduce((item, next) => {
    hash[next.address] ? '' : hash[next.address] = true && item.push(next);
    return item
}, []);
复制代码

保存数据到 MongoDB 数据库。

const MusicServer = require('../server/music.js');

// 保存数据
for (let i = 0; i < musicPlayList.length; i++) {
    const item = musicPlayList[i];
    item.date = dayjs().format('YYYY-MM-DD HH:mm:ss');
    item.show = true;
    MusicServer.save(item);
}
复制代码

最后关闭浏览器

最后别忘了关闭开始的时候创建的浏览器。

browser.close();
复制代码

到这里,爬取网易云音乐的精品歌单已经完成了。接下来开始爬取 QQ 音乐。

爬取 QQ 音乐精品歌单

由于爬取方式基本一样,下面只介绍不同的地方。

分析页面结构

使用Puppeteer轻松爬取网易云音乐、QQ音乐的精品歌单

分析页面,QQ 音乐,没有采用和网易云音乐一样的 iframe 方式,这样爬取更加简单。

获取歌单数据的元素并处理

可以通过在页面上执行方法就能够爬取到我们需要的数据。

// 获取歌单
const result = await page.evaluate(() => {
    const elements = document.querySelectorAll('#playlist_box > li');
    let res = [];
    for (let ele of elements) {
        const _n = ele.querySelector('.js_playlist');
        let image = 'https:' + ele.querySelector('.playlist__pic').getAttribute('src');
        let name = _n.getAttribute('title');
        let count = ele.querySelector('.playlist__other').innerText.split(':')[1].replace(/\s+/g, '');
        let author = ele.querySelector('.playlist__author').innerText.replace(/\s+/g, '');
        let address = `https://y.qq.com/n/yqq/playsquare/${_n.getAttribute('data-disstid')}.html#stat=${_n.getAttribute('data-stat')}`;
        const flag = (count.indexOf('万') > -1) && (parseInt(count.split('万')[0]) > 1000);
        if (flag) {
            res.push({
                image,
                name,
                count,
                author,
                address,
                from: 'qq'
            });
        }
    }
    return res;
});
复制代码

循环爬取数据

由于 QQ 音乐采取的分页方式和网易云音乐一样,所有我们还使用相同的方法,暴力爬取(可见我是有多懒~~)。

找到页面中一共有多少页歌单,然后写个像下面的循环。

// 定于数组存储数据
let musicPlayList = [];
const page = await browser.newPage();
// 爬取是总歌单也为 120 页
for (let i = 1; i < 120; i++) {
    const item = await getOnePageData(page, i);
    console.log(`获取到数据${item.length}条。`);
    musicPlayList = musicPlayList.concat(item);
}
复制代码

然后像上边一样,保存进数据库就可以了。

定时任务

由于每天歌单都会有大量的播放量,不断的更新,因此写个定时任务,每天定时爬取更新数据才是稳妥的方法,能够保证我们的数据最新。

封装爬取方法

把爬取方法封装成模块方法,然后在固定的时候调用执行爬虫。

// qq.js
const QQMusic = async () => {
    const browser = await puppeteer.launch({timeout: 300000, headless: true, args: ['--no-sandbox']});
    // 定于数组存储数据
    let musicPlayList = [];
    const page = await browser.newPage();
    for (let i = 1; i < 120; i++) {
        const item = await getOnePageData(page, i);
        console.log(`获取到数据${item.length}条。`);
        musicPlayList = musicPlayList.concat(item);
    }
    // 保存之前去重
    let hash = {};
    musicPlayList = musicPlayList.reduce((item, next) => {
        hash[next.address] ? '' : hash[next.address] = true && item.push(next);
        return item
    }, []);
    
    MusicServer.updateAllHide(() => {
        // 保存数据
        for (let i = 0; i < musicPlayList.length; i++) {
           const item = musicPlayList[i];
           item.date = dayjs().format('YYYY-MM-DD HH:mm:ss');
           item.show = true;
           MusicServer.save(item);
       }
    }, { from: 'qq' });

    await browser.close();
};
module.exports = QQMusic;

// netease.js
const NeteaseMusic = async () => {
    const browser = await puppeteer.launch({timeout: 300000, headless: true, args: ['--no-sandbox']});
    // 定于数组存储数据
    let musicPlayList = [];
    const page = await browser.newPage();
    for (let i = 0; i < 1191; i += 35) {
        const item = await getOnePageData(page, i);
        console.log(`获取到数据${item.length}条。`);
        musicPlayList = musicPlayList.concat(item);
    }

    // 保存之前去重
    let hash = {};
    musicPlayList = musicPlayList.reduce((item, next) => {
        hash[next.address] ? '' : hash[next.address] = true && item.push(next);
        return item
    }, []);

    MusicServer.updateAllHide(() => {
        // 保存数据
        for (let i = 0; i < musicPlayList.length; i++) {
           const item = musicPlayList[i];
           item.date = dayjs().format('YYYY-MM-DD HH:mm:ss');
           item.show = true;
           MusicServer.save(item);
       }
    }, { from: 'netease' });

    await browser.close();
};
module.exports = NeteaseMusic;
复制代码

编写定时器

应用 node-schedule 模块,我们能够简单的创建定时任务。

// 创建爬取歌单定时任务
const qqPlayList = () => {
    TimeSchedule.scheduleJob('0 5 0 * * *', async () => {
        await QQMusic();
    });
}

const neteasePlayList = () => {
    TimeSchedule.scheduleJob('0 50 0 * * *', async () => {
        await NeteaseMusic();
    });
}

const scheduleList = () => {
    qqPlayList();
    neteasePlayList();
};

scheduleList();
复制代码

注意两个爬虫之间的时间间隔,尽量大一些,方式同时两个爬虫都运行,造成服务器的过大压力(土豪机,请随意~~~)。

线上部署

使用 pm2 我们可以方便的管理我们的 NodeJS 服务。

安装 pm2

npm install -g pm2
复制代码

使用 pm2 启动我们的服务。

pm2 start index.js
复制代码

更多相关内容请查阅这里。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Tomcat与Java Web开发技术详解

Tomcat与Java Web开发技术详解

孙卫琴 / 电子工业出版社 / 2004-4-1 / 45.00元

《Tomcat与Java Web开发技术详解》编辑推荐:Jakarta Tomcat服务器是在SUN公司的JSWDK(JavaServer Web DevelopmentKit,SUN公司推出的小型Servlet/JSP调试工具)的基础上发展起来的一个优秀的Java Web应用容器,它是Apache-Jakarta的一个子项目。Tomcat被JavaWorld杂志的编辑选为2001年度最具创新的J......一起来看看 《Tomcat与Java Web开发技术详解》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具