关闭

Nodejs-cli 填坑记

标签: nodejs命令行工具翻译工具
7501人阅读 评论(3) 收藏 举报
分类:

真的是玩玩没想到,一个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工具就能制作完毕了。真的是无波折,不开心呐。

总结

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

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

1
0
查看评论

八大排序算法 之 快速排序(填坑法)

排序思想: 1,将第一个数字作为基准数字,将数组分为左右两部分,左边是比它小的数字,右边是比它大的数字; 2,将左部分按照上面的思想再次进行划分成两部分,依次类推; 3,将右部分按照上面的思想再次进行划分成两部分,依次类推; 排序趟数: 不确定 排序原理: 填坑法: 1,将第一个数字...
  • yxb_yingu
  • yxb_yingu
  • 2016-05-07 11:36
  • 1036

填坑法---快速搞定快速排序算法

填坑法快速搞定快速排序
  • a4118000113209
  • a4118000113209
  • 2016-07-16 10:29
  • 699

Vue.js填坑记

前言 上一篇文章主要介绍了我们团队的「Vue.js项目模板」的搭建过程,这只是第一步。作为新手,在实际开发过程中,还会遇到各种各样奇怪的问题。本文主要介绍问题的原因以及解决方式。 http://www.heeroluo.net/article/detail/138/vuejs-probl...
  • sinat_17775997
  • sinat_17775997
  • 2017-09-20 18:37
  • 315

Scrapy填坑记

写这个博客来记录使用Scrapy遇到的各种问题及解决方法 问题一:安装anaconda科学计算包之后使用conda list命令出现“UnicodeDecodeError: 'ascii' codec can't decode byte 0xb9 ”错误 解决办法...
  • qq_31556581
  • qq_31556581
  • 2017-09-02 14:19
  • 73

RN填坑记

因为控件不符合需求,修改 1、react-native-scrollable-tab-view,这个设置的滚动按钮,上下可以滑动, 在ScrollableTabBar.js 170行左右return <View style={[styles.container, {backgr...
  • ralbatr
  • ralbatr
  • 2016-11-04 13:35
  • 391

微信小程序填坑篇(一)

在微信小程序浅析中讲到微信小程序原理以及怎么新建一个hello world 工程,还挖了坑。现在把坑填上,捋一捋小程序的代码结构。
  • pjk971035770
  • pjk971035770
  • 2017-01-15 02:33
  • 212

牛人填坑分享

http://beartung.github.io/rockwithandroid/image.html Android Tips – 填坑手册  开发进阶  AndroidChina  1年前 (2015-09-24)  216...
  • tianhe718
  • tianhe718
  • 2016-11-07 15:00
  • 349

Tensorflow、深度学习填坑记

问题1 背景:VGG16去做一个人脸j检测的算法,使用RCNN,在fine-tuning的时候其实就是一个二分类问题,区分出来background和face 问题描述:在fine-tuning的时候总是将所有样本归到负样本,即就是background。 解决方案:增大学习率,一开始我还以为是我se...
  • liangdong2014
  • liangdong2014
  • 2017-08-17 10:18
  • 42

Python填坑记——作用域

先来看看两段代码:def fn(): if True: week = {'monday' : 1} week.update({'tuesday' : 2}) for (k , v) in week.items(): ...
  • ace_fei
  • ace_fei
  • 2016-01-19 22:54
  • 858

PendingIntent使用填坑记

PendingIntent使用填坑记PendingIntent作用其实和Intent作用差不多,可以理解为是一种特殊的Intent。和Intent区别在于执行的时机不一样。Intent是立刻执行,PendingIntent是等待条件满足在执行。常用于通知、短信、闹钟等应用情景。PendingInte...
  • hzqtby
  • hzqtby
  • 2017-10-19 00:04
  • 46
    个人资料
    • 访问:3235733次
    • 积分:34857
    • 等级:
    • 排名:第149名
    • 原创:357篇
    • 转载:35篇
    • 译文:9篇
    • 评论:733条
    友情链接
    我的偶像
    个人主页
      GitHub
    放松一下
    博客专栏
    最新评论
    版权信息
    去除本页广告
    图片炸弹装填中...

        
    [img=赞一个]http://bpic.588ku.com/element_origin_min_pic/16/12/12/0d96da96cf36505736c09d63832eaac8.jpg[/img]