nodejs爬虫笔记(五)---利用nightmare模拟点击下一页

目标

  腾讯滚动新闻为例,利用nightmare模拟点击下一页,爬取所有页面的信息。首先得感谢node社区godghdai的帮助,开始接触不太熟悉nightmare,感觉很高大上,自己写代码的时候问题也很多,多亏大神的指点。

一、选择模拟的原因

  腾讯滚动新闻,是每六十秒更新一次,而且有下一页。要是直接获取页面的话得一页一页的获取,不太方便,又想到了找数据接口,然后通过请求得到数据,结果腾讯新闻的数据接口是加密的,这种想法又泡汤了。因而想到笔记(四)中模拟加载更多的模块,看利用nightmare这个模块模拟点击下一页,是不是就可以获取全部新闻的信息了呢。

二、分析页面

  打开腾讯滚动新闻页面,通过浏览器点击检查,选择页码部分内容(如图),此时是第一页,上一页的类名是"na",下一页的类名是是"f12",再点击第二页的时候会发现上一页和下一页的类名都是"f12",要选择下一页,可以利用Jquery选择器中过滤元素的方法,如:$('#pageArea .f12:contains("下一页")'),这样就可以选择点击下一页了。从下图可以看到页面去还有页面总数,因此可以获取页面总数,然后通过页面总数做一个点击的判断,直到点击到最后一页。再点击到最后一页,会发现下一页的类名会变成"na",因此,我们也可以通过下一页的类名变化判断是否点击到了最后一页。

                

三、思路一(利用总页数)

  1、在nightmare的wait方法里面等待页面加载完成。

  2、之后获取总页数,没点击一次,总页数就减一,直到最后一页,点击完成,wait方法才返回true。

  在文件目录下新建qqnightmare.js(需要安装相关模块),编辑如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var Nightmare = require('nightmare');      
var nightmare = Nightmare({
    show: true//显示electron窗口
});
 
nightmare
    //加载页面
    .goto('http://roll.news.qq.com/')
    .wait(function() {
        return document.querySelectorAll("#artContainer li").length>0;//通过新闻列表的长度,来判断页面是否加载完成
    })
    .inject('js','jquery.min.js')//插入jquery
    .wait(function(){
        if(window.qqNews === undefined){<br>              //定义变量
            window.qqNews={
                page :$("#totalPage").val();,
                arr : []
            };
            if(qqNews.page!==1){
                $('#artContainer li').each(function(){
                    var title = $(this).find('a').text();
                    qqNews.arr.push(title);
                });
                $('#pageArea .f12:contains("下一页")').click();
                qqNews.page -= 1;
                return false;
            }
            if(qqNews.page===1){
                $('#artContainer li').each(function(){
                    var title = $(this).find('a').text();
                    qqNews.arr.push(title);
                });
                return true;
            }  
        }
        return false;
    })
    .evaluate(function(){
        return qqNews.arr;
    })
    .end()
    .then(function(res){
      console.log(res,res.length);
    })
    .catch(function (error) {
      console.error('failed:', error);
    });

  在后台打开文件夹,运行node qqnightmare ,会发现wait方法等待超时。检查代码后发现wait方法一直没有返回true,因为每次判断qqNews.page!==1,会返回false,再次调用又会重新定义一个变量,因此一直会返回false,根本不会返回true。是不是可以添加一个wait用来定义变量,改写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var Nightmare = require('nightmare');      
var nightmare = Nightmare({
    show: true//显示electron窗口
    // waitTimeout : 5000
});
 
nightmare
    //加载页面
    .goto('http://roll.news.qq.com/')
    .wait(function() {
        return document.querySelectorAll("#artContainer li").length>0;
    })
    .inject('js','jquery.min.js')
    .wait(function(){
        window.qqNews=[];
        page = $("#totalPage").val();
        return true;
    })
    .wait(function(){
         
        if(page!==1){
            $('#artContainer li').each(function(){
                var title = $(this).find('a').text();
                qqNews.push(title);
            });
            $('#pageArea .f12:contains("下一页")').click();
            page -= 1;
            return false;
        }
        if(page===1){
            $('#artContainer li').each(function(){
                var title = $(this).find('a').text();
                qqNews.push(title);
            });
            return true;
        }  
         
        return false;
    })
    .evaluate(function(){
        return qqNews;
    })
    .end()
    .then(function(res){
      console.log(res,res.length);
    })
    .catch(function (error) {
      console.error('failed:', error);
    });

  再点击运行,会发现所有的新闻都打印出来了。

