前两天发现知乎收藏夹中的答案正在不断减少。。看来需要保存一下了,但之前别人的方式是用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>([sS]+)</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));