【nodejs】理想論壇帖子下載爬蟲1.06

//======================================================
// 理想論壇帖子下載爬蟲1.06
// 循環改爲了遞歸,但最多下載千餘文件就崩了
// 2018年4月27日
//======================================================
var http=require("http");            // http模塊
var zlib = require('zlib');            // 用於解析gzip網頁
var fs=require('fs');                // 文件處理模塊
var iconv = require('iconv-lite');    // 用於轉碼。
var cheerio = require("cheerio");    // 用於從HTML中以相似jquery方式查找目標
var async=require('async');            // 用於異步流程控制
var EventProxy = require('eventproxy');// 用來控制併發

//--- 下面爲全局變量 ---
var folder;// 存文件的目錄
var topics=[]; // 帖子數組
var finalTopics=[];// 全部帖子加子貼的最終數組

//-------------------------------
// 用於建立目錄
//-------------------------------
function createFolder(){
    console.log('準備建立目錄');

    folder='infos('+currDateTime()+")";
    fs.mkdir('./'+folder,function(err){
        if(err){
            console.log("目錄"+folder+"已經存在");
        }else{
            console.log("目錄"+folder+"已建立");
        }
    });
}

//-------------------------------
// 瀏覽頁面找主貼
// start:開始頁,end:結束頁
//-------------------------------
function findTopics(start,end){
    console.log('準備從如下頁面尋找主貼');

    for(var i=start;i<=end;i++){        
        pageUrl='http://www.55188.com/forum-8-'+i+'.html'
        findTopicsInPage(pageUrl);
    }
}

//-------------------------------
// 找到每一個論壇頁的帖子
// pageUrl:論壇頁的地址
//-------------------------------
function findTopicsInPage(pageUrl){
    console.log("page="+pageUrl);

    var currUrl=pageUrl.replace("http://","");
    var pos=currUrl.indexOf("/");
    var hostname=currUrl.slice(0,pos);    
    var path=currUrl.slice(pos);    
    pos=currUrl.lastIndexOf("/");
    var dir="http://"+currUrl.slice(0,pos);            
    
    var options={
        hostname:hostname,
            port:80,
            path:path,
          method:'GET',        
    };    

    var req=http.request(options,function(resp){
        var html = [];

        resp.on("data", function(data) {
            html.push(data);
        })
        resp.on("end", function() {
            var buffer = Buffer.concat(html);

            var body = iconv.decode(buffer,'gb2312');
            var $ = cheerio.load(body);            

            $("tbody").each(function(index,element){
                var $tbody=cheerio.load($(element).html());

                var topic={};
                topic.pageCount=1;
                topic.url=null;
                topic.title=null;

                $tbody(".forumdisplay a").each(function(index,element){                
                    var topicUrl='http://www.55188.com/'+$tbody(element).attr("href");
                    var topicTitle=$tbody(element).text();

                    topic.url=topicUrl
                    topic.title=topicTitle;                        
                })

                $tbody(".threadpages").each(function(index,element){
                    topic.pageCount=$tbody(element).children().last().text();
                })
                
                if(topic.url!=null && topic.title!=null){
                    topics.push(topic);    
                }
            })  
        }).on("error", function(err) {
            console.log("findTopicsInPage函數請求後獲取響應時出現異常"+err);
        })
    });

    // 超時處理
    req.setTimeout(7500,function(){
        req.abort();
    });

    // 出錯處理
    req.on('error',function(err){
        console.log('findTopicsInPage函數請求時發生錯誤'+err);  
    });

    // 請求結束
    req.end();    
}

