node+experss實(shí)現(xiàn)爬取電影天堂爬蟲
上周寫了一個(gè)node+experss的爬蟲小入門。今天繼續(xù)來(lái)學(xué)習(xí)一下,寫一個(gè)爬蟲2.0版本。
這次我們不再爬博客園了,咋玩點(diǎn)新的,爬爬電影天堂。因?yàn)槊總€(gè)周末都會(huì)在電影天堂下載一部電影來(lái)看看。
talk is cheap,show me the code!
抓取頁(yè)面分析
我們的目標(biāo):
1、抓取電影天堂首頁(yè),獲取左側(cè)最新電影的169條鏈接
2、抓取169部新電影的迅雷下載鏈接,并且并發(fā)異步抓取。
具體分析如下:
1、我們不需要抓取迅雷的所有東西,只需要下載最新發(fā)布的電影即可,比如下面的左側(cè)欄。一共有170個(gè),除去第一個(gè)(因?yàn)榈谝粋€(gè)里面有200部電影),一共有169部電影。
2、除了抓取首頁(yè)的東西,我們還要抓取點(diǎn)進(jìn)去之后,每部電影的迅雷下載鏈接
環(huán)境搭建
1、需要的東西:node環(huán)境、express、cherrio 這三個(gè)都是上一篇文章有介紹的,所以這里不再做介紹:點(diǎn)擊查看
2、需要安裝的新東西:
superagent:
作用:跟request差不多,我們可以用它來(lái)獲取get/post等請(qǐng)求,并且可以設(shè)置相關(guān)的請(qǐng)求頭信息,相比較使用內(nèi)置的模塊,要簡(jiǎn)單很多。
用法:
var superagent = require('superagent'); superagent .get('/some-url') .end(function(err, res){ // Do something });
superagent-charset:
作用:解決編碼問(wèn)題,因?yàn)殡娪疤焯玫木幋a是gb2312,爬取下來(lái)的中文會(huì)亂碼掉。
用法:
var superagent = require('superagent'); var charset = require('superagent-charset'); charset(superagent); superagent .get('/some-url') .charset('gb2312') //這里設(shè)置編碼 .end(function(err, res){ // Do something });
async:
作用:Async是一個(gè)流程控制工具包,提供了直接而強(qiáng)大的異步功能,在這里作為處理并發(fā)來(lái)調(diào)用。
用法:這里需要用到的是:async.mapLimit(arr, limit, iterator, callback)
mapLimit可以同時(shí)發(fā)起多個(gè)異步操作,然后一起等待callback的返回,返回一個(gè)就再發(fā)起下一個(gè)。
arr是一個(gè)數(shù)組,limit并發(fā)數(shù),將arr中的每一項(xiàng)依次拿給iterator去執(zhí)行,執(zhí)行結(jié)果傳給最后的callback
eventproxy:
作用:eventproxy 起到了計(jì)數(shù)器的作用,它來(lái)幫你管理到底異步操作是否完成,完成之后,它會(huì)自動(dòng)調(diào)用你提供的處理函數(shù),并將抓取到的數(shù)據(jù)當(dāng)參數(shù)傳過(guò)來(lái)。
例如我首先抓取到電影天堂首頁(yè)側(cè)欄的鏈接,才可以接著抓取鏈接里面的內(nèi)容。具體作用可以點(diǎn)這里
用法:
var ep = new EventProxy(); ep.after('got_file', files.length, function (list) { // 在所有文件的異步執(zhí)行結(jié)束后將被執(zhí)行 // 所有文件的內(nèi)容都存在list數(shù)組中 }); for (var i = 0; i < files.length; i++) { fs.readFile(files[i], 'utf-8', function (err, content) { // 觸發(fā)結(jié)果事件 ep.emit('got_file', content); }); } //注意got_file這兩個(gè)名字必須對(duì)應(yīng)
開始爬蟲
主要的程序在app.js這里,所以看的話可以主要看app.js即可
1、首先定義一些全局變量,該引入的庫(kù)引進(jìn)來(lái)
var cheerio = require('cheerio'); //可以像jquer一樣操作界面 var charset = require('superagent-charset'); //解決亂碼問(wèn)題: var superagent = require('superagent'); //發(fā)起請(qǐng)求 charset(superagent); var async = require('async'); //異步抓取 var express = require('express'); var eventproxy = require('eventproxy'); //流程控制 var ep = eventproxy(); var app = express(); var baseUrl = 'http://www.dytt8.net'; //迅雷首頁(yè)鏈接 var newMovieLinkArr=[]; //存放新電影的url var errLength=[]; //統(tǒng)計(jì)出錯(cuò)的鏈接數(shù) var highScoreMovieArr=[] //高評(píng)分電影
2、開始爬取首頁(yè)迅雷首頁(yè):
//先抓取迅雷首頁(yè) (function (page) { superagent .get(page) .charset('gb2312') .end(function (err, sres) { // 常規(guī)的錯(cuò)誤處理 if (err) { console.log('抓取'+page+'這條信息的時(shí)候出錯(cuò)了') return next(err); } var $ = cheerio.load(sres.text); // 170條電影鏈接,注意去重 getAllMovieLink($); highScoreMovie($); /* *流程控制語(yǔ)句 *當(dāng)首頁(yè)左側(cè)的鏈接爬取完畢之后,我們就開始爬取里面的詳情頁(yè) */ ep.emit('get_topic_html', 'get '+page+' successful'); }); })(baseUrl);
在這里,我們先抓取首頁(yè)的東西,把首頁(yè)抓取到的頁(yè)面內(nèi)容傳給 getAllMovieLink和highScoreMovie這兩個(gè)函數(shù)來(lái)處理,
getAllMovieLink獲取到了左側(cè)欄除了第1部的電影的169電影。
highScoreMovie為左側(cè)欄第一個(gè)鏈接,里面的都是評(píng)分比較高的電影。
上面的代碼中,我們弄了一個(gè)計(jì)數(shù)器,當(dāng)它執(zhí)行完之后,我們就可以執(zhí)行與‘get_topic_html‘名字對(duì)應(yīng)的流程了,從而可以保證在執(zhí)行完首頁(yè)的抓取工作之后,再執(zhí)行次級(jí)頁(yè)面的抓取工作。
ep.emit('get_topic_html', 'get '+page+' successful');
highScoreMovie方法如下,其實(shí)我們這里的作用不大,只是我統(tǒng)計(jì)一下高評(píng)分電影首頁(yè)的信息,懶的繼續(xù)抓取了
//評(píng)分8分以上影片 200余部!,這里只是統(tǒng)計(jì)數(shù)據(jù),不再進(jìn)行抓取 function highScoreMovie($){ var url='http://www.dytt8.net'+$('.co_content2 ul a').eq(0).attr('href'); console.log(url); superagent .get(url) .charset('gb2312') .end(function (err, sres) { // 常規(guī)的錯(cuò)誤處理 if (err) { console.log('抓取'+url+'這條信息的時(shí)候出錯(cuò)了') } var $ = cheerio.load(sres.text); var elemP=$('#Zoom p'); var elemA=$('#Zoom a'); for (var k = 1; k < elemP.length; k++) { var Hurl=elemP.eq(k).find('a').text(); if(highScoreMovieArr.indexOf(Hurl) ==-1){ highScoreMovieArr.push(Hurl); }; } }); }
3、分離出左側(cè)欄的信息,
如下圖,首頁(yè)中,詳情頁(yè)的鏈接都在這里$('.co_content2 ul a')。
因此我們將左側(cè)欄這里的詳情頁(yè)鏈接都遍歷出來(lái),保存在一個(gè)newMovieLinkArr這個(gè)數(shù)組里面。
getAllMovieLink方法如下:
// 獲取首頁(yè)中左側(cè)欄的所有鏈接 function getAllMovieLink($){ var linkElem=$('.co_content2 ul a'); for(var i=1;i<170;i++){ var url='http://www.dytt8.net'+linkElem.eq(i).attr('href'); // 注意去重 if(newMovieLinkArr.indexOf(url) ==-1){ newMovieLinkArr.push(url); }; } }
4、對(duì)獲取到的電影詳情頁(yè)進(jìn)行爬蟲,提取有用信息,比如電影的下載鏈接,這個(gè)是我們所關(guān)心的。
// 命令 ep 重復(fù)監(jiān)聽 emit事件(get_topic_html),當(dāng)get_topic_html爬取完畢之后執(zhí)行 ep.after('get_topic_html', 1, function (eps) { var concurrencyCount = 0; var num=-4; //因?yàn)槭?個(gè)并發(fā),所以需要減4 // 利用callback函數(shù)將結(jié)果返回去,然后在結(jié)果中取出整個(gè)結(jié)果數(shù)組。 var fetchUrl = function (myurl, callback) { var fetchStart = new Date().getTime(); concurrencyCount++; num+=1 console.log('現(xiàn)在的并發(fā)數(shù)是', concurrencyCount, ',正在抓取的是', myurl); superagent .get(myurl) .charset('gb2312') //解決編碼問(wèn)題 .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 + ' 成功', ',耗時(shí)' + time + '毫秒'); concurrencyCount--; var $ = cheerio.load(ssres.text); // 對(duì)獲取的結(jié)果進(jìn)行處理函數(shù) getDownloadLink($,function(obj){ res.write('<br/>'); res.write(num+'、電影名稱--> '+obj.movieName); res.write('<br/>'); res.write('迅雷下載鏈接--> '+obj.downLink); res.write('<br/>'); res.write('詳情鏈接--> <a href='+myurl+' target="_blank">'+myurl+'<a/>'); res.write('<br/>'); res.write('<br/>'); }); var result = { movieLink: myurl }; callback(null, result); }); }; // 控制最大并發(fā)數(shù)為5,在結(jié)果中取出callback返回來(lái)的整個(gè)結(jié)果數(shù)組。 // mapLimit(arr, limit, iterator, [callback]) async.mapLimit(newMovieLinkArr, 5, function (myurl, callback) { fetchUrl(myurl, callback); }, function (err, result) { // 爬蟲結(jié)束后的回調(diào),可以做一些統(tǒng)計(jì)結(jié)果 console.log('抓包結(jié)束,一共抓取了-->'+newMovieLinkArr.length+'條數(shù)據(jù)'); console.log('出錯(cuò)-->'+errLength.length+'條數(shù)據(jù)'); console.log('高評(píng)分電影:==》'+highScoreMovieArr.length); return false; }); });
首先是async.mapLimit對(duì)所有詳情頁(yè)做了一個(gè)并發(fā),并發(fā)數(shù)為5,然后再爬取詳情頁(yè),爬詳情頁(yè)的過(guò)程其實(shí)和爬首頁(yè)的過(guò)程是一樣的,所以這里不做過(guò)多的介紹,然后將有用的信息打印到頁(yè)面上。
5、執(zhí)行命令之后的圖如下所示:
瀏覽器界面:
這樣,我們爬蟲的稍微升級(jí)版就就完成啦??赡芪恼聦懙牟皇呛芮宄乙呀?jīng)把代碼上傳到了github上,可以將代碼運(yùn)行一遍,這樣的話比較容易理解。后面如果有時(shí)間,可能會(huì)再搞一個(gè)爬蟲的升級(jí)版本,比如將爬到的信息存入mongodb,然后再在另一個(gè)頁(yè)面展示。而爬蟲的程序加個(gè)定時(shí)器,定時(shí)去抓取。
備注:如果運(yùn)行在瀏覽器中的中文亂碼的話,可以將谷歌的編碼設(shè)置為utf-8來(lái)解決;
代碼地址:https://github.com/xianyulaodi/mySpider2
有誤之處,歡迎指出
相關(guān)文章
nodeJS實(shí)現(xiàn)路由功能實(shí)例代碼
本篇文章主要介紹了nodeJS實(shí)現(xiàn)路由功能實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06輕松創(chuàng)建nodejs服務(wù)器(7):阻塞操作的實(shí)現(xiàn)
這篇文章主要介紹了輕松創(chuàng)建nodejs服務(wù)器(7):阻塞操作的實(shí)現(xiàn),本文先是組出了代碼,然后對(duì)代碼一一分析,需要的朋友可以參考下2014-12-12node制作一個(gè)視頻幀長(zhǎng)圖生成器操作分享
這篇文章主要介紹了node制作一個(gè)視頻幀長(zhǎng)圖生成器操作分享,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08NodeJS實(shí)現(xiàn)單點(diǎn)登錄原理解析
隨著公司業(yè)務(wù)的增多,必然會(huì)產(chǎn)生各個(gè)不同的系統(tǒng),如果每個(gè)系統(tǒng)都需要單獨(dú)登錄的話就會(huì)很不方便,所以這個(gè)時(shí)候單點(diǎn)登錄會(huì)很方便,今天通過(guò)本文給大家講解NodeJS實(shí)現(xiàn)單點(diǎn)登錄原理解析,感興趣的朋友一起看看吧2022-05-05Node.js中的async?和?await?關(guān)鍵字微任務(wù)和宏任務(wù)
這篇文章主要介紹了Node.js中的async和await關(guān)鍵字微任務(wù)和宏任務(wù),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07Node配合WebSocket做多文件下載以及進(jìn)度回傳
這篇文章主要介紹了Node配合WebSocket做多文件下載以及進(jìn)度回傳功能,本文通過(guò)實(shí)例代碼效果截圖給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-11-11