Nodejs 天涯帖子《鹿鼎記中計》 柳成萌著 下載爬蟲

功能:從天涯帖子中下載樓主發言到一個文本文件中html

實驗對象:http://bbs.tianya.cn/post-no05-308123-1.shtml  《鹿鼎記中計》 柳成萌著數組

爬取效果:除第一個貼須要手動下載外,其它都可自動完成,並有斷點續傳功能。 app

爬取結果下載:https://files.cnblogs.com/files/xiandedanteng/ludingjizhongji.zip   這是篇好文章,值得一讀。函數

代碼:post

//======================================================
// 天涯帖子下載爬蟲1.00
// 目標:http://bbs.tianya.cn/post-no05-308123-1.shtml
// 2018年3月22日
//======================================================

// 內置https模塊
var https=require("https");

// 內置http模塊
var http=require("http");

// 用於解析gzip網頁(ungzip,https獲得的網頁是用gzip進行壓縮的)
var zlib = require('zlib'); 

// 內置文件處理模塊,用於建立目錄和圖片文件
var fs=require('fs');

// 用於轉碼。非Utf8的網頁如gb2132會有亂碼問題,須要iconv將其轉碼
var iconv = require('iconv-lite');

// cheerio模塊,提供了相似jQuery的功能,用於從HTML code中查找圖片地址和下一頁
var cheerio = require("cheerio");

// 請求參數,JSON格式,http和https都有使用
var options;

// request請求
var req;

// 數據數組,找到的帖子時間和內容會放到這裏
var datas=[];

//--------------------------------------
// 爬取網頁,找帖子內容,再爬
// pageUrl sample:http://bbs.tianya.cn/post-no05-308123-1.shtml
//--------------------------------------
function crawl(pageUrl){
    console.log("Current page="+pageUrl);

    // 獲得hostname和path
    var currUrl=pageUrl.replace("http://","");
    var pos=currUrl.indexOf("/");
    var hostname=currUrl.slice(0,pos);    
    //console.log("hostname="+hostname);
    var path=currUrl.slice(pos);    
    //console.log("path="+path);
    pos=currUrl.lastIndexOf("/");
    var dir="http://"+currUrl.slice(0,pos);            
    //console.log("dir="+dir);
    
    // 初始化options  
    options={
        hostname:hostname,
            port:80,
            path:path,// 子路徑
          method:'GET',        
    };

    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 = buffer.toString();// http://bbs.tianya.cn/post-no05-308123-1.shtml 無需解碼
            //console.log("body="+body);

            var $ = cheerio.load(body);        
            var count=0;

            // 因爲天涯帖子的特殊格式,開篇只好略過,讀者請自行添加
            /*
            $(".atl-main .atl-item .bbs-content").each(function(index,element){
                var content=$(element).text();

                var obj=new Object;
                obj.user="樓主";
                obj.time="開篇";
                obj.content=content;

                datas.push(obj); 

                count++;
                
            })  
            */

            // 找帖子內容放入數組
            $(".atl-item").each(function(index,element){
                var user=$(element).attr("js_username");
                //console.log("user="+user);

                if(user=="柳成萌"){// 柳成蔭是樓主ID
                    var innerHtml=$(element).html();
                    //console.log("innerHtml="+innerHtml);

                    // 找時間
                    var topicTime=null;
                    var $1=cheerio.load(innerHtml); 
                    $1(".atl-info span").each(function(index1,element){                        

                        if(index1==1){
                            topicTime=$1(element).text();
                            //console.log("topicTime="+topicTime);                        
                        }
                    });

                    // 找內容
                    var topicContent=null;
                    //var $1=cheerio.load(innerHtml); 
                    $1(".bbs-content").each(function(index1,element){
                        topicContent=$1(element).text().trim();
                        //console.log("topicContent="+topicContent);                        
                    });

                    // 內容和時間都找到了再放入數組
                    if(topicTime!=null && topicContent!=null){
                        // 先看有沒有
                        var isFound=false;
                        for(var i=0;i<datas.length;i++){
                            var value=datas[i];

                            if(value.time==topicTime){
                                isFound=true;
                                break;
                            }
                        }
                        
                        // 沒有再往裏放
                        if(isFound==false){
                            var obj=new Object;
                            obj.user=user;
                            obj.time=topicTime;
                            obj.content=topicContent;

                            datas.push(obj); 
                            console.log("user="+obj.user+" "+obj.time);    

                            count++;
                        }                        
                    }
                }
            })   
            console.log("找到帖子"+count+"條.");                
            
            // 找下一頁
            var nextPageUrl=null;            
            $(".js-keyboard-next").each(function(index,element){
                var text=$(element).text();

                if(text.indexOf('下頁')!=-1){
                    nextPageUrl=dir+$(element).attr("href");
                    //console.log("找到下一頁.="+nextPageUrl);
                }       
            })

            if(nextPageUrl==null){
                console.log(pageUrl+"已是最後一頁了.\n");
                saveFile(pageUrl,datas);// 保存
                download(datas);
            }else{
                console.log("繼續下一頁");
                crawl(nextPageUrl);
            }       
            
            
        }).on("error", function() {
            saveFile(pageUrl,datas);// 保存
            console.log("crawl函數失敗,請進入斷點續傳模式繼續進行");
        })
    });

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

    // 出錯處理
    req.on('error',function(err){
        console.log('請求發生錯誤'+err);  
        saveFile(pageUrl,datas);// 保存
        console.log("crawl函數失敗,請進入斷點續傳模式繼續進行");
    });

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

