Nodejs-cli 填坑记

原创 2017年06月12日 15:26:31

真的是玩玩没想到,一个cli竟然坑了我这么久,想当年写Python命令行工具的时候,哪有这么麻烦?随随便便写几下,添加个批处理命令脚本就搞定了。怎么Nodejs写一个就这么不顺利呢?

吐槽归吐槽,当我成功的写出来一个cli版本的工具之后,我才发现,是我错了。nodejs-cli其实真的是很方便,也很简单。

命令行翻译小工具Nodejs实现

秉承分享知识的原则,在此记录一下。

写在前面

这篇文章严格来说不能算是一片技术性的文章,没什么难点。有的只是一些好玩的小工具。对于Nodejs新人来说,鉴于没什么难度,倒是可以适当的练练手。因此,如果你是Nodejs高手的话,还是不要在此浪费时间了。

段子手

言简意赅点,就是一个爬取糗事百科的段子的小助手,一个简单的不能再简单的爬虫。姑且称之为爬虫吧。

外部模块

这里需要用到一点点的第三方模块。虽然实现相同的功能,标准模块也可以满足,但是有轮子的话,何必自己再去造一个呢(除非你能做的更好)。

  • superagent: 类似于Python里面的requests,挺好用的。
  • cheerio: 类似于Python里面的BeautifulSoup,但是其使用的是JQuery的选择器语法,所以对前端比较熟悉的话,用起来会非常的顺手。
  • cli-color: 如果你想在命令行里面打印出彩色的字符,那么用它就对了。

events事件发射接收

在Nodejs中events模块可谓是核心了。异步编程要是没有它,那就算是完了。这里为了使用而使用,我也简单的用了一下。

具体的思路是:

  • 每一张网页解析完毕后,发射一个pageover事件,来更新解析列表。
  • 全部内容解析完毕后发射done事件,通知客户端完成代码运行。

完整代码

/**
 * 使用几个比较不错的第三方模块实现糗事百科网站的小爬虫。
 */
const superagent = require('superagent');
const cheerio = require('cheerio');
const color = require('cli-color');
const events = require('events');

/**
 * 定义一个网页总数变量。
 */
const MAX_PAGE_SIZE = 3;

// var website = 'https://www.qiushibaike.com/8hr/page/2/';
// var website = "http://blog.csdn.net/marksinoberg";
// var website = 'https://www.qiushibaike.com/article/119148411';
var total_results = [];
var emitter = new events.EventEmitter();

function crawl_by_page(page, website) {
    var results = []
    superagent.get(website).then((response) => {
        // 获取到网页内容,交给cheerion进行解析即可。
        var $ = cheerio.load(response.text);
        // fs.writeFileSync('text.txt', response.text);

        $("div .untagged").each(function (index, element) {
            console.log("正在处理第:" + (page)+"页第"+(index+1)+" 条数据!");
            var authorage = $(this).find('div[class="author clearfix"]').text();
            var author = '', age = '';
            author = authorage.trim().split("\n")[0];
            age = authorage.match(/\d+/g);
            // console.log("作者:" + author + "\n年龄:" + age + "\n");
            // console.log('---------------------\n笑话内容:\n');
            var temp = $(this).find('div[class="content"]').text().trim();
            // console.log(temp);
            var obj = {
                author: author,
                age: age,
                content: temp
            }

            read_duanzi(obj);

            if (obj)
                results.push(obj);
        });
    }).then(function () {
        console.log('done.');
        // 设置一个最终响应事件
        var counts = (parseInt(page) * 20);
        emitter.emit('pageover', results);
        if (page == MAX_PAGE_SIZE) {
            emitter.emit('done', counts);
        }
    });

}


function read_duanzi(item){
    console.log(color.red('作者:'+item.author));
    console.log(color.green('年龄:' + item.age));
    console.log(color.blue('段子内容:\n')+item.content);
    console.log(color.green.bold("----------------------------------------------\
    ------------------------------------------------------------------------------------------"));
}


function main() {
    for (var page = 1; page <= MAX_PAGE_SIZE; page++) {
        var website = 'https://www.qiushibaike.com/8hr/page/' + page + '/';
        crawl_by_page(page, website);
    }
}




/**
 * 入口函数。
 */
main();
emitter.on('pageover', function (results) {
    total_results.concat(results);
    console.log(color.yellow("page downloading over."));

});
emitter.on('done', (counts) => {
    console.log(color.green("共下载了:" + counts + "个段子!"));
});