四、思路二(根据下一页类名判断)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var Nightmare = require('nightmare');
var nightmare = Nightmare({
    show: true //显示electron窗口
});
nightmare
    .goto('http://roll.news.qq.com/')
    .wait(function() {
        return !document.querySelector(".loading");
    })
    .wait(function() {
        window._$qqNews = [];
        return true;
    })
    .wait(function() {
 
        //如果显示正在加载中……
        if (document.querySelector(".loading")) return false;
 
        var newslist = document.querySelectorAll("#artContainer li a");
        for (var i = 0; i < newslist.length; i++) {
            _$qqNews.push({
                title: newslist[i].childNodes[0].data,
                href: newslist[i].href
            });
        }
 
        var next_page_button = document.querySelector("#pageArea .f12:last-child");
        if (next_page_button) {
            next_page_button.click();
            return false;
        }
        return true;
 
    })
    .evaluate(function() {
        return _$qqNews;
    })
    .end()
    .then(function(res) {
        console.log(res[res.length-1], res.length);
    })
    .catch(function(error) {
        console.error('failed:', error);
    });

  运行后会发现所有新闻也都打印到后台了,另外大神还给出了另外一种方法,自己不太懂,也就不多说了,就放这存一下吧。

五、通过js文件获取全部新闻信息

  点击检查页面,会发现相应的js内容如下图,再点击sources查看js,会发现js文件夹下有qq.js文件,这个文件里面包含了所有的方法等信息。

大神给的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
var Nightmare = require('nightmare');
var nightmare = Nightmare({
    show: true,
    pollInterval: 1000
});
 
 
nightmare
    .goto('http://roll.news.qq.com/')
    .wait(function() {
        return document.querySelectorAll("#artContainer li").length>0;
    })
    .wait(function() {
 
        if (window._$qqNews == undefined) {
 
            window._$qqNews = {
                total: 0,
                page: 0,
                items: []
            }
 
            //停止自动刷新
            AutoRefresh();
            _$qqNews.total = qq.$("totalPage").value;
 
            G.showArtList = function(responseText) {
                try {
                    eval("var json = " + responseText);
                    if (json.response.code == "0") {
                        qq.$("artContainer").innerHTML = json.data.article_info;
 
                        //
                        var newslist = document.querySelectorAll("#artContainer li a");
                        for (var i = 0; i < newslist.length; i++) {
 
                            _$qqNews.items.push({
                                title: newslist[i].childNodes[0].data,
                                href: newslist[i].href
                            });
                        }
 
                        qq.$("totalPage").value = json.data.count;
 
                        if (_$qqNews.total > 1) {
                            _$qqNews.total -= 1;
                            nextPage();
                        }
 
 
                    } else if (json.response.code == "2") {
                        qq.$("totalPage").value = 1;
                        G.gotoPage(1);
                        qq.$("artContainer").innerHTML = '<div class="article-tips">该日期没有文章!</div>';
                    } else {
                        qq.$("totalPage").value = 1;
                        G.gotoPage(1);
                        qq.$("artContainer").innerHTML = '<div class="article-tips">文章加载失败!</div>';
                    }
                } catch (e) {}
            }
 
            //加载第1页
            Refresh();
 
            return false;
        }
 
        if (_$qqNews.total == 1)
            return true;
 
        return false;
 
    }).evaluate(function() {
 
        return _$qqNews.items;
    })
    .end()
    .then(function(result) {
        console.log(result, result.length)
    }).catch(function(error) {
        console.log('错误是:' + error);
    })

 运行之后,会发现运行时间相对来讲要比前面两种快一些。 

六、总结

   通过一段时间的爬虫,发现静态页面可以直接通过request和cheerio等模块直接获取,但对于动态页面,尽量先找数据接口,如果数据接口加密或者解析不出来,再去考虑用模拟浏览器,因为模拟浏览器会耗时间,当数据量一大,运行时间就会很长。

 

posted @   半夜打老虎  阅读(9652)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示