//-------------------------------
// 保存每一個帖子到文件,完成一個遞歸調本身一次
// Nodejs特殊的回調處理強迫咱們把for,while循環改爲遞歸方式
//-------------------------------
function saveTopicDetails(){
    if(finalTopics.length>0){
        var topic=finalTopics.pop();

        var topicUrl=topic.url;
        var topicTitle=topic.title;
        var index=topic.index;

        var currUrl=topicUrl.replace("http://","");
        var pos=currUrl.indexOf("/");
        var hostname=currUrl.slice(0,pos);    
        var path=currUrl.slice(pos);    
        pos=currUrl.lastIndexOf("/");
        var dir="http://"+currUrl.slice(0,pos);
        
        var options={
            hostname:hostname,
                port:80,
                path:path,
              method:'GET',        
                 headers:{
                    'Connection':'close',     
                  }
        };    

        var req=http.request(options,function(resp){
            req.setSocketKeepAlive(false);
            var html = [];

            resp.on("data", function(data) {
                html.push(data);
            })
            resp.on("end", function() {

                try {
                    var buffer = Buffer.concat(html);

                    var body = iconv.decode(buffer,'gb2312');
                    var $ = cheerio.load(body);
                    var infos=[];// 得到的發帖人信息

                    // 獲得發帖人信息
                    $(".postinfo").each(function(index,element){                    
                        var content=$(element).text();
                        content=content.replace(/\s+/g,' ');// 空白字符替換爲一個空格
                        var arr=content.split(" ");// 以空格劈分
                        
                        if(arr.length==7){
                            info={'url':topicUrl,
                                  'title':topicTitle,
                                  '樓層':arr[1],
                                  '做者':arr[2].replace('只看:',''),
                                  '日期':arr[4],
                                  '時間':arr[5]};
                            infos.push(info);
                            //console.log('info='+info);
                        }else if(arr.length==8){
                            info={'url':topicUrl,
                                  'title':topicTitle,
                                  '樓層':arr[1],
                                  '做者':arr[2].replace('只看:',''),
                                  '日期':arr[5],
                                  '時間':arr[6]};
                            infos.push(info);
                            //console.log('info='+info);
                        }
                    })

                    if(infos.length>0){
                        var text=JSON.stringify(infos);

                        filename='./'+folder+'/'+index+'.dat';

                        fs.writeFile(filename,text,function(err){
                            if(err){
                                console.log('寫入文件'+filename+'失敗,由於'+err);
                            }
                        });

                        if(index % 50==0){
                            console.log(coloredText(nowTime()+'第'+index+'個文件保存完畢','green'));//讓控制檯出點動靜
                        }

                        //req.end();
                        saveTopicDetails();//一個帖子完成,遞歸一次
                    }
                } catch(e) { 
                    var text=nowTime()+"saveTopicDetail函數請求時(on_end)獲取響應時出現異常"+e+'\n';
                    text+=nowTime()+" url="+topicUrl+'\n';
                    text+=nowTime()+" title="+topicTitle+'\n';
                    text+=nowTime()+" index="+index+'\n';
                    console.log(coloredText(text,'magenta'));
                    ///
                    //req.end();
                    saveTopicDetails();//出錯後依舊遞歸
                }
            }).on("error", function(err) {
                var text=nowTime()+"saveTopicDetail函數請求後(on_error)獲取響應時出現異常"+err+'\n';
                text+=nowTime()+" url="+topicUrl+'\n';
                text+=nowTime()+" title="+topicTitle+'\n';
                text+=nowTime()+" index="+index+'\n';
                console.log(coloredText(text,'red'));
                ///
                //req.end();
                saveTopicDetails();//出錯後依舊遞歸
            })
        });

        // 超時處理
        req.setTimeout(1000,function(){
            req.abort();
        });

        // 出錯處理
        // 這裏最容易出錯 Error: socket hang up
        req.on('error',function(err){
            var text=nowTime()+"saveTopicDetails函數請求時出現異常"+err+'\n';
            text+=nowTime()+" url="+topicUrl+'\n';
            text+=nowTime()+" title="+topicTitle+'\n';
            text+=nowTime()+" index="+index+'\n';
            console.log(coloredText(text,'red'));
            ///
            req.end();
            //finalTopics.push(topic);
            saveTopicDetails();//出錯後依舊遞歸
        });

        // 請求結束
        req.end();
    }
}

