1. 程式人生 > >nodejs爬蟲筆記(五)---利用nightmare模擬點擊下一頁

nodejs爬蟲筆記(五)---利用nightmare模擬點擊下一頁

結果 lis out 類名 author 技術分享 日期 ext lan

目標

  以騰訊滾動新聞為例,利用nightmare模擬點擊下一頁,爬取所有頁面的信息。首先得感謝node社區godghdai的幫助,開始接觸不太熟悉nightmare,感覺很高大上,自己寫代碼的時候問題也很多,多虧大神的指點。

一、選擇模擬的原因

  騰訊滾動新聞,是每六十秒更新一次,而且有下一頁。要是直接獲取頁面的話得一頁一頁的獲取,不太方便,又想到了找數據接口,然後通過請求得到數據,結果騰訊新聞的數據接口是加密的,這種想法又泡湯了。因而想到筆記(四)中模擬加載更多的模塊,看利用nightmare這個模塊模擬點擊下一頁,是不是就可以獲取全部新聞的信息了呢。

二、分析頁面

  打開騰訊滾動新聞頁面,通過瀏覽器點擊檢查,選擇頁碼部分內容(如圖),此時是第一頁,上一頁的類名是"na",下一頁的類名是是"f12",再點擊第二頁的時候會發現上一頁和下一頁的類名都是"f12",要選擇下一頁,可以利用Jquery選擇器中過濾元素的方法,如:$(‘#pageArea .f12:contains("下一頁")‘),這樣就可以選擇點擊下一頁了。從下圖可以看到頁面去還有頁面總數,因此可以獲取頁面總數,然後通過頁面總數做一個點擊的判斷,直到點擊到最後一頁。再點擊到最後一頁,會發現下一頁的類名會變成"na",因此,我們也可以通過下一頁的類名變化判斷是否點擊到了最後一頁。

技術分享 技術分享

三、思路一(利用總頁數)

  1、在nightmare的wait方法裏面等待頁面加載完成。

  2、之後獲取總頁數,沒點擊一次,總頁數就減一,直到最後一頁,點擊完成,wait方法才返回true。

  在文件目錄下新建qqnightmare.js(需要安裝相關模塊),編輯如下代碼:

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){
              //定義變量 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用來定義變量,改寫代碼如下:

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);
	});

  再點擊運行,會發現所有的新聞都打印出來了。

四、思路二(根據下一頁類名判斷)

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文件,這個文件裏面包含了所有的方法等信息。

技術分享

技術分享

大神給的代碼:

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等模塊直接獲取,但對於動態頁面,盡量先找數據接口,如果數據接口加密或者解析不出來,再去考慮用模擬瀏覽器,因為模擬瀏覽器會耗時間,當數據量一大,運行時間就會很長。

nodejs爬蟲筆記(五)---利用nightmare模擬點擊下一頁