Nodejs 实现爬虫的改造:Promise优化、动态页面数据的获取、多个页面并发爬取

2023-11-15

跟着Scott老师把上一次的那个爬虫代码进行改造,主要包括单个网页爬取变为多个网页爬取、使用Promise来优化多层回调、动态数据的获取(Scott老师视频中没有的,自己乱搞一个晚上出来的。。。) 

首先来介绍一下Promise,Promise可以将多层的回调转换为链式来操作,大大提升了代码的可读性与维护性。从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。下面来介绍如何使用Promise来优化

1. 由于该爬虫是多个页面并发爬取的,使用普通的方法需要层层回调,所以对该回调函数(获取页面数据函数)进行Promise包装

function getPageAsync(url) {//使用Promise对象来包装获取到页面的html的方法
    return new Promise(function (resolve,reject) {
        console.log('正在爬取 ' + url + '\n');

        http.get(url,function(res){
            var html = '';

            res.on('data',function (data) {
                html += data.toString('utf-8');
            })
            res.on('end',function(){
                resolve(html);//把当前的获取到页面的html返回回去(传递下去)
            })

        }).on('error',function (e) {
            reject(e);
            console.log("获取课程数据出错!");
        })
    })
}

2. 使用Promise的all方法(参数是一个数组,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回)来并发获取每个页面的源码,然后执行then方法来执行每个页面的爬取操作

Promise
    .all(fetchPageUrl)//针对每个url地址返回的页面HTML源码并发操作进行爬取
    .then(function (Pages) {
        var coursesData = [];
        Pages.forEach(function (html) {
            var course = selecttHtml(html);//获取当前爬取的数据
            coursesData.push(course);//保存当前爬取的数据
        })
        //console.log(courseMembers);
        for(var i in courseIds)//获取每个课程的学习人数,因为获取是异步操作的,所以要用同步地给每个课程对象赋值
        {
            for(var j in courseMembers.id)
                if(courseMembers.id[j] === courseIds[i]) {
                    coursesData[i].number = courseMembers.numbers[j];
                }
        }
       coursesData.sort(function (a, b) {//按照学习的人数从高到低排序
            return a.number < b.number;
        })
        printinfo(coursesData);//打印已经爬取好的数据
    })


动态数据的获取,因为慕课网页面源码的改变,原本Scott老师视频中获取的学习人数是静态数据来的,现在却是动态数据(好心塞啊。。),下面我来介绍一下自己是怎么获取到的动态数据的

1.首先在页面源码中找到学习人数这个数值的标签位置,发现它由一个特有的类(js-learn-num)来控制的。

2. 然后在开发人员工具中的调试程序面板下搜索js-learn-num,然后发现该数据是通过ajax的GET方法来异步获取的(如下图所示)



3. 在网络面板中搜索上图的url地址,然后找到一个AjaxCourseMembers?ids=259的请求,里面用JSON格式来封装的就是我们需要获取学习人数的动态数据


4. 下面直接用Nodejs中http模块的get方法去获取这个JSON数据,然后进行JSON解析该数据从而获得我们想要的数据


下面直接来看代码:

/**
 * Created by Turne on 2017/2/10.
 */

var http = require('http');
var Promise = require('bluebird')
var querystring = require('querystring');
var url = 'http://www.imooc.com/course/AjaxCourseMembers?ids=728';
var titleBaseUrl = 'http://www.imooc.com/course/AjaxCourseMembers?ids=';//用以获取每个课程的学习人数,该数据是动态的
var cheerio = require('cheerio');
var baseUrl = 'http://www.imooc.com/learn/';
var courseIds = [728,637,348,259,197,134,75];//需要爬取课程的id
var courseMembers = {id:[],numbers:[]};//每个课程学习的人数

function printinfo(coursesData) {//打印已经爬好的东西
    coursesData.forEach(function (courseData) {
        console.log(courseData.number + " 学过了 " + courseData.title + '\n');
    })
    var chapterTitle;
    coursesData.forEach(function (courseDatas) {
        console.log('###'+courseDatas.title +'###'+ '\n');//打印每个课程的标题
        courseDatas.courseData.forEach(function (item) {
            chapterTitle  = item.chapterTitle;
            console.log(chapterTitle + '\n');//打印每一章的标题

            item.videos.forEach(function (video) {
                console.log(' 【' + video.id + '】 '+ video.title + '\n');//打印每个视频的id和标题
            })
        })
    })
}

