做给自己用的命令行工具

最近自己用 Node 写了很多的小工具,然而并不能全局使用。上网找了下答案,发现了 TJ 大神写的 Commander.js 可以很方便的制作命令行工具。于是就照着 API 把之前的 知乎专栏爬虫 给整成 CLI 工具。我把工具命名为 nodc,意思是node collection,然而这一路并不平静,所以写下此文。

commander: 命令行模块

这个模块是 TJ 大神封装好的模块,能快速开发命令行工具。官网有很多例子,我直接上我用到的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var program = require('commander');

program
.command('crawler [zhihuId]')
.alias('cr')
.description('🔄 知乎专栏爬虫 ⛎')
.option('-o ,--out <path>',"🔙 输出位置")
.action(function(zhihuId, options){
var zhihuId = zhihuId || "leanreact";
path = options.out || process.cwd();//当前执行路径
console.log('🐛 知乎专栏爬取 %s 到 %s 文件夹',zhihuId, path);
zhihu(zhihuId,path)
}).on('--help', function() {
console.log(' 举个例子:');
console.log();
console.log(' $ nodc crawler leanreact');
console.log(' $ nodc cr leanreact -o ~/');
console.log();
});
program
.parse(process.argv);

上面是 commander 模块创建类似 git 一样的子命令的代码

command:是创建子命令的方法,可以接收两个参数。接收一个参数时,可以使用 action 方法在后面发起动作。接收两个参数时,第二个参数是命令说明,后边就不能使用action了。[zhihuId]意思是可选参数,因为我后面在action方法里设置了默认知乎专栏 id

alias:子命令别名

description:命令摘要说明

option:子命令属性。我设置了输出路径属性,接收两个参数,第二个参数为命令说明。字符串中-o必须在--out前面,后面<path>是必填参数。如果option不跟在command后面,则作为主命令的属性。

action:动作,顾名思义,就是发起子命令时做什么动作。传一个匿名函数做参数,前面 command 后面括号的内容可以作为参数传入。而 option 的中括号的参数需要用options. 参数来传入

on:这里设置了 help 的说明

.parse(process.argv):没了这个代码好像不能用,这个放在所有 program 的最后,就像是 JavaScript 程序的return,放在这行代码后面的commander 模块代码都不会执行,就算只是简单打console.log也不会执行。不过我在前面调用了zhihu(zhihuId,path)zhihu()里面的代码引用并不受它影响。

关于这个模块就说这么多。更多用法可以去 github 看 README,有中文版的。

整合 GetZhiHuZhuanLan

之前写这个爬虫留下了很多问题没处理,这也是我参考 zhangolve 的项目 的后遗症。第一个问题是,把整个爬虫分成两部分处理的:爬取下载,线下处理。第二个问题是,代码转换都成单行。由于这段时间都没空,也就搁置了。前些天看到 Node 也能写命令行工具,就拿这个爬虫来试手。翻出来看,完全不能忍啊。于是找各种办法把问题处理了。

把两个分离模块合在同一个文件里

其实不写成同一个文件里也没关系的。不过为了开发方便,不用切换文件,我还是硬把两个模块的所有代码都放进去了。放进去,改了下参数名跑了一次,卧槽,线下处理的 for 循环居然跑在爬取下载模块的前面,怪不得之前我参考的项目是把两个文件分开来执行。

怎么办?怎么办?这问题很让人抓狂啊。

这时候一定要冷静,两个模块能分开运行,都能完成自己的任务,问题处在哪里?问题在于 JavaScript 的任务队列里:线下处理模块是被 for 包裹的同步运行代码,而爬取下载是一个异步的任务。如果把两个模块放一起,异步任务发起后,执行异步需要时间,而这时候,轮到 for 循环,它要处理下载好的 json 文件,可是异步下载还没完成呢,自然 for 里面的任务就异常了。

首次使用 eventproxy

相同了这个问题,赶紧去恶补 callback ,异步的知识,用了 async,感觉要搞很多代码,转向用朴灵大神的eventproxy。其实这个我完全是第一次玩呀,硬着头皮上吧。

看了下 README,幸好有中文版的,用了allafter两种方法。all是全部事件触发emit,就会执行执行。after是在n次执行完后,才触发,nafter的第二个参数。

按理说 for 循环用after比较好处理,然而我拿不到 for 的次数,因为被封装在另一个函数中。我试了好多次都不成功。换all吧,all面临一个问题是,怎么样监听什么售后下载完,才发送给all。由于我用的是request模块的pipe方法,后面不能监听呀。eventproxy还没知道怎么用呢,两个方法都不能立马行得通,很打击人的啊。

认准一条路,脚踏实地的走下去

after试过了很多次都行不通,all又因为request后面直接使用pipe不能监听。果断选择all,至少我知道只要能监听request什么时候执行完,就可以发起all了。而after我根本就没有思路,就算这条路是捷径,我也走不了,还不如脚踏实地的 focus 目标。

既然request直接使用pipe不能监听,那我不直接使用不就行了。上网找了个 方法,用fs.writeStreamon监听。把eventproxyemit放在oncallback 里面,用一个变量来累加计算,判断for写入次数来执行emit

运行

因为我本来就没有 npmjs 的账号,所以不能发布在 npmjs 上,那怎么样全局使用呢?方法还是有的:

1
2
# 项目的根目录下
$ sudo npm i -g

搞定。

还有很多事没做呢

这是只是我nodc的第一个小功能,我还想集成更多

nodc 功能列表

  • 知乎专栏爬虫
  • 结巴分词全文排序关键词
  • 天气预报
  • 中英翻译

而知乎专栏爬虫还有些问题需要解决:

  • 文件名上加入文章发布时间,方便排序 (20170717@learnreact.md)
  • 代码还很不美观,而且部分代码需要重写,虽然可以运行(用 request 代替 https)
  • 增加 进度条,让爬虫进度更直观。
  • 增加多 id 下载
  • 输出带 颜色 的信息

你们可能会说,别人其实已经造好了很多轮子,比如翻译就有 ici,为什么我还要自己弄?因为别人的东西,如果出了 bug 🐛,我自己修改会很费力,或者我只能坐以待毙。而我想通过一个个小项目开源给大家一起来完善,自己享用自己的劳动成果,也是一件乐事。


做给自己用的命令行工具
https://bubao.github.io/posts/8f7d7e58.html
作者
一念
发布于
2017年7月17日
许可协议