一个保存网页的小工具,基于Node实现

话说本机保存了大量的网页,多半是技术文章。javascript

浏览器自带的保存方式“还原度”不高,把内容粘贴到word吧又太费劲,是据说有一些现成的工具,但仍是决定本身造个轮子。。。css

网页自己、引用的脚本文件(默认不下载)、样式文件(样式文件里引用的图片也会下载下来)、图片等均可如下载下来,而后再替换网页、样式文件里的路径为本地路径。html

我得认可,使用起来有点费劲,须要先粘贴网址到剪贴板而后敲下回车,再粘贴“实际”的网页内容到剪贴板而后再敲下回车,工具自身会去读系统的剪贴板里的内容。java

之因此要求“实际”的网页内容而不是最初由服务器返回的HTML,缘由是有不少网页只有当你滚动到页面最下端或者等待脚本文件执行完毕,才会真正渲染完成。
 
拿Chrome举例, 你能够这样得到实际的网页内容:
打开开发者工具, 切换到Elements标签, 右击 html 元素, 出现上下文菜单, 选择 Copy > Copy outerHTML。
 
主文件 webpageGetter.js:
 1 const argv = require('minimist')(process.argv.slice(2));  2 const log = require('console').log;  3 const readlineSync = require('readline-sync');  4 const clipboard = require('clipboardy');  5 const shell = require('shelljs');  6 
 7 const fs = require('fs');  8 const path = require('path');  9 
 10 const digger = require('./digger.js');  11 const parser = require('./parser.js');  12 const getter = require('./getter.js');  13 const cssDigger = require('./cssDigger.js');  14 const cssParser = require('./cssParser.js');  15 
 16 const cfg = require('./configs/config.json');  17 const en = require('./configs/en.json');  18 const cn = require('./configs/cn.json');  19 
 20 let langCfg = argv['lang'] ? getLang(argv['lang']) : getLang('en');//设置语言 支持中文 英文
 21 if (!langCfg) {  22     langCfg = getLang('en');  23 }  24 
 25 if (argv['help']) {//显示帮助
 26  logContent(langCfg.intro);  27     process.exit(1);  28 }  29 
 30 const name = argv['name'] ? argv['name'] : '';//设置生成的html文件的名称
 31 const verbose = argv['verbose'] ? !!argv['verbose'] : true;//是否显示下载文件详细信息
 32 
 33 log(langCfg.help);  34 log(langCfg.lang);  35 log(langCfg.name);  36 log(langCfg.verbose);  37 
 38 waitAnyKey(langCfg.askURL);  39 const url = getFromClipboard();//从剪贴板读取网址
 40 waitAnyKey(langCfg.askContent);  41 let content = getFromClipboard();//从剪贴板读取实际网页内容
 42 
 43 const dest = genIdentifier();  44 shell.exec('md ' + dest);//建立存放资源文件的目录
 45 
 46 digger.config(cfg.html, content);  47 digger.dig();  48 const originalAddrList = digger.originalAddrList();//“挖掘”出网页里须要下载的路径信息
 49 
 50 parser.config(cfg.html, dest, getDomain(url), originalAddrList);  51 parser.parse();//生成实际的下载路径、用于替换的本地路径
 52 
 53 const done = getter.downloadAllOfThese(verbose, 'FROM HTML: ', parser.downloadAddrList(), parser.saveAddrList());  54 
 55 makeHtmlFile(name, content);//生成最终的Html文件
 56 
 57 done.then(() => {  58     fs.readdirSync(dest).map(function(file) {//Promise.then 当所有下载完毕后,遍历资源目录,针对每个样式文件进行“挖掘”
 59         if (file.substr(file.lastIndexOf('.')) === '.css') {  60             let content = fs.readFileSync(path.join(dest, file)).toString();  61 
 62  cssDigger.reset();  63  cssDigger.config(cfg.css, content);  64  cssDigger.dig();  65             const originalAddrList = cssDigger.originalAddrList();  66 
 67  cssParser.reset();  68  cssParser.config(cfg.css, dest, path.join(dest, file), parser.cssBox(), originalAddrList);  69  cssParser.parse();  70 
 71             if (cssParser.downloadAddrList().length > 0) {  72                 getter.downloadAllOfThese(true, 'FROM CSS: ', cssParser.downloadAddrList(), cssParser.saveAddrList());  73 
 74                 makeCssFile(file, content, originalAddrList, cssParser.replaceAddrList());//对原样式文件更名,生成一个新的样式文件,里面的路径已替换成本地路径
 75  }  76  }  77  })  78 })  79 
 80 function getLang(lang) {  81     return {'en': en, 'cn': cn}[lang];  82 }  83 
 84 function logContent(content) {  85     content.forEach((item) => {  86  log(item);  87  });  88 }  89 
 90 function waitAnyKey(queryText) {  91  readlineSync.question(queryText);  92 }  93 
 94 function getFromClipboard() {  95     return clipboard.readSync();  96 }  97 
 98 function getDomain(url) {  99     let domain = ''; 100 
101     if (url.substr(0, 7) != 'http://' && url.substr(0, 8) != 'https://') { 102         throw new Error(); 103  } 104     url = url.substr(url.length - 1) === '/' ? url : url + '/'; 105 
106     if (url.substr(0, 7) === 'http://') { 107         url = url.substr(7); 108         domain = 'http://' + url.substr(0, url.indexOf('/')); 109     } else { 110         url = url.substr(8); 111         domain = 'https://' + url.substr(0, url.indexOf('/')); 112  } 113 
114     return domain; 115 } 116 
117 function genIdentifier() { 118     const d = new Date(); 119 
120     return d.getFullYear().toString() + ifNeedZero(d.getMonth() + 1) + ifNeedZero(d.getUTCDate()) + ifNeedZero(d.getHours().toString()) + ifNeedZero(d.getMinutes().toString()) + ifNeedZero(d.getSeconds().toString()); 121 } 122 
123 function ifNeedZero(number) { 124     return number < 10 ? "0" + number.toString() : number.toString(); 125 } 126 
127 function replace(content, originalAddrList, replaceAddrList) { 128     originalAddrList.forEach((addr, idx) => { 129         addr = addr.replace(new RegExp('\\?', 'g'), '\\?'); 130         content = content.replace(new RegExp(addr, 'g'), replaceAddrList[idx]); 131  }); 132 
133     return content; 134 } 135 
136 function makeFile(name, content) { 137     const stream = fs.createWriteStream(name); 138  stream.write(content); 139  stream.end(); 140 } 141 
142 function makeHtmlFile(name, content) { 143     if (cfg.killScript) { 144         content = content.replace(new RegExp('<script.*?</script>', 'g'), ''); 145         content = content.replace(new RegExp('<script.*?/>', 'g'), ''); 146         content = content.replace(new RegExp('<script', 'g'), '<textarea style="display:none" '); 147         content = content.replace(new RegExp('</script>', 'g'), '</textarea>'); 148  } 149 
150     content = replace(content, originalAddrList, parser.replaceAddrList()); 151     makeFile(name + '_' + dest + '.html', '<!-- Current URL: ' + url + '-->\r\n' + content); 152 } 153 
154 function makeCssFile(name, content, originalArr, replaceArr) { 155     fs.renameSync(path.join(dest, name), path.join(dest, name + '.xss')); 156 
157     content = replace(content, originalArr, replaceArr); 158  makeFile(path.join(dest, name), content); 159 }

 

配置文件(config.json)部分属性:
 
findProp - 从哪些属性里寻找要下载的文件,如<img src=""/>
ignoreProp - 哪些属性就放弃下载,如<img src=\" data:image/png;base64,iVBORw0KGgoAAAANSU......
ignoreTag - 哪些标记放弃下载,如<script>
ignoreTagUnless - 哪些标记放弃下载,除非有某些特定的值,如<link>若是引用的样式文件就下载,不然放弃
killScript - 若是设为true,页面上全部的<script>包括大段的写在页面上的javascript代码清除掉。
 
下面看下效果:
注意那个背景,那是一个样式文件所引用的图片,如今也被下载到本地,而且改写了页面的引用地址。
 
但愿这个工具能对你们有点儿小用!
 
19.11.1
 
代码压缩成了一个zip文件,网站自带的编辑器没找到显示下载连接的地方,只能手动编辑html了。
相关文章
相关标签/搜索