function selecttHtml(html) {//通过页面源码来选择需要爬取的东西
    var $ = cheerio.load(html);
    var contents = $('.chapter');//某章节下的HTML的源码
    var title = $($('.course-infos')).find('h2').text();//整个课程的大标题
    var id = $($(".course-infos").find('a')[3]).attr('href').split('/learn/')[1];
    //getCourseMembers(parseInt(id,10));
    //console.log(number);
    //var courseData = [];

    var coursesData = {
        title:title,
        number:0,
        courseData:[]
    }

    contents.each(function (item) {
        var content = $(this);//当前这一章的HTML源码数据
        var text = content.find('.chapter-content').text();
        var chapterTitle = content.find('strong').text().split(text)[0].trim();//获取每一章的标题
        var videos = content.find('.video').children('li');//获取每个视频的信息,包含视频的id和标题

        var chapterData = {
            chapterTitle:chapterTitle,
            videos: []
        };

        videos.each(function (item) {
            var video = $(this).find('a');
            var title = video.text().split('开始学习')[0].trim();//获取每个视频的标题
            //console.log(title.length);
            title = title.substring(0,title.length - 10).trim() + " " + title.substring(title.length - 10,title.length).trim();
            var id = video.attr('href').split('video/')[1];//获取每个视频的id

            chapterData.videos.push({
                title:title,
                id:id
            })

        })

        coursesData.courseData.push(chapterData)//保存爬取的数据
    })
    return coursesData;
}

function getPageAsync(url) {//使用Promise对象来包装获取到页面的html的方法
    return new Promise(function (resolve,reject) {
        console.log('正在爬取 ' + url + '\n');

        http.get(url,function(res){
            var html = '';

            res.on('data',function (data) {
                html += data.toString('utf-8');
            })
            res.on('end',function(){
                resolve(html);//把当前的获取到页面的html返回回去(传递下去)
            })

        }).on('error',function (e) {
            reject(e);
            console.log("获取课程数据出错!");
        })
    })
}

function getCourseMembers(id) {//用以获取每个课程的学习人数
    var url = titleBaseUrl + id;
    var members;
    //由于学习人数是通过AjAX来异步更新的,所以我们要使用http的个get方法去获取AJAX获取数据的url去获得我们想要的数据
    http.get(url,function(res){
        var datas = '';

        res.on('data',function (chunk) {
            datas += chunk;
        })
        res.on('end',function(){
            datas = JSON.parse(datas);//由于获取到的数据是JSON格式的,所以需要JSON.parse方法浅解析
            courseMembers.id.push(id);//保存每个课程的
            courseMembers.numbers.push(parseInt(datas.data[0].numbers,10));//保存每个课程的学习人数
        })

    })
}

var fetchPageUrl = [];

courseIds.forEach(function (id) {
    fetchPageUrl.push(getPageAsync(baseUrl + id));
    getCourseMembers(id);
})


Promise
    .all(fetchPageUrl)//针对每个url地址返回的页面HTML源码并发操作进行爬取
    .then(function (Pages) {
        var coursesData = [];
        Pages.forEach(function (html) {
            var course = selecttHtml(html);//获取当前爬取的数据
            coursesData.push(course);//保存当前爬取的数据
        })
        //console.log(courseMembers);
        for(var i in courseIds)//获取每个课程的学习人数,因为获取是异步操作的,所以要用同步地给每个课程对象赋值
        {
            for(var j in courseMembers.id)
                if(courseMembers.id[j] === courseIds[i]) {
                    coursesData[i].number = courseMembers.numbers[j];
                }
        }
       coursesData.sort(function (a, b) {//按照学习的人数从高到低排序
            return a.number < b.number;
        })
        printinfo(coursesData);//打印已经爬取好的数据
    })



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nodejs 实现爬虫的改造:Promise优化、动态页面数据的获取、多个页面并发爬取 的相关文章