//-------------------------------
// 入口函數
// start:起始頁,從1開始
// end:終止頁,>start
//-------------------------------
function main(start,end){

    var flow=require('nimble');

    flow.series([
        function(callback){
            setTimeout(function(){ 
                createFolder();
                callback();
            },100);
        },

        function(callback){
            setTimeout(function(){ 
                findTopics(start,end);
                callback();
            },100);
        },

        function(callback){
            setTimeout(function(){ 
                var n=topics.length;
                console.log("共找到"+n+"個帖子");

                // 得到每一個子貼所在地址,序號和標題
                var index=0;
                var arr=[];
                for(var i=0;i<n;i++){
                    var topic=topics[i];

                    for(var j=1;j<=topic.pageCount;j++){
                        var regexp=new RegExp(/-(\d+)-(\d+)-(\d+)/);
                        var topicUrl=topic.url.replace(regexp,"-$1-"+j+"-$3");// 用正則表達式替換第二個數字
                        
                        index++;
                
                        var item={'index':index,'url':topicUrl,'title':topic.title};
                        arr.push(item);
                    }
                }                
                
                finalTopics=arr;// 全部帖子加子貼的最終數組
                finalTopics.reverse();
                console.log('擬生成文件'+finalTopics.length+'個');

                saveTopicDetails();

                callback();
            },3000);
        },
    ]);
}

//--------------------------------------
// 通用函數,返回當前日期時間 建立目錄用
//--------------------------------------
function currDateTime() {
    var date = new Date();
    var seperator1 = "-";
    var seperator2 = "_";
    var month = date.getMonth() + 1;
    var strDate = date.getDate();
    if (month >= 1 && month <= 9) {
        month = "0" + month;
    }
    if (strDate >= 0 && strDate <= 9) {
        strDate = "0" + strDate;
    }
    var currentdate =date.getFullYear() + seperator1 + month + seperator1 + strDate
            + " " + date.getHours() + seperator2 + date.getMinutes()
            + seperator2 + date.getSeconds();
    return currentdate;
}

//--------------------------------------
// 通用函數,返回當前日期時間 控制檯輸出時間用
//--------------------------------------
function nowTime() {
    var date = new Date();
    var seperator1 = "-";
    var seperator2 = ":";
    var month = date.getMonth() + 1;
    var strDate = date.getDate();
    if (month >= 1 && month <= 9) {
        month = "0" + month;
    }
    if (strDate >= 0 && strDate <= 9) {
        strDate = "0" + strDate;
    }
    var currentdate =date.getFullYear() + seperator1 + month + seperator1 + strDate
            + " " + date.getHours() + seperator2 + date.getMinutes()
            + seperator2 + date.getSeconds()+ " ";
    return currentdate;
}

//-------------------------------
// 獲得帶顏色(前景色)的文字,用於在控制檯輸出
// text:文字,color:前景色
//-------------------------------
function coloredText(text,color){
    var dic = new Array();
    dic["white"] = ['\x1B[37m', '\x1B[39m'];
    dic["grey"] = ['\x1B[90m', '\x1B[39m'];
    dic["black"] = ['\x1B[30m', '\x1B[39m'];
    dic["blue"] = ['\x1B[34m', '\x1B[39m'];
    dic["cyan"] = ['\x1B[36m', '\x1B[39m'];
    dic["green"] = ['\x1B[32m', '\x1B[39m'];
    dic["magenta"] = ['\x1B[35m', '\x1B[39m'];
    dic["red"] = ['\x1B[31m', '\x1B[39m'];
    dic["yellow"] = ['\x1B[33m', '\x1B[39m'];

    return dic[color][0]+text+dic[color][1];
}

// 開始
main(2,2);
相關文章
相關標籤/搜索