实现效果

下面上张图,来演示一下运行效果。
糗事百科段子爬虫运行示例


翻译官

之前用Python写过一个类似的工具,可以方便的读取系统剪切板的待翻译文本,然后以模态弹出框的形式通知用户翻译结果。自认为用起来还是不错的。现在学了点Nodejs,就有点手痒了,于是也来用Nodejs实现一个类似的功能。

当然不能完全的模仿,要有点新意。比如加一个这样的功能。

自判断语言类型(主要是英语和汉语),并进行翻译处理。

外部模块

同样的,因为有网络请求,所以离不开我最喜欢的SuperAgent了。然后为了进一步提升用户体验,我又添加了一个cli-color模块。

这样就可以优雅的在命令行里面显示绚丽的文本了。

部分代码释义

首先为了完成汉语的识别,用到了下面的这个函数。

/**
 * 判断给定的文本是否为中文。
 * @param {*给定的文本内容串} text 
 */
function is_Chinese(text) {
    // [\u4e00-\u9fa5] 是汉语所在的Unicode字符区间。
    return !!text.match(/[\u4e00-\u9fa5]+/gi);
}

代码比较简单,而且一看就差不多能明白这段代码的功能。我们都知道汉语在Unicode字符中的区间为[\u4e00-\u9fa5],所以匹配到这些字符的话,就默认为汉语了。

这里面的最后一句话是双非表达式。作用就是返回一个boolean类型的结果。这个使用技巧是我在浏览别人GitHub代码的时候看到的,当时就觉得这样写很优雅,于是模仿了一下。

完整代码

#!/usr/bin/env node
/**
 * 利用百度翻译接口实现一个小小的翻译软件。
 */
const superagent = require('superagent');
const color = require('cli-color');
const commander = require('commander');


/**
 * 将中文文本编码为安全的URI字符串。
 * @param {*中文文本词} text 
 */
function Chinese_encode(text) {
    return encodeURIComponent(text);
}


/**
 * 判断给定的文本是否为中文。
 * @param {*给定的文本内容串} text 
 */
function is_Chinese(text) {
    // [\u4e00-\u9fa5] 是汉语所在的Unicode字符区间。
    return !!text.match(/[\u4e00-\u9fa5]+/gi);
}

/**
 * 处理可能会出现异常信息的内容。
 * @param {*JSON格式的结果串} json 
 */
function handle_chinese_exception(json) {
    var dict;
    try {
        dict = {
            english_means: json['dict_result']['zdict']['simple']['means'][0]['exp'][0]['des'][0]['main'],
            word_means: json['dict_result']['simple_means']['word_means'],
            chinese_means: json['dict_result']['simple_means']['symbols'][0]['parts'][0]['means'][0]['means']
        }
        return dict;
    } catch (err) {
        return dict = {
            english_means: "未找到",
            word_means: "未找到",
            chinese_means: "未找到"
        };
    }
}

/**
 * 处理中文翻译部分的内容。
 * @param {*JSON格式的结果集串} json 
 */
function handle_Chinese_result(json) {
    // 获取字面意思:literal
    var literal = json['trans_result']['data'][0]['result'][0][1];
    // console.log(literal);
    // 处理字典方面含义
    var dict = handle_chinese_exception(json);
    // console.log(dict);
    return result = {
        literal: literal,
        dict: dict
    };

}

/**
 * 处理英文翻译相关可能出现异常的操作。
 */
function handle_english_exception(json) {
    var dict;
    // console.log(json['dict_result']['edict']['item'][0]['tr_group'][0]['tr']);
    try {
        dict = {
            english_means: json['dict_result']['edict']['item'][0]['tr_group'][0]['tr'],
            word_means: json['dict_result']['simple_means']['word_means'],
            tags: json['dict_result']['simple_means']['tags']['core'],
            chinese_means: json['dict_result']['simple_means']['symbols'][0]['parts'][0]['means']
        }
        return dict;
    } catch (error) {
        return dict = {
            english_means: 'not found.',
            word_means: 'not found.',
            tags: 'not found.',
            chinese_means: 'not found.'
        };
    }
}

/**
 * 处理英文翻译相关可能出现操作异常的情况。
 * @param {*JSON格式的结果串} json 
 */