//--------------------------------------
// 下載內容
//--------------------------------------
function download(datas){

    var total=datas.length;
    console.log("總計有"+total+"條帖子將被下載.");

    // 合併內容
    var space = '____';
    var newLine = '\n';
    var chunks = [];
    var length = 0;

    for(var i=0;i<datas.length;i++){
        var data=datas[i];

        var value = space+data.content+newLine;// data.time也能夠加入
        var buffer = new Buffer(value);
        chunks.push(buffer);
        length += buffer.length;
    }

    var resultBuffer = new Buffer(length);
    for(var i=0,size=chunks.length,pos=0;i<size;i++){
        chunks[i].copy(resultBuffer,pos);
        pos += chunks[i].length;
    }

    // 寫入文件
    var fileName='result'+getNowFormatDate()+".txt";
    fs.appendFile('./'+fileName, resultBuffer, function (err) {
        if(err){
            console.log("不能寫入文件"+fileName);
            console.log(err);
        }
    });

    console.log("寫入文件"+fileName+"完成");
}


//--------------------------------------
// 取得當前時間
//--------------------------------------
function getNowFormatDate() {
    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 getInput(){
    process.stdin.resume();    
    process.stdout.write("\033[33m 新建模式輸入第一頁URL,斷點續傳模式輸入0,請輸入: \033[39m");// 草黃色
    process.stdin.setEncoding('utf8');
    
    process.stdin.on('data',function(text){
        var input=text.trim();
        process.stdin.end();// 退出輸入狀態    

        if(text.trim()=='0'){
            process.stdout.write("\033[36m 進入斷點續傳模式. \033[39m");    // 藍綠色

            // Read File
            fs.readFile('./save.dat','utf8',function(err,data){
                if(err){
                    console.log('讀取文件save.dat失敗,由於'+err);
                }else{
                    //console.log(data);
                    var obj=JSON.parse(data);

                    datas=obj.datas;
                    console.log('提取原有數據'+datas.length+'條');

                    crawl(obj.url);        
                }
            });
            
            // Resume crawl
        }else{
            process.stdout.write("\033[35m 進入新建模式. \033[039m");    //紫色

            crawl(input);            
        }
    });    
}

//--------------------------------------
// 將爬行中信息存入數據文件
//--------------------------------------
function saveFile(url,datas){
    var obj=new Object;
    obj.url=url;
    obj.datas=datas;

    var text=JSON.stringify(obj);
    fs.writeFile('./save.dat',text,function(err){
        if(err){
            console.log('寫入文件save.dat失敗,由於'+err);
        }
    });
}

// 調用getInput函數,程序開始
getInput();

下載文本截圖:ui

 

相關文章
相關標籤/搜索