从0到1学nodejs爬虫小程序
来源:互联网 发布:中国男性阴茎 知乎 编辑:程序博客网 时间:2024/05/18 00:28
什么是爬虫?
wiki是这么解释的:
是一种“自动化浏览网络”的程序,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式。它们可以自动采集所有其能够访问到的页面内容,以供搜索引擎做进一步处理(分检整理下载的页面),而使得用户能更快的检索到他们需要的信息。
robots协议
robots.txt是一种存放于网站根目录下的ASCII编码的文本文件,它通常告诉网络搜索引擎的漫游器(又称网络爬虫),此网站中的哪些内容是不应被搜索引擎的漫游器获取的,哪些是可以被漫游器获取的。
Robots.txt协议并不是一个规范,而只是约定俗成的,所以并不能保证网站的隐私。
说白了,这并不是一项需要强制遵守的规定,这只是一个君子之间的协议,防君子不防小人,但是不遵守这个协议可能导致不正当竞争,各位看官可以自行搜索下~
现简单列举下robots.txt中的一些配置规则,有个大致的印象,也有助于对爬虫逻辑的理解
- 允许所有机器人:User-agent: *
- 仅允许特定的机器人:User-agent: name_spider
- 拦截所有机器人:Disallow: /
- 禁止机器人访问特定的目录:Disallow: /images/
- …
反爬虫(Anti-Spider)
一般网站从三个方面反爬虫:
* 用户请求的Headers
* 用户行为
* 网站目录和数据加载方式
* …
前两种比较容易遇到,大多数网站都从这些角度来反爬虫。第三种一些应用ajax的网站会采用,这样增大了爬取的难度。
通过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')// 设置爬虫目标URLvar 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); }) })
输出:
这都是相对路径呀,肿么办?别急,有url模块:
// ---------- 3 -------------var href = url.resolve(targetUrl, $(element).attr('href'));
然后继续在执行程序输出:
获取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]); });});
输出:
咦~这里outcome为空?什么鬼?我们又检查了下代码。shit!没有控制异步,导致执行topicUrls.forEach()的时候,topicUrls为空,当然是吗都没有,我们加入神器promise来改良下吧~
//---------- 5 -----------// 加入promise控制var topicUrls = []; function getTopicUrls() { return new Promise(function(resolve){ ... // 参照----4----中的代码 });};getTopicUrls().then(function(topicUrls){ ... // 参照----4----中的代码})
输出:
惊喜来了,页面上出现了我们希望出现的标题、url、和评论,但是有一点不符合预期,仔细观察输出日志,会有很多空的对象字符串:
难道是页面不可访问?
我们复制这个没有输出的页面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); });})
再看下输出日志:
强迫症患者觉得很舒服有木有~
等等~还有一个重点没讲呢!对于诸多程序猿宅男来讲(此处省略万字),有了上面的基础,实现图片爬虫和储存也是很简单的额,比如说下载刚才讲的例子中的二级页面中的作者头像,分析页面的步骤就不多加描述了,直接上代码:
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文件夹下面多了很多图片了….嘿嘿嘿~
- 从0到1学nodejs爬虫小程序
- nodeJs爬虫小程序练习
- 24小时从0到1开发阴阳师小程序
- 24小时从0到1开发阴阳师小程序
- 24小时从0到1开发阴阳师小程序
- NodeJs编写小爬虫
- nodejs小爬虫
- 一步一步学网络爬虫(从python到scrapy)
- 一步一步学网络爬虫(从python到scrapy)
- nodejs做小爬虫 nodejs 学习 day3
- 小程序从注册到使用流程
- 爬虫小程序的发展(1)
- 小爬虫程序
- java小爬虫程序
- python爬虫小程序
- 爬虫小程序-01
- 爬虫小程序-02
- 爬虫小程序
- java实现最简单socket通讯
- eclipse本地安装svn插件
- Win7+php7+apache2.4
- MPEG DASH简析
- 各个类型所占字节
- 从0到1学nodejs爬虫小程序
- SqlBulkCopy 之大数据插入
- 关于正则表达式中的lookahead
- 整理的一份敏感词sql
- 分数线划定
- Mocha+Unit.js操作实例代码
- Java 网络编程
- 常见BIOS界面
- android的四大组件之Content Provider