function handle_english_result(json) {
    // 处理字面意义literal
    var literal = json['trans_result']['data'][0]['result'][0][1];
    // console.log(literal);
    // 处理字典相关含义
    var dict = handle_english_exception(json);
    // console.log("****************************\n", dict);
    return result = {
        literal: literal,
        dict: dict
    }
}


/**
 * 翻译给定的文本。
 * @param {*待翻译的文本内容串} text 
 */
function trans(text) {
    var flag = is_Chinese(text);
    var url_interface;
    var result;
    // 处理中文待翻译串
    if (flag) {
        var encoded_text = Chinese_encode(text);
        url_interface = "http://fanyi.baidu.com/v2transapi?from=zh&to=en&query=" + encoded_text;
        // 开始进行网络请求
        superagent.get(url_interface).then((response) => {
            // console.log(response.text);
            var json = JSON.parse(response.text);
            var result = handle_Chinese_result(json);
            // console.log('-------------------\n');
            // console.log(result)
            // 调用终端打印函数,打印相关内容
            print_in_terminal(result, false);

        });
    } else {
        // url_interface = "http://fanyi.baidu.com/v2transapi?query=" + text;
        url_interface = "http://fanyi.baidu.com/v2transapi?from=zh&to=en&query=" + text;
        // 开始处理英文的翻译请求
        superagent.get(url_interface).then((response) => {
            var json = JSON.parse(response.text);
            var result = handle_english_result(json);
            // console.log('============================\n');
            // console.log(result);
            // 调用终端打印函数,打印相关内容
            print_in_terminal(result, true);
        });
    }

}

/**
 * 根据中英文的不同,在命令行打印相关的内容。
 * @param {*待打印内容对象} data 
 * @param {*是否为英文文本} isEnglish 
 */
function print_in_terminal(data, isEnglish) {
    // 英文模式
    if (isEnglish) {
        console.log("字面意:" + color.red.bold(data.literal) + '\n');
        console.log("英文解释:" + color.green.bold(data.dict.english_means) + '\n');
        console.log("中文解释:" + color.green.bold(data.dict.chinese_means) + '\n');
        console.log("考试标签:" + color.blue(data.dict.tags) + '\n');
    } else { // 中文模式
        console.log("字面意:" + color.red.bold(data.literal) + '\n');
        console.log("英文解释:" + color.green.bold(data.dict.english_means) + '\n');
        console.log("词语解释:" + color.blue.bold(data.dict.word_means) + '\n');
        console.log("中文解释:" + color.yellow(data.dict.chinese_means) + '\n');
    }

}

/**
 * 创建命令行参数解析工具,并予以运用。
 */
function create_commander() {
    commander.command('help').description('提示信息: 如何使用这个小工具.').action(() => { commander.outputHelp(); });
    commander.command('trans [text]').description('翻译给定的文本,中英文都可.').action((text) => {
        trans(text);
    });

    // 开始解析命令行参数
    commander.parse(process.argv);
}


/////////////////////////////////////////////测试部分
function main() {
    // var text = "你好";
    // var flag = is_Chinese(text);
    // console.log("是否为中文:"+flag);
    // text = "hello";
    // flag = is_Chinese(text);
    // console.log("是否为中文:"+flag);

    // trans("软件");
    create_commander();
}
main();

为了尽可能清楚的表达我的逻辑,代码上添加了很多注释。应该是很容易就能读懂了吧。

效果演示

下面来看下运行的效果。
翻译官代码运行示例


cli工具

到了最重要的一部分内容了。被这个cli坑了一个多小时了。不过还好,最终还是解决了它。

标配需求

安装完最新版本的Node之后,默认会自带npm这个包管理工具。为了构建我的cli工具,我需要一个package.json的文件。

比如我的文件目录结构如下:

E:\Code\Nodejs\learn\my-work\translator>tree /f .
卷 文档 的文件夹 PATH 列表
卷序列号为 0000-4823
E:\CODE\NODEJS\LEARN\MY-WORK\TRANSLATOR
│  index.js
│  package.json
└─ test.js

然后在当前目录的命令行中执行下面的命令:

npm init

然后根据命令行中的提示信息进行填写即可。填写完成就会生成一个package.json的文件了。

修改配置

完成上面一步后就会得到类似于下面内容的文件了。

{
  "name": "translator",
  "version": "1.0.0",
  "description": "命令行版本的翻译工具,适用于中英文,无需手动选择语言模式。",
  "preferGlobal": true,
  "bin":{
    "translator": "index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "翻译小工具"
  ],
  "author": "郭璞",
  "license": "MIT"
}

