一直想做一个能把理想论坛指定页范围的帖子都能完整下载下来的爬虫,但未能如愿。

主要的障碍在并发数的控制和长时间任务的突然退出,比如想下载前五页的帖子,分析后可得到大约15000个主贴或子贴,如果用回调函数直接在循环中访问是不行的,程序会报Error:socket hang up异常,大约一次只能并发百个左右(其实这个数很神奇的和Python理想爬虫的最高线程数接近了)。如果一次性把一万五千个回调都发出来,那连本机的有线网络都会短时间崩塌,路由器的访问互联网的功能也会中断几分钟。

于是采用了EventProxy控制并发的方案,将并发数控制在20左右,但只能运行到680个任务左右,离跑完15000个还差很多,然后程序就退出了。

不知这种令人烦恼情况,主要决定于网络还是我的机器,还是两者都有。

当然,明天照此思路写Python的爬虫,应该不会遇到回调/多线程并发的问题,单线程做下去完全可以,只是慢点,但是数据的完整性能保障。

先把代码贴出来:

//======================================================
// 理想论坛帖子下载爬虫1.04
// 2018年4月25日
//======================================================
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();
}

//-------------------------------
// 保存每个帖子的细节
// topicUrl:帖子地址
//-------------------------------
function saveTopicDetail(index,topicUrl,topicTitle,ep){
    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:{
                'Referer':'http://www.55188.com',
              }*/
    };    

    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);
            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){
                //obj.savefile(index,infos); // 存文件
                var text=JSON.stringify(infos);

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

                fs.writeFile(filename,text,function(err){
                    if(err){
                        console.log('写入文件'+filename+'失败,因为'+err);
                    }
                });

                ep.emit('ok');//一个任务完成,触发一次ok事件
            }
        }).on("error", function(err) {
            console.log("saveTopicDetail函数请求后获取相应时出现异常"+err);
        })
    });

    // 超时处理
    req.setTimeout(7500,function(){
        req.abort();
    });

    // 出错处理
    req.on('error',function(err){
        console.log('saveTopicDetail函数请求时出现异常'+err);
    });

    // 请求结束
    req.end();
}

//-------------------------------
// 递归调用
// start:起始位置
//-------------------------------
function foo(start){
    console.log('start='+start);

    var most=20;// 并发数不要超过100
    var ep = new EventProxy();
    ep.after('ok',most,function(){
        foo(start+most);//一个批次任务完成,递归进行下一批任务
    });

    var q=0;
    for(var i=start;i<finalTopics.length;i++){
        if(q>=most){
            break;//最多添加most个任务
        }

        var item=finalTopics[i];
        saveTopicDetail(item.index,item.url,item.title,ep);

        q++;
    }
}

//-------------------------------
// 入口函数
// 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+"个帖子");

                /*for(var i=0;i<n;i++){
                    saveTopicDetail(i,'http://www.55188.com/thread-8350207-2-1.html',' T+4荐股赢金钻选股赛马场 ');
                }*/

                // 获得每个子贴所在地址,序号和标题
                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);
                    }
                }
                console.log('将生成文件'+index+'个');
                finalTopics=arr;

                foo(0);

                callback();
            },5000);
        },
    ]);
}

//--------------------------------------
// 通用函数,返回当前日期时间
//--------------------------------------
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;
}

// 开始
main(1,5);

控制台的输出示例:

C:\Users\horn1\Desktop\node.js\59-理想论坛爬虫1.04>node lixiang.js
准备创建目录
目录infos(2018-04-25 21_29_8)已创建
准备从以下页面寻找主贴
page=http://www.55188.com/forum-8-1.html
page=http://www.55188.com/forum-8-2.html
page=http://www.55188.com/forum-8-3.html
page=http://www.55188.com/forum-8-4.html
page=http://www.55188.com/forum-8-5.html
共找到350个帖子
将生成文件15334个
start=0
start=20
start=40
start=60
start=80
start=100
start=120
start=140
start=160
start=180
start=200
start=220
start=240
start=260
start=280
start=300
start=320
start=340
start=360
start=380
start=400
start=420
start=440
start=460
start=480
start=500
start=520
start=540
start=560
start=580
start=600
start=620
start=640
start=660
start=680

C:\Users\horn1\Desktop\node.js\59-理想论坛爬虫1.04>node lixiang.js

