保存知乎收藏夹功能的NodeJS版本

前两天发现知乎收藏夹中的答案正在不断减少。。看来需要保存一下了,但之前别人的方式是用chrome插件(浏览器无法自动保存本地文件)+wget前后端配合来完成这个工作的,而且还有一些缺点(比如保存的html无法更名),十分麻烦,所以打算用后端程序来抓一下网页,最终还是选择了Node来实现,是因为想借此机会学习一下Node。

结果:好端端的异步编程被窝写成了同步编程。。这次就放同步编程的代码好了,下次再放异步的。。(而且发现在一个月前就有人用python和c#实现了一个足够好的版本。。一下就没动力了)

废话少说,放代码。(Node的基本知识不用我说了吧)

//自己用的小品代码,所以没有拆成多个模块,而是塞到一起了。
var http = require('http'),
    fs = require('fs'),
    path = require('path');


//自己封装的一个创建目录的工具函数,因为要创建的目录的父目录不存在时,不会像shell指令一样连父目录一块创建了,而是会报错。
//所以用try-catch,出错的话则把目录拆成多个部分,一步步检查。
function myMkdir(dir) {
    var nowPath = '';
    dir = path.normalize(dir.toString());
    try{
        if (fs.existsSync(dir) !== true) {
            fs.mkdirSync(dir);
        } else {
            console.log('该目录或文件已存在');
            return true;
        }
    } catch (e) {
        dir = path.normalize('/') === '\\'? dir.split('\\'):dir.split('/');
        while (dir.length > 0) {
            nowPath += dir.shift()+'\\';
                if (fs.existsSync(nowPath) === true && fs.statSync(nowPath).isDirectory() === false) {
                    console.log(nowPath+'处已有文件存在,请指定其他目录。');
                    return false;
                } else if (fs.existsSync(nowPath) === true && fs.statSync(nowPath).isDirectory() === true) {
                    continue;
                }else {
                    try {
                        fs.mkdirSync(nowPath);
                    } catch (e) {
                        console.log('创建目录'+nowPath+'失败,可能是权限不足,请指定其他目录。');
                        return false;
                    }
                }

        }
    }
    console.log('创建目录成功');
    return true;
}
//从module.paths获取程序运行的当前目录
function getNowPath () {
    //不同系统的斜杠不同,需要这样处理一下。
    var slash = path.normalize('/');
    return module.paths[0].split(slash).slice(0,-1).join(slash);
}

//src,dir
//传入两个参数,src和dir,src是指定知乎上收藏夹的url,dir是指定保存目录
//我的做法是,先检查目录dir,然后遍历src的目录页获得所有文章的链接,最后再一一下载
function main(argv) {
    var waitingList = [],nowBuffer,
        slash = path.normalize('/'),
        src = argv[0],
        dir = argv[1],
        links = [],
        nowPage = 0,    //当前页码
        totalPage, //总页码
        downloading = -1,
        whitespace = "[\\x20\\t\\r\\n\\f]",
        //node不能用jQuery和DOM的API很蛋疼。。所以正则只能写长一点。。
        rgetTotalPage = new RegExp('<span>'+whitespace+'*<a href="\\?page=(\\d+)">\\1<\\/a>'+whitespace+'*<\\/span>'+whitespace+'*<span>'+
            whitespace+'*<a href="\\?page=2">下一页<\\/a>'+whitespace+'*<\\/span>'+whitespace+'*<\\/div>');
    var getAllLinkThisPage = function () {
        nowPage++;
        var contentTmp = [];
        var reg = /href="(\/question\/\d+\/answer\/\d+)">/g,
            match = true;

        if (nowPage <= totalPage) {
            console.log('正在获取第'+nowPage+'页的链接');
            http.get(src+'?page='+nowPage,function (response) {
                response.on('data',function (chunk) {
                    contentTmp.push(chunk);
                });
                response.on('end',function () {
                    console.log('第'+nowPage+'页链接获取完毕');

                    contentTmp = Buffer.concat(contentTmp).toString();
                    while (reg.lastIndex < contentTmp.length && match) {
                        match = reg.exec(contentTmp);
                        if (match) {
                            var tmp = {type:'html',address:'http://www.zhihu.com'+match[1],dir:dir};
                            links.push(tmp);
                        }
                    }
                    contentTmp = [];
                    getAllLinkThisPage();
                });
            });
        } else {
            console.log('链接获取完毕,开始下载页面,总共有'+links.length+'个页面');
            download();
        }
    };
    var download = function () {
        downloading++;
        var rgetTitle = /<title>([\s\S]+)<\/title>/,
            match = true,
            title = '',
            fileName = '';
        if (downloading < links.length) {
            http.get(links[downloading].address,function (response) {
                console.log('开始下载第'+downloading+'个页面');
                console.log(links[downloading].address);
                var contentTmp = [];
                response.on('data',function (chunk) {
                    contentTmp.push(chunk);
                });
                response.on('end',function () {
                    contentTmp = Buffer.concat(contentTmp).toString();
                    title = rgetTitle.exec(contentTmp)[1].replace(/[\\\/:*?"<>|]/g,'');
                    fileName = links[downloading].dir+path.normalize('/')+title +'.'+links[downloading].type;
                    fs.writeFile(fileName,contentTmp,function (err) {
                        if (err) {
                            console.log('创建'+fileName+'文件失败,原因:');
                            console.log(err);
                        } else {
                            console.log('已成功保存'+fileName);
                        }
                    });
                    contentTmp = [];
                    download();
                });
            });
        } else {
            console.log(links.length+'个文件已下载完毕');
        }
    };
    if (src === undefined || src.indexOf('www.zhihu.com') < 0) {
        console.log('获取失败!请输入正确的知乎收藏夹网址,http://www.zhihu.com/collection/20026124');
        return false;
    }
    if (dir === undefined) {
        dir = getNowPath()+slash+'zhihu'+slash+src.split('/').slice(-1);
        console.log('没有指定存放位置,将使用默认位置:'+dir);
    }
    //创建目录
    if (fs.existsSync(dir) === false) {
        console.log('该目录不存在,开始创建目录');
        if(myMkdir(dir) === false) {
            console.log('创建目录出错,请看错误信息,若不能解决,请联系作者 Aeolia yiaolia@gmail.com');
            return false;
        }
    } else if (fs.existsSync(dir) === true && fs.statSync(dir).isDirectory() === false) {
        console.log('该目录已被文件占领,请移除该文件');
        return false;
    }
    console.log('开始连接'+src);
    http.get(src,function (response) {
        var content = [];
        console.log(response.statusCode);
        response.on('data',function (chunk) {
            content.push(chunk);
        });
        response.on('end',function () {
            content = Buffer.concat(content).toString();
            console.log('拿到目录文件');
            totalPage = rgetTotalPage.exec(content.toString())[1];
            console.log(totalPage);
            getAllLinkThisPage();
        });

    })
}

main(process.argv.slice(2));