zoukankan      html  css  js  c++  java
  • nodejs爬虫笔记(四)---利用nightmare解决加载更多问题

    目标:

      解决页面加载更多问题。笔记三中,我们只爬取到网页的部分信息,而点击加载更多后的页面内容是没有提取到的。开始我的想法是找到加载更多的数据接口(可参照: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个,这个速度还可以。但是当我进入具体视频时,页面就变成了一万多个,解析一个差不多会花掉两秒的样子,这样的话速度太慢了。因而到第三层可以考虑采用其他模块解析,源码请点击

  • 相关阅读:
    Easy UI form表单提交 IE浏览器不执行success ,以及 datagrid 展示过慢
    JS批量获取参数构建JSON参数对象
    Easy UI datebox控件无法正常赋值
    EasyUI控件combobox重复请求后台,dialog窗口数据异常
    后台Post/Get 请求接口 方式
    WebForm 页面ajax 请求后台页面 方法
    实现输入框小数多 自动进位展示,编辑时实际值不变
    页面获取Web控件ID不能正常获取,它惹得祸
    线性表的链式存储——单链表
    线性表
  • 原文地址:https://www.cnblogs.com/xiaxuexiaoab/p/7265910.html
Copyright © 2011-2022 走看看