随机推荐

  • windows环境下springboot程序启停脚本

    1 启动应用脚本 echo off if 1 h goto begin mshta vbscript createobject wscript shell run nx0 h 0 window close exit begin start
  • css伪类where、is、has用法

    目录 一 where 1 作用 2 用法 3 优先级 二 is 1 作用 2 用法 3 优先级 三 has 1 作用 2 用法 3 优先级 css伪类where is has用法 一 where 1 作用 where CSS 伪类函数接受选
  • Windows查看和导入证书(.cer / .pfx)

    文章目录 证书介绍 问题汇总 导入导出细节注意 如何查看以上两种证书的到期日 Windows下导入证书 证书介绍 作为文件形式存在的证书一般有以下几种格式 带有私钥的证书 由Public Key Cryptography Standards
  • 深度学习-第T5周——运动鞋品牌识别

    深度学习 第T5周 运动鞋品牌识别 深度学习 第T5周 运动鞋品牌识别 一 前言 二 我的环境 三 前期工作 1 导入数据集 2 查看图片数目 3 查看数据 四 数据预处理 1 加载数据 1 设置图片格式 2 划分训练集 3 划分验证集 4
  • 如何选购阿里云服务器并快速入门(Windows版本)?

    本入门教程采用ecs g6 large实例规格 在Windows Server 2016系统上配置了IIS服务 结合ECS管理控制台展示如何快速使用云服务器ECS 准备工作 创建账号 以及完善账号信息 注册阿里云账号 并完成实名认证 具体操
  • Centos 7 Zabbix 6.0 TimescaleDB 安装配置

    Zabbix 6 0 TimescaleDB 安装配置 系统 Centos7 PHP PHP 7 4 30 apache httpd 2 4 6 PostgreSQL 13 TimescaleDB version 2 7 0 zabbix
  • C++学习(四三五)android获取so安装路径

    ClassLoader loader getClassLoader try Method library ClassLoader class getDeclaredMethod findLibrary String class String
  • 《深入理解计算机系统》实验八Proxy Lab 下载和官方文档机翻

    前言 深入理解计算机系统 官网 http csapp cs cmu edu 3e labs html 该篇文章是 实验八Proxy Lab的Writeup proxylab pdf 机翻 原文 http csapp cs cmu edu 3
  • python的面向对象和面向过程(意义和区别)

    面向过程 侧重于怎么做 1 把完成某一个需求的 所有步骤 从头到尾 逐步实现 2 根据开发要求 将某些功能独立的代码封装成一个又一个函数 3 最后完成的代码 就是顺序的调用不同的函数 特点 1 注重步骤和过程 不注重职责分工 2 如果需求复
  • 2020电赛经验总结+E题解题思路

    2020电赛经验总结 E题解题思路 取得的成果和经验 四川省2020年电子设计竞赛已经落下帷幕 第一次参加电赛 无论从知识还是经验上都有所获得 虽然只取得省三的成绩 但整个比赛过程为明年备战国赛具有指导作用 也算是一个不错的结果 一个团队中
  • 深度学习超分辨率重建(总结)

    本文为概述 详情翻看前面文章 1 SRCNN 2 3改进 开山之作 三个卷积层 输入图像是低分辨率图像经过双三次 bicubic 插值和高分辨率一个尺寸后输入CNN 图像块的提取和特征表示 特征非线性映射和最终的重建 使用均方误差 MSE
  • linux time 和/usr/bin/time

    http codingstandards iteye com blog 798788 用途说明 time命令常用于测量一个命令的运行时间 注意不是用来显示和修改系统时间的 这是date命令干的事情 但是今天我通过查看time命令的手册页 发
  • LeetCode #124 二叉树中的最大路径和

    124 二叉树中的最大路径和 路径 被定义为一条从树中任意节点出发 沿父节点 子节点连接 达到任意节点的序列 同一个节点在一条路径序列中 至多出现一次 该路径 至少包含一个 节点 且不一定经过根节点 路径和 是路径中各节点值的总和 给你一个
  • ADO方法操作数据库

    一 ADO连接数据库步骤 1 这行不能少 import C Program Files Common Files system ado msado60 tlb no namespace rename EOF adoEOF 2 初始化ado组
  • 让HTML img垂直居中的三种办法:

    声明 原文来自DIVCSS5 其次原文代码存在一些引起误解的地方 已经进行修改和测试 下文会注明引起误解的地方 主要收藏为方便下次阅读 故进行转发 如有侵权 请私聊本人 定立即删除 原文连接 DIVCSS5 让html img垂直居中的三种
  • 2018腾讯移动游戏技术评审标准与实践案例

    文档下载点这里 lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt 下载文档 gt gt gt gt gt gt gt gt gt gt gt gt gt gt
  • webpack系列 —— 打包原理

    为什么要使用webpack 如今的很多网页其实可以看做是功能丰富的应用 它们拥有着复杂的JavaScript代码和一大堆依赖包 为了简化开发的复杂度 前端社区涌现出了很多好的实践方法 模块化 让我们可以把复杂的程序细化为小的文件 类似于Ty
  • java中的字符串常量池_java字符串常量池

    字符串常量池SCP jdk1 6是放在永久代 8中叫方法区或叫元空间 中 jdk1 7 中 字符串常量池放入了堆中 注意运行时常量依然存放在方法区 例如 Integer a 40 Java在编译的时候会直接将代码封装成Integer a I
  • Vue中使用z-tree插件 —— 点击展开事件异步加载子节点

    在vue中使用z tree插件 执行异步加载时候 API文档提示必须写上 async enable true url nodes php autoParam id name 琢磨了好久 写出来的 首先 要弄清楚这两个事件的方法 1 节点展开
  • Nodejs 实现爬虫的改造:Promise优化、动态页面数据的获取、多个页面并发爬取

    跟着Scott老师把上一次的那个爬虫代码进行改造 主要包括单个网页爬取变为多个网页爬取 使用Promise来优化多层回调 动态数据的获取 Scott老师视频中没有的 自己乱搞一个晚上出来的 首先来介绍一下Promise Promise可以将