nodejs爬蟲筆記(五)---利用nightmare模擬點擊下一頁
目標
以騰訊滾動新聞為例,利用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模擬點擊下一頁