之前已经介绍了node.js的一些基本知识,下面这篇文章我们的目标是学习完本节课程后,能进行网页简单的分析与抓取,对抓取到的信息进行输出和文本保存。
爬虫的思路很简单:
确定要抓取的URL;
对URL进行抓取,获取网页内容;
对内容进行分析并存储;
重复第1步
在这节里做爬虫,我们使用到了两个重要的模块:
一、 hello world
说是hello world,其实首先开始的是最简单的抓取。我们就以cnode网站为例(https://cnodejs.org/),这个网站的特点是:
不需要登录即可访问首页和其他页面
页面都是同步渲染的,没有异步请求的问题
DOM结构清晰
代码如下:
var request = require('request'), cheerio = require('cheerio'); request('https://cnodejs.org/', function(err, response, body){ if( !err && response.statusCode == 200 ){ // body为源码 // 使用 cheerio.load 将字符串转换为 cheerio(jQuery) 对象, // 按照jQuery方式操作即可 var $ = cheerio.load(body); // 输出导航的html代码 console.log( $('.nav').html() ); } });
这样的一段代码就实现了一个简单的网络爬虫,爬取到源码后,再对源码进行拆解分析,比如我们要获取首页中第1页的 问题标题,作者,跳转链接,点击数量,回复数量。通过chrome,我们可以得到这样的结构:
每个div[.cell]是一个题目完整的单元,在这里面,一个单元暂时称为$item
{ title : $item.find('.topic_title').text(), url : $item.find('.topic_title').attr('href'), author : $item.find('.user_avatar img').attr('title'), reply : $item.find('.count_of_replies').text(), visits : $item.find('.count_of_visits').text() }
因此,循环div[.cell] ,就可以获取到我们想要的信息了:
request('https://cnodejs.org/?_t='+Date.now(), function(err, response, body){ if( !err && response.statusCode == 200 ){ var $ = cheerio.load(body); var data = []; $('#topic_list .cell').each(function(){ var $this = $(this); // 使用trim去掉数据两端的空格 data.push({ title : trim($this.find('.topic_title').text()), url : trim($this.find('.topic_title').attr('href')), author : trim($this.find('.user_avatar img').attr('title')), reply : trim($this.find('.count_of_replies').text()), visits : trim($this.find('.count_of_visits').text()) }) }); // console.log( JSON.stringify(data, ' ', 4) ); console.log(data); } }); // 删除字符串左右两端的空格 function trim(str){ return str.replace(/(^\s*)|(\s*$)/g, ""); }
二、爬取多个页面
上面我们只爬取了一个页面,怎么在一个程序里爬取多个页面呢?还是以CNode网站为例,刚才只是爬取了第1页的数据,这里我们想请求它前6页的数据(别同时抓太多的页面,会被封IP的)。每个页面的结构是一样的,我们只需要修改url地址即可。
2.1 同时抓取多个页面
首先把request请求封装为一个方法,方便进行调用,若还是使用console.log方法的话,会把6页的数据都输出到控制台,看起来很不方便。这里我们就使用到了上节文件操作内容,引入fs模块,将获取到的内容写入到文件中,然后新建的文件放到file目录下(需手动创建file目录):
// 把page作为参数传递进去,然后调用request进行抓取 function getData(page){ var url = 'https://cnodejs.org/?tab=all&page='+page; console.time(url); request(url, function(err, response, body){ if( !err && response.statusCode == 200 ){ console.timeEnd(url); // 通过time和timeEnd记录抓取url的时间 var $ = cheerio.load(body); var data = []; $('#topic_list .cell').each(function(){ var $this = $(this); data.push({ title : trim($this.find('.topic_title').text()), url : trim($this.find('.topic_title').attr('href')), author : trim($this.find('.user_avatar img').attr('title')), reply : trim($this.find('.count_of_replies').text()), visits : trim($this.find('.count_of_visits').text()) }) }); // console.log( JSON.stringify(data, ' ', 4) ); // console.log(data); var filename = './file/cnode_'+page+'.txt'; fs.writeFile(filename, JSON.stringify(data, ' ', 4), function(){ console.log( filename + ' 写入成功' ); }) } }); }
CNode分页请求的链接:https://cnodejs.org/?tab=all&page=2,我们只需要修改page的值即可:
var max = 6; for(var i=1; i<=max; i++){ getData(i); }
这样就能同时请求前6页的数据了,执行文件后,会输出每个链接抓取成功时消耗的时间,抓取成功后再把相关的信息写入到文件中: