上周写了一个。今天继续来学习一下,写一个爬虫2.0版本。
这次我们不再爬博客园了,咋玩点新的,爬爬电影天堂。因为每个周末都会在电影天堂下载一部电影来看看。
talk is cheap,show me the code!
抓取页面分析
我们的目标:
1、抓取电影天堂首页,获取左侧最新电影的169条链接
2、抓取169部新电影的迅雷下载链接,并且并发异步抓取。
具体分析如下:
1、我们不需要抓取迅雷的所有东西,只需要下载最新发布的电影即可,比如下面的左侧栏。一共有170个,除去第一个(因为第一个里面有200部电影),一共有169部电影。
2、除了抓取首页的东西,我们还要抓取点进去之后,每部电影的迅雷下载链接
环境搭建
1、需要的东西:node环境、express、cherrio 这三个都是上一篇文章有介绍的,所以这里不再做介绍:
2、需要安装的新东西:
superagent:
作用:跟request差不多,我们可以用它来获取get/post等请求,并且可以设置相关的请求头信息,相比较使用内置的模块,要简单很多。
用法:
superagent-charset:
作用:解决编码问题,因为电影天堂的编码是gb2312,爬取下来的中文会乱码掉。
用法:
.get('/some-url')
.charset('gb2312') //这里设置编码
.end(function(err,res){
// Do something
});
async:
作用:Async是一个流程控制工具包,提供了直接而强大的异步功能,在这里作为处理并发来调用。
用法:这里需要用到的是:async.mapLimit(arr,limit,iterator,callback)
mapLimit可以同时发起多个异步操作,然后一起等待callback的返回,返回一个就再发起下一个。
arr是一个数组,limit并发数,将arr中的每一项依次拿给iterator去执行,执行结果传给最后的callback
eventproxy:
作用:eventproxy 起到了计数器的作用,它来帮你管理到底异步操作是否完成,完成之后,它会自动调用你提供的处理函数,并将抓取到的数据当参数传过来。
例如我首先抓取到电影天堂首页侧栏的链接,才可以接着抓取链接里面的内容。具体作用可以点这里
用法:
开始爬虫
主要的程序在app.js这里,所以看的话可以主要看app.js即可
1、首先定义一些全局变量,该引入的库引进来
var baseUrl = 'http://www.dytt8.net'; //迅雷首页链接
var newMovieLinkArr=[]; //存放新电影的url
var errLength=[]; //统计出错的链接数
var highscoreMovieArr=[] //高评分电影
在这里,我们先抓取首页的东西,把首页抓取到的页面内容传给 getAllMovieLink和highscoreMovie这两个函数来处理,
getAllMovieLink获取到了左侧栏除了第1部的电影的169电影。
highscoreMovie为左侧栏第一个链接,里面的都是评分比较高的电影。
上面的代码中,我们弄了一个计数器,当它执行完之后,我们就可以执行与‘get_topic_html‘名字对应的流程了,从而可以保证在执行完首页的抓取工作之后,再执行次级页面的抓取工作。
ep.emit('get_topic_html','get '+page+' successful');
highscoreMovie方法如下,其实我们这里的作用不大,只是我统计一下高评分电影首页的信息,懒的继续抓取了
3、分离出左侧栏的信息,
如下图,首页中,详情页的链接都在这里$('.co_content2 ul a')。
因此我们将左侧栏这里的详情页链接都遍历出来,保存在一个newMovieLinkArr这个数组里面。
getAllMovieLink方法如下:
4、对获取到的电影详情页进行爬虫,提取有用信息,比如电影的下载链接,这个是我们所关心的。
// 利用callback函数将结果返回去,然后在结果中取出整个结果数组。
var fetchUrl = function (myurl,callback) {
var fetchStart = new Date().getTime();
concurrencyCount++;
num+=1
console.log('现在的并发数是',concurrencyCount,',正在抓取的是',myurl);
superagent
.get(myurl)
.charset('gb2312') //解决编码问题
.end(function (err,ssres) {
if (err) {
callback(err,myurl + ' error happened!');
errLength.push(myurl);
return next(err);
}
var time = new Date().getTime() - fetchStart;
console.log('抓取 ' + myurl + ' 成功',',耗时' + time + '毫秒');
concurrencyCount--;
var $ = cheerio.load(ssres.text);
// 对<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>的结果进行处理<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
getDownloadLink($,function(obj){
res.write('<br/>');
res.write(num+'、电影<a href="/tag/mingcheng/" target="_blank" class="keywords">名称</a>--> '+obj.movieName);
res.write('<br/>');
res.write('迅雷下载<a href="/tag/lianjie/" target="_blank" class="keywords">链接</a>--> '+obj.downLink);
res.write('<br/>');
res.write('详情<a href="/tag/lianjie/" target="_blank" class="keywords">链接</a>--> <a href='+myurl+' target="_blank">'+myurl+'<a/>');
res.write('<br/>');
res.write('<br/>');
});
var result = {
movieLink: myurl
};
callback(null,result);
});
};
// 控制最大并发数为5,在结果中取出callback返回来的整个结果数组。
// mapLimit(arr,[callback])
async.mapLimit(newMovieLinkArr,5,function (myurl,callback) {
fetchUrl(myurl,callback);
},result) {
// 爬虫结束后的回调,可以做一些统计结果
console.log('抓包结束,一共抓取了-->'+newMovieLinkArr.length+'条数据');
console.log('出错-->'+errLength.length+'条数据');
console.log('高评分电影:==》'+highScoreMovieArr.length);
return false;
});
});
首先是async.mapLimit对所有详情页做了一个并发,并发数为5,然后再爬取详情页,爬详情页的过程其实和爬首页的过程是一样的,所以这里不做过多的介绍,然后将有用的信息打印到页面上。
5、执行命令之后的图如下所示:
浏览器界面:
这样,我们爬虫的稍微升级版就就完成啦。可能文章写的不是很清楚,我已经把代码上传到了github上,可以将代码运行一遍,这样的话比较容易理解。后面如果有时间,可能会再搞一个爬虫的升级版本,比如将爬到的信息存入mongodb,然后再在另一个页面展示。而爬虫的程序加个定时器,定时去抓取。
备注:如果运行在浏览器中的中文乱码的话,可以将谷歌的编码设置为utf-8来解决;
有误之处,欢迎指出