【原】小玩node+express爬蟲-2

上週寫了一個node+experss的爬蟲小入門。今天繼續來學習一下,寫一個爬蟲2.0版本。html

此次咱們再也不爬博客園了,咋玩點新的,爬爬電影天堂。由於每一個週末都會在電影天堂下載一部電影來看看。node

talk is cheap,show me the code!git

 

【原】小玩node+express爬蟲-1:http://www.cnblogs.com/xianyulaodi/p/6049237.htmlgithub

 

抓取頁面分析

咱們的目標:mongodb

一、抓取電影天堂首頁,獲取左側最新電影的169條連接express

二、抓取169部新電影的迅雷下載連接,而且併發異步抓取。數組

具體分析以下:瀏覽器

一、咱們不須要抓取迅雷的全部東西,只須要下載最新發布的電影便可,好比下面的左側欄。一共有170個,除去第一個(由於第一個裏面有200部電影),一共有169部電影。併發

 

二、除了抓取首頁的東西,咱們還要抓取點進去以後,每部電影的迅雷下載連接app

 

環境搭建

一、須要的東西:node環境、express、cherrio 這三個都是上一篇文章有介紹的,因此這裏再也不作介紹:點擊查看

二、須要安裝的新東西:

superagent:

做用:跟request差很少,咱們能夠用它來獲取get/post等請求,而且能夠設置相關的請求頭信息,相比較使用內置的模塊,要簡單不少。

用法:

var superagent = require('superagent');
superagent
.get('/some-url')
.end(function(err, res){
    // Do something 
});

 

superagent-charset:

做用:解決編碼問題,由於電影天堂的編碼是gb2312,爬取下來的中文會亂碼掉。

用法:

var superagent = require('superagent');
var charset = require('superagent-charset');
charset(superagent);

superagent
.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 起到了計數器的做用,它來幫你管理到底異步操做是否完成,完成以後,它會自動調用你提供的處理函數,並將抓取到的數據當參數傳過來

例如我首先抓取到電影天堂首頁側欄的連接,才能夠接着抓取連接裏面的內容。具體做用能夠點這裏

用法:

var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
  // 在全部文件的異步執行結束後將被執行 
  // 全部文件的內容都存在list數組中 
});
for (var i = 0; i < files.length; i++) {
  fs.readFile(files[i], 'utf-8', function (err, content) {
    // 觸發結果事件 
    ep.emit('got_file', content);
  });
}
//注意got_file這兩個名字必須對應

 

開始爬蟲

主要的程序在app.js這裏,因此看的話能夠主要看app.js便可

一、首先定義一些全局變量,該引入的庫引進來

var cheerio = require('cheerio'); //能夠像jquer同樣操做界面
var charset = require('superagent-charset'); //解決亂碼問題:
var superagent = require('superagent'); //發起請求 
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';  //迅雷首頁連接
var newMovieLinkArr=[]; //存放新電影的url
var errLength=[];     //統計出錯的連接數
var highScoreMovieArr=[] //高評分電影

 

二、開始爬取首頁迅雷首頁:

//先抓取迅雷首頁
(function (page) {
    superagent
    .get(page)
    .charset('gb2312')
    .end(function (err, sres) {
        // 常規的錯誤處理
        if (err) {
          console.log('抓取'+page+'這條信息的時候出錯了')
            return next(err);
        }
        var $ = cheerio.load(sres.text);
        // 170條電影連接,注意去重
        getAllMovieLink($);
        highScoreMovie($);
        /*
        *流程控制語句
        *當首頁左側的連接爬取完畢以後,咱們就開始爬取裏面的詳情頁
        */
        ep.emit('get_topic_html', 'get '+page+' successful');
    });
})(baseUrl);

在這裏,咱們先抓取首頁的東西,把首頁抓取到的頁面內容傳給 getAllMovieLink和highScoreMovie這兩個函數來處理,

getAllMovieLink獲取到了左側欄除了第1部的電影的169電影。

highScoreMovie爲左側欄第一個連接,裏面的都是評分比較高的電影。

上面的代碼中,咱們弄了一個計數器,當它執行完以後,咱們就能夠執行與‘get_topic_html‘名字對應的流程了,從而能夠保證在執行完首頁的抓取工做以後,再執行次級頁面的抓取工做。

ep.emit('get_topic_html', 'get '+page+' successful');

 

highScoreMovie方法以下,其實咱們這裏的做用不大,只是我統計一下高評分電影首頁的信息,懶的繼續抓取了

//評分8分以上影片 200餘部!,這裏只是統計數據,再也不進行抓取
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) {
        // 常規的錯誤處理
        if (err) {
            console.log('抓取'+url+'這條信息的時候出錯了')
        }
        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);
            };
        }
    });
}

 

三、分離出左側欄的信息,

以下圖,首頁中,詳情頁的連接都在這裏$('.co_content2 ul a')。

所以咱們將左側欄這裏的詳情頁連接都遍歷出來,保存在一個newMovieLinkArr這個數組裏面。

getAllMovieLink方法以下:

// 獲取首頁中左側欄的全部連接
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);
        };
    }
}

 

四、對獲取到的電影詳情頁進行爬蟲,提取有用信息,好比電影的下載連接,這個是咱們所關心的。

// 命令 ep 重複監聽 emit事件(get_topic_html),當get_topic_html爬取完畢以後執行
ep.after('get_topic_html', 1, function (eps) {
    var concurrencyCount = 0;
    var num=-4; //由於是5個併發,因此須要減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);

            // 對獲取的結果進行處理函數
            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);
        });
    };

    // 控制最大併發數爲5,在結果中取出callback返回來的整個結果數組。
    // mapLimit(arr, limit, iterator, [callback])
    async.mapLimit(newMovieLinkArr, 5, function (myurl, callback) {
        fetchUrl(myurl, callback);
    }, function (err, result) {
        // 爬蟲結束後的回調,能夠作一些統計結果
        console.log('抓包結束,一共抓取了-->'+newMovieLinkArr.length+'條數據');
        console.log('出錯-->'+errLength.length+'條數據');
        console.log('高評分電影:==》'+highScoreMovieArr.length);
        return false;
    });
    
});

首先是async.mapLimit對全部詳情頁作了一個併發,併發數爲5,而後再爬取詳情頁,爬詳情頁的過程其實和爬首頁的過程是同樣的,因此這裏不作過多的介紹,而後將有用的信息打印到頁面上。

 

五、執行命令以後的圖以下所示:

 

瀏覽器界面:

       這樣,咱們爬蟲的稍微升級版就就完成啦。可能文章寫的不是很清楚,我已經把代碼上傳到了github上,能夠將代碼運行一遍,這樣的話比較容易理解。後面若是有時間,可能會再搞一個爬蟲的升級版本,好比將爬到的信息存入mongodb,而後再在另外一個頁面展現。而爬蟲的程序加個定時器,定時去抓取。

 

備註:若是運行在瀏覽器中的中文亂碼的話,能夠將谷歌的編碼設置爲utf-8來解決;

 

代碼地址:https://github.com/xianyulaodi/mySpider2

有誤之處,歡迎指出

相關文章
相關標籤/搜索