• 【Nodejs】理想论坛帖子爬虫1.02


    在1.01版本中,我发现各回调函数找到数据后再插入数据库有个竞争问题不好解决,如果等所有回调都完成也没有好的处理方法,因为启动不止一处启动了新的TopicSpider实例。

    于是我决定把读数据和写DB分开,爬虫负责前一部分,insertDB.js负责后一部分。

    这样做避免了爬虫写DB竞争和判断所有回调都结束的难点问题。

    而insertDB.js中,等待所有读文件的回调都结束是有办法的,那就是并行化控制,毕竟读各个文件比较简单,易于用task控制。

    下面是爬虫代码:

    //======================================================
    // 理想论坛帖子下载爬虫1.02
    // 目标网址:http://www.55188.com/forum-8-1.html
    // 已经将获得数据和插入数据分开
    // 此文件代码负责找到数据并存入文件
    // 插入数据由insertDB.js负责
    // 2018年4月16日
    //======================================================
    
    // 内置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 spiderCount=0;
    
    // 完成任务的爬虫数
    var spiderFinished=0;
    
    // 爬虫获得的全部信息
    var allInfos=[];
    
    // 存放文件的目录
    var folder="";
    
    //-------------------------------
    // 爬虫类
    // index:爬虫序号
    // title:帖子标题
    // url:帖子地址
    // pageNo:帖子页码
    //-------------------------------
    function TopicSpider(title,url){
        var obj=new Object;
        spiderCount++;
        obj.index=spiderCount;    // 序号
        obj.title=title;        // 标题
        obj.url=url;            // 地址
        obj.pageNo=1;            // 页码
    
        obj.crawl=function(){
            // 得到hostname和path
            var currUrl=this.url.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);    
            
            // 初始化options  
            options={
                hostname:hostname,
                    port:80,
                    path:path,// 子路径
                  method:'GET',        
            };    
    
            // 请求并处理response
            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);    
                    var infos=[];// 获得的发帖人信息
    
                    // 得到下一页信息
                    var nexturl=null;
                    $(".pages_btns .pages strong").each(function(index,element){
                        
                        if(nexturl==null){
                            obj.pageNo=$(element).text();
                            
                            var nextNode=$(element).next();
                            if(nextNode && nextNode.attr("href")){
                                // 如有下一页再起新爬虫
                                nexturl='http://www.55188.com/'+nextNode.attr("href");
    
                                var nextspider=new TopicSpider(obj.title,nexturl);
                                nextspider.crawl();
                            }
                        }
                    })
    
                    // 得到发帖人信息
                    $(".postinfo").each(function(index,element){                    
                        var content=$(element).text();
                        content=content.replace(/s+/g,' ');// 空白字符替换为一个空格
                        var arr=content.split(" ");// 以空格劈分
                        
                        if(arr.length==7){
                            info={'url':obj.url,
                                  'title':obj.title,
                                  '楼层':arr[1],
                                  '作者':arr[2].replace('只看:',''),
                                  '日期':arr[4],
                                  '时间':arr[5]};
                            infos.push(info);
                            //console.log('info='+info);
                        }else if(arr.length==8){
                            info={'url':obj.url,
                                  'title':obj.title,
                                  '楼层':arr[1],
                                  '作者':arr[2].replace('只看:',''),
                                  '日期':arr[5],
                                  '时间':arr[6]};
                            infos.push(info);
                            //console.log('info='+info);
                        }
                    })
    
                    // 信息获取结束,打印出爬虫信息
                    var msg='';
                    msg+='小爬序号:'+obj.index+'
    ';
                    msg+='小爬爬取的帖子标题:'+obj.title+'
    ';
                    msg+='小爬爬取的页码:'+obj.pageNo+'
    ';
                    msg+='小爬爬取的帖子地址:'+obj.url+'
    ';
                    msg+='小爬获取的信息数量:'+infos.length+'
    ';                
                    obj.savefile(obj.index,infos); // 存文件
                    msg+='小爬#:'+obj.index+'任务完成
    ';
                    console.log(msg);                
    
                    // 计算比例
                    spiderFinished++;
                    var percent=(spiderFinished*100/spiderCount).toFixed(2);
                    console.log('
    启动的爬虫数:'+spiderCount+',完成任务的爬虫数:'+spiderFinished+',完成比例:'+percent+"%");
                    
                }).on("error", function(err) {
                    console.log(coloredText('请求失败,异常为'+err,'red'));  
                })
            });
    
            // 超时处理
            req.setTimeout(7500,function(){
                req.abort();
            });
    
            // 出错处理
            req.on('error',function(err){
                console.log(coloredText('请求发生错误'+err,'red'));  
            });
    
            // 请求结束
            req.end();    
            
        };
    
        // 保存文件
        obj.savefile=function(index,infos){
            var text=JSON.stringify(infos);
    
            filename='./'+folder+'/'+index+'.dat';
    
            fs.writeFile(filename,text,function(err){
                if(err){
                    console.log('写入文件'+filename+'失败,因为'+err);
                }
            });
        }
    
        return obj;
    }
    
    //-------------------------------
    // 找到每个论坛页的帖子
    // pageUrl:论坛页的地址
    //-------------------------------
    function findTopicsInPage(pageUrl){
        console.log("Current page="+pageUrl);
    
        // 得到hostname和path
        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);            
        
        // 初始化options  
        options={
            hostname:hostname,
                port:80,
                path:path,// 子路径
              method:'GET',        
        };    
    
        // 请求并处理response
        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);
    
                $(".forumdisplay a").each(function(index,element){                
                    var topicUrl='http://www.55188.com/'+$(element).attr("href");
                    var topicTitle=$(element).text();
    
                    // 启动子贴爬虫
                    
                    var spider=new TopicSpider(topicTitle,topicUrl);
                    spider.crawl();
                })  
            }).on("error", function(err) {
                console.log("findTopicsInPage函数失败"+err);
            })
        });
    
        // 超时处理
        req.setTimeout(7500,function(){
            req.abort();
        });
    
        // 出错处理
        req.on('error',function(err){
            console.log('请求发生错误'+err);  
        });
    
        // 请求结束
        req.end();    
    }
    
    //-------------------------------
    // 得到带颜色(前景色)的文字,用于在控制台输出
    // 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]
    }
    
    //-------------------------------
    // 入口函数
    // start:起始页,从1开始
    // end:终止页,>start
    //-------------------------------
    function main(start,end){
        console.log(coloredText('爬取任务开始','yellow'));
    
        folder='infos('+getNowFormatDate()+")";
    
        // 创建目录
        fs.mkdir('./'+folder,function(err){
            if(err){
                console.log("目录"+folder+"已经存在");
            }
        });
    
        for(var i=start;i<=end;i++){        
            pageUrl='http://www.55188.com/forum-8-'+i+'.html'
            findTopicsInPage(pageUrl);
        }
    }
    
    //-------------------------------
    // 测试函数,用于测试TopicSpider类
    //-------------------------------
    function testTopicSpider(){
        var spider=new TopicSpider('测试','http://www.55188.com/thread-8325667-1-1.html');
        spider.crawl();
    }
    
    //--------------------------------------
    // 取得当前时间
    //--------------------------------------
    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;
    }
    
    // 开始
    main(1,5);
    
    //testTopicSpider();

    写出的文件示例截图如下:

    这些文件是json格式的,示例:

    [{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"楼主","作者":"先见天机","日期":"2018-4-15","时间":"15:13"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"2楼","作者":"短线冲","日期":"2018-4-15","时间":"15:27"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"3楼","作者":"勇儿马甲","日期":"2018-4-15","时间":"15:52"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"4楼","作者":"崇心开始","日期":"2018-4-15","时间":"15:52"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"5楼","作者":"老法儿","日期":"2018-4-15","时间":"16:28"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"6楼","作者":"金山水财","日期":"2018-4-15","时间":"16:33"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"7楼","作者":"生活如愿","日期":"2018-4-15","时间":"17:19"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"8楼","作者":"李汶安","日期":"2018-4-15","时间":"17:41"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"9楼","作者":"极品之星","日期":"2018-4-16","时间":"07:57"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"10楼","作者":"叼叼狼","日期":"2018-4-16","时间":"08:54"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"11楼","作者":"先见天机","日期":"2018-4-16","时间":"09:47"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"12楼","作者":"先见天机","日期":"2018-4-16","时间":"09:56"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"13楼","作者":"先见天机","日期":"2018-4-16","时间":"10:10"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"14楼","作者":"先见天机","日期":"2018-4-16","时间":"10:23"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"15楼","作者":"先见天机","日期":"2018-4-16","时间":"10:35"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"16楼","作者":"先见天机","日期":"2018-4-16","时间":"10:51"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"17楼","作者":"先见天机","日期":"2018-4-16","时间":"10:58"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"18楼","作者":"先见天机","日期":"2018-4-16","时间":"11:00"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"19楼","作者":"先见天机","日期":"2018-4-16","时间":"11:10"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"20楼","作者":"先见天机","日期":"2018-4-16","时间":"11:17"}]

    而insertDB.js的代码如下:

    //======================================================
    // 在理想论坛帖子下载爬虫1.02生成数据文件后读取数据插入数据库
    // 2018年4月17日
    //======================================================
    var fs=require('fs');    // 内置文件处理模块
    var allInfos=[];        // 数据文件里获得的全部信息
    var folder;                // 数据文件所在的目录
    var tasks=[];            // 任务数组
    var finished=0;            // 已完成任务的数量
    
    //--------------------------------------
    // 将数据插入数据库
    //--------------------------------------
    function insertDB(){
        console.log("开始插入DB");
        console.log('总计将有'+allInfos.length+"条数据将被插入数据库");
        
        var mysql=require('mysql'); // 连接mysql数据库的模块
    
        var conn=mysql.createConnection({
            host:'127.0.0.1',
            port:'3306',
            database:'test',
            user:'root',
            password:'12345678',
        });
    
        conn.connect(function(err){
            if(err){
                console.log('与MySQL数据库建立连接失败');
            }else{
                console.log('与MySQL数据库建立连接成功');
    
                for(var i=0;i<allInfos.length;i++){
                    var info=allInfos[i];
                    sql="insert into test.topic7(floor,author,tdate,ttime,addtime,url,title) values ('"
                        +info['楼层']+"','"
                        +info['作者']+"','"
                        +info['日期']+"','"
                        +info['时间']+"',"
                        +"now(),'"
                        +info['url']+"','"
                        +info['title']+"' "
                        +" )";
    
                    conn.query(sql,function(err,result){
                        if(err){
                            console.log(err);
                        }else{
                            //console.log("Insert succeed");    
                        }
                                
                    })
                }
    
                conn.end();
                //console.log('全部数据插入完成');
            }
        }); 
    }
    
    //--------------------------------------
    // 检查是否完成
    //--------------------------------------
    function checkIfFinished(){
        finished++;
    
        // 读取任务全完成再插DB
        if(finished==tasks.length){
            console.log("数据总量:"+allInfos.length);
            insertDB();
        }
    }
    
    //--------------------------------------
    // 入口函数
    //--------------------------------------
    function main() {
        folder='./'+'infos(2018-04-17 5_38_18)';// 数据文件所在目录
    
        fs.readdir(folder,function(err,files){
            if(err) throw err;
    
            for(var index in files){
                var task=(function(file){
                    return function(){
                        fs.readFile(file,'utf8',function(err,data){
                            if(err){
                                console.log('读取文件失败,因为'+err);
                            }else{
                                var infos=JSON.parse(data);
                                allInfos=allInfos.concat(infos);   
                                
                                checkIfFinished();
                            }
                        });
                    }
                })(folder+'/'+files[index]);
    
                tasks.push(task);
            }
    
            for(var task in tasks){
                tasks[task]();
            }
        });    
    }
    
    // 开始
    main();

    下面是数据库截图:

    这样分离后,原有的问题就算解决了,Python爬虫也可以照此解决。

    2018年4月17日08点45分

  • 相关阅读:
    Spark学习之Spark调优与调试(二)
    Spark学习之Spark调优与调试(一)
    Spark学习之在集群上运行Spark
    Spark学习之编程进阶总结(二)
    Spark学习之编程进阶总结(一)
    Spark学习之数据读取与保存总结(二)
    Spark学习之数据读取与保存总结(一)
    Eclipse 出现项目没有错但是项目名称却有红色感叹号或者红叉的解决办法
    CF1284E New Year and Castle Construction
    CF559E Gerald and Path
  • 原文地址:https://www.cnblogs.com/heyang78/p/8861515.html
Copyright © 2020-2023  润新知