Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 445|回复: 0

nodejs爬虫笔记(四)---利用nightmare解决加载更多问题

[复制链接]
  • TA的每日心情
    奋斗
    4 天前
  • 签到天数: 802 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726006
    发表于 2021-5-26 14:54:50 | 显示全部楼层 |阅读模式

    目标:

      解决页面加载更多问题。笔记三中,我们只爬取到网页的部分信息,而点击加载更多后的页面内容是没有提取到的。开始我的想法是找到加载更多的数据接口(可参照:http://www.jianshu.com/p/3fdb6ab47aef),但是我又发现一个问题,当我打开一个订阅号页面时,找到数据接口如下图,点击response会发现里面有相应的内容,对其进行解析时得到的内容却是空的,也就是说我得不到页面的信息。而且我发现有些网页的数据接口是加密的,根本访问不到。因此,我又只能换种思路,看能不能模仿浏览器进行点击,等加载更多没有的时候我再去解析页面,是不是就能够获取到所有页面信息呢。我开始找到了PhantomJS这个模块,但是运行的时候速度有点慢,后面和同学一起又找到了一个更好的模块---nightmare

     

     

    一、nightmare

    1、简单介绍

      nightmare是PhantomJS的高级封装,让你能够实现浏览器自动化任务。PhantomJS 是一个基于WebKit的服务器端 JavaScript API。它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。PhantomJS可以用于页面自动化,网络监测,网页截屏,以及无界面测试等。下面再贴一段官网介绍。我们可以看到速度是phantomJs的两倍,这正是我们想要的。

    2、应用时需要注意的地方

      首先肯定是要安装啦,安装介绍我就不废话了,可以参考nightmare官方文档,还有一些常用的api,如type输入操作,click点击操作等等。具体可以参考»。下面我只提一下几个需要注意的地方:

      (1)evaluate方法,方法的参数函数的执行环境是浏览器环境,而不是脚本的运行环境,因此,其内部无法访问到脚本内定义的全局变量,但是可以通过evaluate的后续参数,来作为第一参数函数的参数传入,虽说如此,但是!!!这种方法不能传一些特殊对象,比如原型链上的内容,是不会带上的,而且即使说node环境有Date对象,浏览器也有Date对象,也不能直接传入一个Date的实例,传入之后,也用不了Date的内置方法。例如:下面的例子中我将定义的数组arr传入给了evaluate方法。

    var arr = [];
    nightmare
    //加载页面
    .goto('http://www.baidu.com')
    //等待选择器加载完毕,可以用数值(表示等待时间,单位毫秒)
    .wait('body')
    //浏览器内页面执行
    .evaluate(function(arr){
      //这里外部的变量hello以参数的形式传了进来,可以用content获得。
      var p = document.querySelector('#cp').innerText;
      arr.push(p);
      return arr;
    }, arr)
    //结束操作
    .end()
    //前面都是操作队列,需要有then方法才会触发执行上述队列的操作
    .then(function(res){//函数参数为evaluate的返回值
      console.log(res);
    })
    //处理异常情况
    .catch(function (error) {
      console.error('failed:', error);
    });

     

      (2)在使用 nightmare 的时候,如果在忘记了在代码的最后调用 then 方法,会发现 nightmare 不会执行任何操作。例如执行下面这段代码,在打开浏览窗口之后不会有任何操作结果。

    nightmare
        .goto('https://github.com')
        .wait(1000)
        .evaluate(function(){})
        .end()

      

      (3)循环利用nightmare ,可以参照:nightmare async operations loops

      (4)如果想利用JQuery解析页面,得在evaluate方法前面利用inject()方法插入本地jquery.js文件,如:

    .inject('js', 'jquery.min.js')
    

     

     

     二、以音乐为例,模拟加载更多。

      打开页面,我们可以发现页面底部有一个加载更多的按钮,而当我们等页面加载完成,加载更多这个按钮便会消失,因而我们可以利用nightmare的wait()方法,等待页面加载完毕后再去进行解析。

     

     

       通过nightmare的API可以看到wait()方法如下,这说明wait会一直等待,直到返回ture,才会执行下面的操作(默认等待时间30s),因而结合YouTube页面加载的特性,可以考虑如果页面正在加载,我们返回false,如果页面底部还有加载更多,我们模拟点击按钮,同样返回false,直到检查到页面底部没有加载更多按钮时,我们才返回true,此时我们的页面就全部加载完成了,再去对页面进行解析就可以获取到加载更多以后的内容。

     

      新建crawler.js文件,编辑如下代码:

    var Nightmare = require('nightmare');
    var nightmare = Nightmare({
        show: true,
        pollInterval: 50
    });
    
    nightmare
        .goto('https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ')
        //.inject('js', 'jquery-2.1.1.min.js')
        .wait(function() {
            var loadMoreText = document.querySelector('.load-more-text');
            if (loadMoreText == null) return true;
            if (/hid/.test(loadMoreText.classList.value)) return false;
            document.querySelector('.browse-items-load-more-button').click();
            return false;
        })
        .evaluate(function() {
    //获取订阅号Id
    var $channelName = $('#c4-primary-header-contents .branded-page-header-title a').attr('href');
    var $channelId = $channelName.match(/channel\/([a-zA-Z0-9_-]+)/);
    
    var category = [];
    $('#browse-items-primary .branded-page-module-title').each(function(){
      var $category = $(this).find('a').first();
      var item = {
        categoryName : $category.text().trim(),
        url : 'https://www.youtube.com' + $category.attr('href')
      };
      //根据URL判断为订阅号还是视频分类
      if(item.url.indexOf('list')!==-1){
        if(Array.isArray($channelId)){
          item.channelId = $channelId[1];
        }           
      }else{
        var s = item.url.match(/channel\/([a-zA-Z0-9_-]+)/);
        if(Array.isArray(s)){
          item.id = s[1];
        }
      }
      //获取youtube某个订阅号下的视频分类
      if(item.categoryName!==''&&item.hasOwnProperty('channelId')){
        category.push(item);
      }
    });
    return category;
    }) 
    .end()
    .then(function(result) {
       console.log(result); 
       console.log(result.length);
    })
    .catch(function(err) { 
      console.log(err);
    })

      

      运行代码后会发现这个订阅号下的所有视频分类都会显示出来,到这里我们的任务就完成了。

     三、总结

       通过这个简单的例子,我发现了nightmare的强悍之处,但是不适合用于处理大量页面,因为这样运行时间会很长,可以将其用于获取相关列表的url等信息,获取下来后到具体的页面可以采取其他模块,如node-crawler。采用笔记三的思路,我爬取YouTube时,先通过订阅号获取到每个订阅号下面的分类列表,再通过分类列表获取到视频列表,然后再到具体的视频。我通过测试5个订阅号,获取到视频列表大概花去5分钟的时间,视频数量大约为15000个,这个速度还可以。但是当我进入具体视频时,页面就变成了一万多个,解析一个差不多会花掉两秒的样子,这样的话速度太慢了。因而到第三层可以考虑采用其他模块解析,源码请点击

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-11-15 15:57 , Processed in 1.102397 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表