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等模块直接获取,但对于动态页面,尽量先找数据接口,如果数据接口加密或者解析不出来,再去考虑用模拟浏览器,因为模拟浏览器会耗时间,当数据量一大,运行时间就会很长。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端