需要注意的就是bin属性,里面填写我们即将被打包的js文件的相对路径即可。比如我的index.js在package.json的同级目录下,所以我这么写了。

如果你的index.js文件在package.json的同级目录下的bin文件夹内,那你就得这么写:

"bin":{
    "translator": "./bin/index.js"
  },

最后记得保存。


再就是执行下面的链接命令了(记得命令行路径在package.json的同级目录下)。

npm link

然后就可以根据bin中声明的属性来调用命令行脚本了。

易错点剖析

下面谈谈我掉进去的坑。按照官网给的使用技巧,本人也测试了一下,发现并不好使。然后我发现通过npm link命令生成的cmd文件有这么个内容:

"%~dp0\node_modules\translator\index.js"   %*

也就是说这个文件本身就是错误的。node是找不到它的。而且在命令行中运行translator命令的时候根本不管用,它总会是用电脑上默认的文本编辑器打开相应的js脚本文件。

一开始我以为是代码有问题,然后修修改改,发现没什么问题啊。前前后后尝试了好几遍,都不得终。场面一度陷入了尴尬的地步。

然后在浏览一些帖子的时候,灵光一闪,为什么找不到???
究其原因就是环境变量呗。于是我就顺藤摸瓜,发现别人家的js脚本的开头都有这么一行代码

#!/usr/bin/env node

一开始没在意,以为是Linux特有的风格,然后我用的是VSCode,也能很好的运行就没在意,然后这次在我的JS文件中,我抱着试一试的态度加了这么一行语句,结果竟然成功了。

这更是验证了我关于环境变量的猜想,原来是这么回事哦。。。


至此,一个简单的node-cli工具就能制作完毕了。真的是无波折,不开心呐。

总结

最后, 我想对自己说的就是:

遵守编码规范,不要想当然。

版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。转载时请标注http://blog.csdn.net/marksinoberg.

相关文章推荐

Node.js 之 cli-color

来自 https://www.npmjs.com/package/cli-color简介 Colors, formatting and other tools for the console 改变...

Nginx 从零搭建

前言 准备篇 搭建篇 安装虚拟机 安装Nginx pcre安装 zlib安装 openssl安装 安装Nginx 安装Apache 配置 验证篇 Nginx ServerA ServerB Nginx...

给自己看的Redis

前言 安装 服务器 本地 配置相关 远程连接 修改密码 登录 远程连接 无密码 无密码 密码不正确 密码类型不对 密码登录 总结前言昨天又收到一条来自阿里云安全方面的短信,说是服务器存在对外DDoS攻...

安装grunt-cli之后的nodejs

  • 2015年07月20日 22:20
  • 8.21MB
  • 下载

记一次错误调试,关于php命令行[php cli]

这几天遇到一个需求,这个需求写在了php文件里,这个文件最好在命令行里执行。但是,出问题了。当我在浏览器里执行php文件的时候,ok,一切正常。但是,当我在命令行里执行的时候,报错!错误信息为:PHP...

php cli模式的一些"坑"

看下cli模式好使不 命令行下运行 php -r "echo php_sapi_name();" 这条命令就是在cli模式运行php语句,php -r就是run一条php命令的意思,php_sapi_...

vue cli+axios踩坑记录+拦截器使用,代理跨域proxy(更新)

1、首先axios不支持vue.use()方式声明使用,看了所有近乎相同的axios文档都没有提到这一点 建议方式在main.js中如下声明使用 import axios from 'axios';...

Vue2.0史上最全入坑教程(上)—— 搭建Vue脚手架(vue-cli)

Vue2.0史上最全入坑教程(上)—— 搭建Vue脚手架(vue-cli) Vue作为前端三大框架之一截至到目前在github上以收获44,873颗星,足以说明其以悄然成为主流。16...

angular-cli的安装及各种坑

1、node的版本太低就会报各种错 我的是node:6.10.0版本 2、用淘宝镜像安装cnpm npm install -g cnpm --registry=https://registry.np...

vue.js通过cli脚手架安装stylus遇到的坑

在上一篇文章中stylus入门中已经介绍了如何安装stylus,但是在vue.js开发中遇到了以下一个问题: 1.通过指令全局安装stylus$ npm install stylus -g虽然全局安...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Nodejs-cli 填坑记
举报原因:
原因补充:

(最多只允许输入30个字)