最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • nodejs爬虫

    正文概述 掘金(BenjaminShih)   2021-01-21   475

    什么是爬虫?

    wiki是这么解释的:

    robots协议

    robots.txt是一种存放于网站根目录下的ASCII编码的文本文件,它通常告诉网络搜索引擎的漫游器(又称网络爬虫),此网站中的哪些内容是不应被搜索引擎的漫游器获取的,哪些是可以被漫游器获取的。

    Robots.txt协议并不是一个规范,而只是约定俗成的,所以并不能保证网站的隐私。

    说白了,这并不是一项需要强制遵守的规定,这只是一个君子之间的协议,防君子不防小人,但是不遵守这个协议可能导致不正当竞争,各位看官可以自行搜索下~

    现简单列举下robots.txt中的一些配置规则,有个大致的印象,也有助于对爬虫逻辑的理解

    • 允许所有机器人:User-agent: *
    • 仅允许特定的机器人:User-agent: name_spider
    • 拦截所有机器人:Disallow: /
    • 禁止机器人访问特定的目录:Disallow: /images/
    • ...

    反爬虫(Anti-Spider)


    一般网站从三个方面反爬虫:

    • 用户请求的Headers
    • 用户行为
    • 网站目录和数据加载方式
    • ...

    通过Headers反爬虫

    很多网站通过检测Headers的:

    • User-Agent
    • Referer

    反反爬虫策略:在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。

    基于用户行为反爬虫

    • 通过检测用户行为:
      • 同一IP短时间内多次访问同一页面
      • 同一账户短时间内多次进行相同操作

    反反爬虫策略:1、专门写一个爬虫,爬取网上公开的代理ip,每请求几次更换一个ip;2、每次请求后随机间隔几秒再进行下一次请求

    动态页面的反爬虫

    上述的几种情况大多都是出现在静态页面,还有一部分网站,我们需要爬取的数据是通过ajax请求得到,或者通过JavaScript生成的

    反反爬虫策略:找到ajax请求,也能分析出具体的参数和响应的具体含义,响应的json进行分析得到需要的数据。

    需要预备的知识


    • Javascript 及 JQuery
    • 简单的nodejs基础
    • http 网络抓包 和 URL 基础 对于前端工程师来讲真的是福利啊

    需要安装的依赖库


    • superagent
    • cheerio
    • eventproxy
    • async

    superagent


    superagent 是个轻量的的 http 方面的库,是 nodejs 里一个非常方便的客户端请求代理模块,方便我们进行 get、post 等网络请求 [slide]

    cheerio


    可以理解成一个 Nodejs 版的 jQuery,用来从网页中以 css selector 获取数据,使用方式跟 jquery 一毛一样的。

    eventproxy


    eventproxy 模块是控制并发用的,它来帮你管理到底这些异步操作是否完成,有时我们需要同时发送 N 个 http 请求,然后利用得到的数据进行后期的处理工作, 请求完成之后,它会自动调用你提供的处理函数,并将抓取到的数据当参数传过来,方便处理。

    async


    async是一个流程控制工具包,提供了直接而强大的异步功能:mapLimit(arr, limit, iterator, callback)

    还有强大的同步功能: mapSeries(arr, iterator, callback)

    爬虫实践

    光说不练假把式,那么咱就开始吧~

    先定义依赖库和全局变量~

    // node自带的模块
    const path = require('path')
    const url = require('url');
    const fs = require('fs')
    // npm安装的依赖库
    const superagent = require('superagent');
    const cheerio = require('cheerio');
    const eventproxy = require('eventproxy');
    const async = require('async');
    const mkdir = require('mkdirp')
    // 设置爬虫目标URL
    var targetUrl = 'https://cnodejs.org/';
    
    //-------- 1 ----------
    // 最简单的爬虫
    superagent.get(targetUrl)
    	.end(function(err, res){
    	  	console.log(res);
    	})
    

    三行代码~但这的确是一个爬虫程序,将页面信息输出到terminal~

    因为要获取页面上的资源,而且要应用cheerio(node版的jQuery)来选择页面上指定class或者id中的内容,所以我们要先分析自己想获取页面的结构,打开google chrome的元素选择器,比如说现在我们只想获取cnode上每个词条点击的url。

    将1中的程序加入cheerio获取urls:

    // -------- 2 ----------
    // 加入cheerio获取页面指定内容
    
    superagent.get(targetUrl)
    	.end(function(err, res){
    		var $ = cheerio.load(res.text);
    		$('#topic_list .topic_title').each(function(index, element){
    		var href = $(element).attr('href');
    		console.log(href);
    		})
    	})
    

    输出:nodejs爬虫

    这都是相对路径呀,肿么办?别急,有url模块:

    // ---------- 3 -------------
    var href = url.resolve(targetUrl, $(element).attr('href'));
    

    然后继续在执行程序输出:nodejs爬虫

    获取urls只是第一步,现在我们要获取把urls所指向的页面中的内容获取过来,比如说,我们要获取二级页面中的标题和第一个评论,然后打印出来。

    这里我们要加入eventproxy模块来优雅地控制指定次数异步之后执行回调函数:

    // ---------- 4 -------------
    // 加入eventproxy来控制计数后回调
    
    var topicUrls = [];	
    
    function getTopicUrls() {
    	// ----3----中的代码片段
    };
    getTopicUrls()
    var ep = new eventproxy();
    // eventproxy 模块要先定义回调函数
    ep.after('crawled', topicUrls.length, function(topics) {
    	topics = topics.map(function(topicPair) {
    		var topicUrl = topicPair[0];
    		var topicHtml = topicPair[1];
    		var $ = cheerio.load(topicHtml);
    		return ({
    			title: $('.topic_full_title').text(),
    			href: topicUrl,
    			comment1: $('.reply_content .markdown-text').eq(0).text().trim()
    		});
    	});
    	console.log('outcome');
    	console.log(topics);
    });
    
    topicUrls.forEach(function(topicUrl) {
    	superagent.get(topicUrl)
    		.end(function(err, res){
    			console.log('fetch--' + topicUrl + '--successfully');
          		// eventproxy 告诉after函数,执行了一次异步,等到次数满足条件,就可以执行回调了
    			ep.emit('crawled', [topicUrl, res.text]);
    		});
    });
    

    输出:nodejs爬虫

    咦~这里outcome为空?什么鬼?我们又检查了下代码。shit!没有控制异步,导致执行topicUrls.forEach()的时候,topicUrls为空,当然是吗都没有,我们加入神器promise来改良下吧~

    //---------- 5 -----------
    // 加入promise控制
    var topicUrls = [];	
    function getTopicUrls() {
    	return new Promise(function(resolve){
    		... // 参照----4----中的代码
    	});
    };
    getTopicUrls().then(function(topicUrls){
    	... // 参照----4----中的代码
    })
    

    输出:nodejs爬虫

    惊喜来了,页面上出现了我们希望出现的标题、url、和评论,但是有一点不符合预期,仔细观察输出日志,会有很多空的对象字符串:nodejs爬虫

    难道是页面不可访问?

    我们复制这个没有输出的页面url到浏览器回车就回发现,页面其实是可以被访问的,因为在浏览器我们只是一次请求,而在爬虫程序中,因为node的高并发特性,我们在同一时间进行非常多次请求,如果超过服务器的负载,那么服务器就会崩溃,所以服务器一般都会有反爬虫的方法,而我们恰巧就遇到了这种情况,如何证明?我们直接输出urls所指向的所有页面(tips:因为terminal输出太多,可以使用linux命令|less来控制输出日志的分页翻页)

    仔细观察输出,不久就会发现以下日志:

    页面都是503,也就是服务器拒绝了我们的访问。

    我们来改良下我们的程序,async登场,来控制程序的并发,并且设置延迟:

    // ---------- 6 -----------
    // 设置延迟,并发控制为5
    // 打印出文章标题和第一条评论
    
    var topicUrls = [];	
    function getTopicUrls() {
    	return new Promise(function(resolve){
    		superagent.get(targetUrl)
    			.end(function(err, res){
    				if (err) {
    					return console.log('error:', err)
    				}
    				var $ = cheerio.load(res.text);
    				$('#topic_list .topic_title').each(function(index, element){
    					var href = url.resolve(targetUrl, $(element).attr('href'));
    					topicUrls.push(href);
    					resolve(topicUrls);
    				})
    			});
    	});
    };
    getTopicUrls().then(function(topicUrls){
    	var ep = new eventproxy();
    	ep.after('crawled', topicUrls.length, function(topics) {
    		topics = topics.map(function(topicPair) {
    			var topicUrl = topicPair[0];
    			var topicHtml = topicPair[1];
    			var $ = cheerio.load(topicHtml);
    			return ({
    				title: $('.topic_full_title').text(),
    				href: topicUrl,
    				comment1: $('.reply_content .markdown-text').eq(0).text().trim()
    			});
    		});
    		console.log('------------------------ outcomes -------------------------');
    		console.log(topics);
    		console.log('本次爬虫结果总共' + topics.length + '条')
    	});
    	var curCount = 0;
    	// 设置延时
        function concurrentGet(url, callback) {
        	var delay = parseInt((Math.random() * 30000000) % 1000, 10);
    	    curCount++;
    		setTimeout(function() {
    		    console.log('现在的并发数是', curCount, ',正在抓取的是', url, ',耗时' + delay + '毫秒');  
    	    	superagent.get(url)
    				.end(function(err, res){
    					console.log('fetch--' + url + '--successfully');
    					ep.emit('crawled', [url, res.text]);
    				});
    		    curCount--;
    		    callback(null,url +'Call back content');
    		}, delay);
        }
    
    	// 使用async控制异步抓取 	
    	// mapLimit(arr, limit, iterator, [callback])
    	// 异步回调
    	async.mapLimit(topicUrls, 5 ,function (topicUrl, callback) {
    		    concurrentGet(topicUrl, callback);
    	    });
    })
    

    再看下输出日志:nodejs爬虫

    nodejs爬虫

    强迫症患者觉得很舒服有木有~

    等等~还有一个重点没讲呢!对于诸多程序猿宅男来讲(此处省略万字),有了上面的基础,实现图片爬虫和储存也是很简单的额,比如说下载刚才讲的例子中的二级页面中的作者头像,分析页面的步骤就不多加描述了,直接上代码:

    var dir = './images'
    // 创建目录图片存储
    mkdir(dir, function(err) {
    	if(err) {
    	    console.log(err);
    	}
    });
    
    //---------- 7 -----------
    // 设置延迟,并发控制为5
    // 下载头像
    
    var topicUrls = [];	
    function getTopicUrls() {
    	return new Promise(function(resolve){
    		...// 参照---6---
    	});
    };
    getTopicUrls().then(function(topicUrls){
    	var ep = new eventproxy();
    	ep.after('crawled', topicUrls.length, function(topics) {
    		var imgUrls = []
    		topics = topics.map(function(topicPair) {
    			...// 参照---6---
    			imgUrls.push($('.user_avatar img').attr('src'));
    		});
          	// 下载图片的使用异步可能会导致没下载完然后图片破损了,这边使用async.mapSeries串行执行
    		async.mapSeries(imgUrls, function (imgUrl, callback) {
    			// 创建文件流
    			const stream = fs.createWriteStream(dir + '/' + path.basename(imgUrl) + '.jpg');
    			const res = superagent.get(imgUrl);
    			// res.type('jpg')
    			res.pipe(stream);
    			console.log(imgUrl, '--保存完成');
    		    callback(null, 'Call back content');
    	    });
    		console.log('------------------------ outcomes -------------------------');
    		console.log('本次爬虫结果总共' + topics.length + '条');
    	});
    	var curCount = 0;
    	// 并发数量
        function concurrentGet(url, callback) {
        	...// 参照---6---
        }
    	// 使用async控制异步抓取 
    	async.mapLimit(topicUrls, 5 ,function (topicUrl, callback) {
    		    concurrentGet(topicUrl, callback);
    	    });
    })
    
    

    执行程序,然后你就会发现images文件夹下面多了很多图片了....嘿嘿嘿~


    起源地下载网 » nodejs爬虫

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元