1

项目初始化

创建一个package.json文件,webstorm快捷创建package.json非常简单。
使用 npm init 快速创建。

工具模块

需要下载的的模块

这是2个npm包,我们先下载:

npm install superagent cheerio --save

需要引入的模块

  • fs
  • path

引入项目依赖

const cheerio = require('cheerio');
const agent = require('superagent');
const path = require('path');
const fs = require('fs');

定义地址数组

我们希望以队列的形式逐个对这些地址进行访问,获取HTML代码,以便后续处理:

const urls = [{
    page:1,
    url:"https://www.imooc.com/course/list?c=fe&page=1"
},{
    page:2,
    url:"https://www.imooc.com/course/list?c=fe&page=2"
},{
    page:3,
    url:"https://www.imooc.com/course/list?c=fe&page=3"
}];

定义数据结构

慕课网课程列表:
clipboard.png

对此我们定义如下的数据结构:

 [
     {
      page: 1,
      data: [
          {
             title:"", // 课程标题
             imgurl:"", // 课程图片
             level:"", // 等级
             studynum:0, // 学习人数
             description:"xxxx" // 课程描述
        }
        ...... // 每一个页面有多条课程信息
      ]  
    }
    ...... // 一共有多个页面
 ]

superagent 页面数据下载

superagent是nodejs里一个非常方便的客户端请求代码模块,superagent是一个轻量级的,渐进式的ajax API,可读性好,学习曲线低,内部依赖nodejs原生的请求API,适用于nodejs环境下。

基本使用方法:
具体的请自行点击连接查看哟...

request
    .get('/login')
    .end(function(err, res){
        // code
    });

cheerio 页面数据解析

cheerio是一个node的库,可以理解为一个Node.js版本的jquery,用来从网页中以 css selector取数据,使用方式和jquery基本相同。

需要先loading一个需要加载html文档,后面就可以jQuery一样使用操作页面了。

基本使用方法:
具体的请自行点击连接查看哟...

const cheerio = require('cheerio');
const $ = cheerio.load('<ul id="fruits">...</ul>');
$('#fruits').addClass('newClass');

使用Promise实现队列

这才是本篇文章的重头戏...

  • 我们需要使用数组的一个方法 reduce()
arr.reduce([callback, initialValue])

有不太懂这个方法的可以查看我写的笔记:https://segmentfault.com/n/13...

reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。

callback (执行数组中每个值的函数,包含四个参数)
initialValue (作为第一次调用 callback 的第一个参数。)

  • 还有一个是Promise实现异步处理

有不太懂这个方法的可以查看我写的笔记:https://segmentfault.com/n/13...

具体是使用Promise的这个方法:
Promise.resolve()
这个方法返回一个fulfilled的Promise实例,或者原始的Promise实例。

代码实现:

// 实现队列 
// 本质: 对.then()方法实现累加 
let curPromise = urls.reduce((promise,curl) => {

    return promise.then(() => {
        return new Promise(resolve => {
            // 网络获取当前地址的网页内容
            requestGet(curl,() => {
                resolve(); 
            });
        });
    });

},Promise.resolve());

将数据写入result.json文件中

代码实现:

// 写入数据
curPromise.then(()=>{
    fs.writeFile('result.json', JSON.stringify(result), function (err) {
        if(err) throw new Error("appendFile failed...");
        console.log("数据写入success...");
    });    
});

完整代码

// 项目依赖
const cheerio = require('cheerio');
const agent = require('superagent');
const path = require('path');
const fs = require('fs');

// 地址数据
const urls = [{ 
    page:1,
    url:"https://www.imooc.com/course/list?c=fe&page=1"
},{
    page:2,
    url:"https://www.imooc.com/course/list?c=fe&page=2"
},{
    page:3,
    url:"https://www.imooc.com/course/list?c=fe&page=3"
}];

// 最终的数据
let result = [];


// 数据结构
/**
 * [
 *     {
 *         page: 1,
 *         data: [
 *                {title:xx,imgurl:xx...},
 *                ......
 *               ]
 *     }
 *     ......    
 * ]
 */


// 发起get请求
function requestGet(urlObj,callback){

    agent.get(urlObj.url)
     .end((err,res) => {
         if(err) throw new Error(err);

         // 分析页面
         let pageJson = analysis(res.text);

         // 拼接数据
         result.push({
             page:urlObj.page,
             data:pageJson
         });

         console.log(`写入第${urlObj.page}页的数据...`);

         // 执行回调
         callback();
     
     });
}


// 对网页分析
function analysis(data){

    let page = [];
    let $ = cheerio.load(data);
    let courseArr = $(".course-list").find(".course-card-container");
    courseArr.each((index,element) => {
        let _this = $(element);
        // 组装数据
        page.push({
            title:_this.find(".course-card-name").text(),
            imgurl:path.join("http:",_this.find(".course-card-top img").attr("src")),
            level:_this.find(".course-card-info span:first-child").text(),
            // level:_this.find(".icon-set_sns").parent().prev().text(),
            studynum:_this.find(".icon-set_sns").parent().text(),
            description:_this.find(".course-card-desc").text()
        });
    });
    return page;
}


// 实现队列 
// 本质: 对.then()方法实现累加 
let curPromise = urls.reduce((promise,curl) => {

    return promise.then(() => {
        return new Promise(resolve => {
            // 具体的内容
            requestGet(curl,() => {
                resolve(); 
            });
        });
    });

},Promise.resolve());

// 写入数据
curPromise.then(()=>{
    fs.writeFile('result.json', JSON.stringify(result), function (err) {
        if(err) throw new Error("appendFile failed...");
        console.log("数据写入success...");
    });    
});

启动项目

node app.js

可以看到终端有次序的输出了以下内容:

clipboard.png

当打开生成的 result.json 文件,其结构也符合我们的预期:
clipboard.png

至此,这篇文章也就结束啦,如果您有好的想法请留言哟。

持续学习中...


五月花开
1.3k 声望878 粉丝

仰望星空,也要脚踏实地。