//======================================================
// 理想论坛帖子下载爬虫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);

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

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

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

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

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

  3. 【Nodejs】理想论坛帖子下载爬虫1.04

    一直想做一个能把理想论坛指定页范围的帖子都能完整下载下来的爬虫,但未能如愿. 主要的障碍在并发数的控制和长时间任务的突然退出,比如想下载前五页的帖子,分析后可得到大约15000个主贴或子贴,如果用回调 ...

  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. Linux免密登录

    ssh连接上服务器 ssh -p 端口 用户名@ip地址 获取本地的pub ssh key cd ~/.ssh vi id_rsa.pub 拷贝里面的内容 将拷贝的内容放到服务器的authorized ...

  2. hdu 4545 贪心 *

    题意:小明和他的好朋友小西在玩一个新的游戏,由小西给出一个由小写字母构成的字符串,小明给出另一个比小西更长的字符串,也由小写字母组成,如果能通过魔法转 换使小明的串和小西的变成同一个,那么他们两个人都 ...

  3. Markdown中如何插入视频 > iframe?

    关于Markdown中如何插入视频这一问题   网上众说纷纭,一直也没找到一个确切的答案,想来也是,这些东西毕竟还不算成熟.各种以前提供过的方法现在来讲,可能在更新或是关闭大潮中又没了   而且,Ma ...

  4. python开发_xml.etree.ElementTree_XML文件操作_该模块在操作XML数据是存在安全隐患_慎用

    xml.etree.ElementTree模块实现了一个简单而有效的用户解析和创建XML数据的API. 在python3.3版本中,该模块进行了一些修改: xml.etree.cElementTree ...

  5. ROS知识(18)----Pluginlib原理

    目录 Overview Example Providing a Plugin Registering/Exporting a Plugin The Plugin Description File Re ...

  6. Notepad++源代码阅读——窗口封装与继承

    引言 近期在看Notepad++的源代码,学习学习Win32 原生API的开发技巧. 本文以Notepad++ 1.0版本的源代码为例讲解如何封装windows窗口,实现面向对象开发,如何通过窗口的继 ...

  7. 修改WampServer的默认端口

    WampServer默认的安装端口是80,容易和已安装的ISS等其他服务冲突,导致WampServer无法启动. 无法启动的现象如下: 1.apache服务无法启动.问题所在:80端口冲突. 2.在浏 ...

  8. HDU 2222 Keywords Search 【AC自动机模板】

    询问有多少个模式串出现在了文本串里面.将模式串插入Trie树中,然后跑一边AC自动机统计一下就哦了. 献上一份模板. #include <cstdio> #include <cstr ...

  9. MVC扩展DataAnnotationsModelMetadataProvider给model属性对应的页面元素添加任意属性和值

    比如,有这样一个类: public class User    {        public string Name { get; set; }    } 当在强类型视图页,显示属性Name对应的i ...

  10. MySQL连接查询(inner join,left join和right join的区别)

    关系数据库由多个相关表组成,这些表使用已知为外键列的常用列链接在一起. 因此,从业务角度来看,每个表中的数据是不完整的. 例如,在示例数据库(yiibaidb)中,使用orderNumber列链接的o ...