【Nodejs】理想论坛帖子下载爬虫1.04的更多相关文章

  1. 【nodejs】理想论坛帖子下载爬虫1.08

    //====================================================== // 理想论坛帖子下载爬虫1.09 // 使用断点续传模式,因为网络传输会因各种原因中 ...

  2. 【nodejs】理想论坛帖子下载爬虫1.07 使用request模块后稳定多了

    在1.06版本时,访问网页采用的时http.request,但调用次数多以后就问题来了. 寻找别的方案时看到了https://cnodejs.org/topic/53142ef833dbcb076d0 ...

  3. 【nodejs】理想论坛帖子下载爬虫1.06

    //====================================================== // 理想论坛帖子下载爬虫1.06 // 循环改成了递归,但最多下载千余文件就崩了 / ...

  4. 【Python】理想论坛帖子读取爬虫1.04版

    1.01-1.03版本都有多线程争抢DB的问题,线程数一多问题就严重了. 这个版本把各线程要添加数据的SQL放到数组里,等最后一次性完成,这样就好些了.但乱码问题和未全部完成即退出现象还在,而且速度上 ...

  5. 【Nodejs】理想论坛帖子爬虫1.01

    用Nodejs把Python实现过的理想论坛爬虫又实现了一遍,但是怎么判断所有回调函数都结束没有好办法,目前的spiderCount==spiderFinished判断法在多页情况下还是会提前中止. ...

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

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

  7. 【Python】爬取理想论坛单帖爬虫

    代码: # 单帖爬虫,用于爬取理想论坛帖子得到发帖人,发帖时间和回帖时间,url例子见main函数 from bs4 import BeautifulSoup import requests impo ...

  8. 【python】理想论坛帖子爬虫1.06

    昨天认识到在本期同时起一百个回调/线程后程序会崩溃,造成结果不可信. 于是决定用Python单线程操作,因为它理论上就用主线程跑不会有问题,只是时间长点. 写好程序后,测试了一中午,210个主贴,11 ...

  9. 【pyhon】理想论坛单帖爬虫取得信息存入MySql数据库

    代码: # 单帖爬虫,用于爬取理想论坛单个帖子得到发帖人,发帖时间和回帖时间并存入数据库,url例子见main函数 from bs4 import BeautifulSoup import reque ...

随机推荐

  1. Codeforces Round #353 (Div. 2) A. Infinite Sequence 水题

    A. Infinite Sequence 题目连接: http://www.codeforces.com/contest/675/problem/A Description Vasya likes e ...

  2. 移动端适配之REM

    随着手机等移动设备的普及,移动端带来的流量已经不可忽视,一个网站不只是只有pc的页面就足够了,移动端的适配已经势在必行.但是移动设备种类繁多,屏幕尺寸也千奇百怪,能不能找到一种方式可以适配所有的手机屏 ...

  3. leetcode660. Remove 9

    leetcode660. Remove 9 题意: 从整数1开始,删除任何包含9的整数,如9,19,29 ... 所以现在,你将有一个新的整数序列:1,2,3,4,5,6,7,8,10,11,... ...

  4. Flash Builder 4的快捷方式和调试技巧

    Flash Builder 4的快捷方式和调试技巧 来自于flex开发人员中心:http://www.adobe.com/cn/devnet/flex/articles/flashbuilder_sh ...

  5. onInterceptTouchEvent 与 onTouchEvent 分析与MotionEvent在ViewGroup与View中的分发

    onInterceptTouchEvent 与 onTouchEvent 分析与MotionEvent在ViewGroup与View中的分发        Notice:本文将紧接着 Android ...

  6. Iterative (non-recursive) Quick Sort

    An iterative way of writing quick sort: #include <iostream> #include <stack> #include &l ...

  7. C#流水号生成汇总(四)

    简单高效的ID生成方式 http://www.ikende.com/blog/6014522c24ff4ef89cfb430f9c5a8489 一个简单唯一ID生成规则 http://www.iken ...

  8. 【linux】linux查看文件大小,磁盘大小

    查看指定目录下 文件或目录大小超过多少的 查看 /backup/tomcat7/ 目录下 超过500M大小的文件 并展示 文件详情 find /backup/tomcat7/  -type f -si ...

  9. [翻译] Haneke(处理图片缓存问题)

    Haneke https://github.com/hpique/Haneke A lightweight zero-config image cache for iOS. 轻量级0配置图片缓存. H ...

  10. #line 的作用是改变当前行数和文件名称

    #line 的作用是改变当前行数和文件名称,它们是在编译程序中预先定义的标识符命令的基本形式如下:   #line number["filename"]其中[]内的文件名可以省略. ...