Nodejs 中文分詞常用模塊用法分析
???
???
???????????????(生活只有在平淡無(wú)味的人看來(lái)才是空虛而平淡無(wú)味的。 —— 車爾尼雪夫斯基)
???
???
????????????????
中文分詞器
引用百度的說(shuō)明 ~~
中文分詞就是將連續(xù)的字序列按照一定的規(guī)范重新組合成詞序列的過程。我們知道,在英文的行文中,單詞之間是以空格作為自然分界符的,而中文只是字、句和段能通過明顯的分界符來(lái)簡(jiǎn)單劃界,唯獨(dú)詞沒有一個(gè)形式上的分界符,雖然英文也同樣存在短語(yǔ)的劃分問題,不過在詞這一層上,中文比之英文要復(fù)雜得多、困難得多
- 與英文為代表的拉丁語(yǔ)系語(yǔ)言相比,英文以空格作為天然的分隔符,而中文由于繼承自古代漢語(yǔ)的傳統(tǒng),詞語(yǔ)之間沒有分隔?!」糯鷿h語(yǔ)中除了連綿詞和人名地名等,詞通常就是單個(gè)漢字,所以當(dāng)時(shí)沒有分詞書寫的必要。而現(xiàn)代漢語(yǔ)中雙字或多字詞居多,一個(gè)字不再等同于一個(gè)詞。
- 在中文里,“詞”和“詞組”邊界模糊
現(xiàn)代漢語(yǔ)的基本表達(dá)單元雖然為“詞”,且以雙字或者多字詞居多,但由于人們認(rèn)識(shí)水平的不同,對(duì)詞和短語(yǔ)的邊界很難去區(qū)分。
例如:“對(duì)隨地吐痰者給予處罰”,“隨地吐痰者”本身是一個(gè)詞還是一個(gè)短語(yǔ),不同的人會(huì)有不同的標(biāo)準(zhǔn),同樣的“海上”“酒廠”等等,即使是同一個(gè)人也可能做出不同判斷,如果漢語(yǔ)真的要分詞書寫,必然會(huì)出現(xiàn)混亂,難度很大。
中文分詞的方法其實(shí)不局限于中文應(yīng)用,也被應(yīng)用到英文處理,如手寫識(shí)別,單詞之間的空格就不很清楚,中文分詞方法可以幫助判別英文單詞的邊界
NodeJS中的中文分詞器
mmseg-node
https://github.com/zzdhidden/mmseg-node
一個(gè)基于 libmmseg 的 NodeJS 驅(qū)動(dòng)
安裝流程
sudo apt-get install make gcc g++ automake libtool wget http://www.coreseek.cn/uploads/csft/3.2/mmseg-3.2.14.tar.gz sudo tar zxvf mmseg-3.2.14.tar.gz cd mmseg-3.2.14 ./bootstrap ./configure make && make install npm install mmseg
示例代碼
const mmseg = require("mmseg"); const q = mmseg.open('/usr/local/etc/'); console.log(q.segmentSync("我是中文分詞"));
從該包的下載量和issue的解決程度來(lái)看,不推薦使用,很容易碰到問題,而沒有得到有效的解決
nseg
https://github.com/mountain/nseg
Chih-Hao Tsai發(fā)明的MMSG,一種非常流行的中文分詞算法。許多實(shí)現(xiàn)可在不同的平臺(tái)上使用,包括Python,Java等
安裝流程
npm install nseg
示例代碼
const dict = require('../data/dict'), freq = require('../data/freq'), date = require('../lex/datetime'), sina = require('../lex/sina'); const opts = { dict: dict, freq: freq, lexers: [date, sina], }; const nseg = require('nseg').normal(opts); nseg('研究生源計(jì)劃', (result) => { console.log(result); });
同mmseg一樣,雖然在其他環(huán)境(比如Java,Python)有著較為成熟的應(yīng)用和場(chǎng)景,但包的維護(hù)者重心可能不在nodejs上,所以還是不太建議使用…
node-segment
https://github.com/leizongmin/node-segment
以盤古分詞組件中的詞庫(kù)為基礎(chǔ), 算法設(shè)計(jì)也部分參考了盤古分詞組件中的算法
具有以下特點(diǎn)
- 純JavaScript編寫,可以在任何支持ECMAScript5的引擎上執(zhí)行(需要稍微修改部分代碼)
- 基于詞性進(jìn)行聯(lián)想識(shí)別
- 可使用JavaScript編寫自定義的分詞模塊
安裝流程
npm install segment --save
示例代碼
const Segment = require('segment'); const segment = new Segment(); segment.useDefault(); // 載入默認(rèn)詞典 segment.loadDict('test.text'); // 載入字典,詳見dicts目錄,或者是自定義字典文件的絕對(duì)路徑 const text = "歡迎來(lái)到CSDN開發(fā)者博客論壇~~"; console.log(segment.doSegment(text, { stripPunctuation: true //去除標(biāo)點(diǎn)符號(hào) }));
定制化示例文件 .text
0x00100000代表16進(jìn)制的詞性,但在返回時(shí)轉(zhuǎn)換為了十進(jìn)制來(lái)標(biāo)識(shí)
- 名詞 十進(jìn)制:1048576 十六進(jìn)制:0x00100000
- 動(dòng)詞 十進(jìn)制:4096 十六進(jìn)制:0x00001000
- 機(jī)構(gòu)團(tuán)體 十進(jìn)制:20 十六進(jìn)制:0x00000020
- 時(shí)間 十進(jìn)制:16384 十六進(jìn)制:0x00004000
- 人名 十進(jìn)制:128 十六進(jìn)制:0x00000080
- 標(biāo)點(diǎn)符號(hào) 十進(jìn)制:2048 十六進(jìn)制:0x00000800
- …
101代表了這個(gè)詞語(yǔ)的權(quán)重,在后續(xù)匹配詞語(yǔ)時(shí),若匹配到近似詞,則以權(quán)重優(yōu)先匹配
架構(gòu)師|0x00100000|101 程序員|0x00100000|101 運(yùn)維工程師|0x00100000|101
該包使用純JavaScript編寫,github上的star量有1k,但通過查看issue,該包的維護(hù)著似乎已經(jīng)不太關(guān)心這個(gè)包了。但如果對(duì)性能要求沒那么高,且多關(guān)注內(nèi)存,使用得當(dāng)?shù)脑?,還是可以考慮的
nodejieba
https://github.com/yanyiwu/nodejieba
NodeJieba是"結(jié)巴(jieba)"中文分詞的 Node.js 版本實(shí)現(xiàn), 由CppJieba提供底層分詞算法實(shí)現(xiàn), 是兼具高性能和易用性兩者的 Node.js 中文分詞組件
具有以下特點(diǎn)
- 詞典載入方式靈活,無(wú)需配置詞典路徑也可使用,需要定制自己的詞典路徑時(shí)也可靈活定制。
- 底層算法實(shí)現(xiàn)是C++,性能高效。
- 支持多種分詞算法,各種分詞算法見CppJieba的README.md介紹。
- 支持動(dòng)態(tài)補(bǔ)充詞庫(kù)。
安裝流程
npm install nodejieba
但在安裝該包的時(shí)候可能會(huì)遇到npm下載權(quán)限的問題,因?yàn)榈讓邮褂昧薈ppJieba,當(dāng)使用sudo npm安裝時(shí)會(huì)提示權(quán)限不足
github issue問題
npm 官方解釋
解決辦法
- 單獨(dú)下載該包文件
sudo npm install nodejieba --unsafe-perm- 將該包緩存至全局
sudo npm install nodejieba -g --unsafe-perm- 配置npm
sudo su -
npm config set unsafe-perm- 切換至root身份運(yùn)行
sudo su -
npm i
示例代碼
jieba詞性
- 獲取文本分詞標(biāo)注結(jié)果
- n 名詞
- v 動(dòng)詞
- nt 機(jī)構(gòu)名稱
- t 時(shí)間
- nr 人名
…
示例自定義文件 .utf-8
客戶模板 n 客戶狀態(tài) n 客戶手機(jī)號(hào) n 自定義 n 自定義字段 n 云通訊 n
const nodejieba = require("nodejieba"); jieba.load({ userDict: `self.utf-8`}); // 自定義詞典 const text = '紅掌撥清波'; const tags = ['n', 'v', 'nt', 'nr', 't']; const docuemnt = jieba.tag(text).filter((v) => tags.includes(v.tag)).map((v) => v.word); console.log(docuemnt);
目前NodeJS中性能最強(qiáng)的分詞器,且github上star量達(dá)到2k+,但開發(fā)者可能會(huì)遇到安裝的問題,因?yàn)榈讓右蕾嚵藀ython g++,如果版本過低或者過高,在安裝時(shí)會(huì)比較棘手。但如果能夠?qū)C(jī)器環(huán)境配置好的話,還是推薦使用該工具的,畢竟性能很強(qiáng)~~
性能對(duì)比
起初在使用nodejieba時(shí),遇到了很多安裝的問題,比如npm權(quán)限,g++版本不匹配等等,期間也嘗試了node-segment,但因?yàn)榉?wù)對(duì)性能的要求較高,所以還是選擇克服機(jī)器配置的問題,使用了nodejieba
node-sgement和nodejieba的性能對(duì)比以及代碼分析
示例代碼
'use strict'; const express = require('express'); const app = express(); const bodyParser = require('body-parser'); const Segment = require('segment'); const segment = new Segment(); const nodejieba = require('nodejieba'); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); segment.useDefault(); segment.loadDict('test.text'); app.post('/', function (req, res, next) { const text = req.body.text; const type = req.body.type; const num = req.body.num; let str = ''; let array = []; for (let index = 0; index < num; index++) { str += text; } const tags = ['n', 'v', 'nt', 'nr', 't']; if (type === 'nodejieba') { array = nodejieba.tag(str).filter((v) => tags.includes(v.tag)).map((v) => v.word) } else { array = segment.doSegment(str).map((v) => v.w); } console.log(array); res.send(array); }); app.listen(3000);
示例請(qǐng)求文件 .text
{ "text": "生活只有在平淡無(wú)味的人看來(lái)才是空虛而平淡無(wú)味的。", "type": "segment", "num": 1 }
- 使用apach bench進(jìn)行測(cè)試
測(cè)試方法:ab -n 1000 -p post.txt -c 200 -T application/json localhost:3000
- -n 請(qǐng)求總數(shù)
- -p post請(qǐng)求文件
- -c 并發(fā)量
- -T 請(qǐng)求格式
…
- 請(qǐng)求返回參數(shù)說(shuō)明
- Document Path: /phpinfo.php #測(cè)試的頁(yè)面
- Document Length: 50797 bytes #頁(yè)面大小
- Concurrency Level: 200 #測(cè)試的并發(fā)數(shù)
- Time taken for tests: 11.846 seconds #整個(gè)測(cè)試持續(xù)的時(shí)間
- Complete requests: 1000 #完成的請(qǐng)求數(shù)量
- Failed requests: 0 #失敗的請(qǐng)求數(shù)量
- Write errors: 0
- Total transferred: 204586997 bytes #整個(gè)過程中的網(wǎng)絡(luò)傳輸量
- HTML transferred: 203479961 bytes #整個(gè)過程中的HTML內(nèi)容傳輸量
- Requests per second: 337.67 [#/sec] (mean) #最重要的指標(biāo)之一,每秒處理請(qǐng)求數(shù)
- Time per request: 2961.449 [ms] (mean) #最重要的指標(biāo)之二,平均等待時(shí)長(zhǎng)
- Time per request: 2.961 [ms] (mean, across all concurrent requests) #每個(gè)連接請(qǐng)求實(shí)際運(yùn)行時(shí)間的平均值
- Transfer rate: 16866.07 [Kbytes/sec] received #平均每秒網(wǎng)絡(luò)上的流量,可以幫助排除是否存在網(wǎng)絡(luò)流量過大導(dǎo)致響應(yīng)時(shí)間延長(zhǎng)的問題
- …
- 測(cè)試版本
- node v12
- segment v0.1.3
- nodejieba v2.4.1
- 測(cè)試結(jié)果
分詞類型 | 字符長(zhǎng)度 | 請(qǐng)求總數(shù) | 并發(fā)條數(shù) | 平均每秒處理 | 平均等待時(shí)長(zhǎng)(ms) | 是否包含分隔符(標(biāo)點(diǎn)符號(hào)等) |
---|---|---|---|---|---|---|
segment | 60 | 1000 | 20 | 129.47 | 154.474 | 是 |
nodejieba | 60 | 1000 | 20 | 2662.83 | 7.511 | 是 |
segment | 60 | 1000 | 50 | 137.51 | 363.598 | 是 |
nodejieba | 60 | 1000 | 50 | 2660.41 | 17.222 | 是 |
segment | 60 | 1000 | 100 | 134.56 | 753.688 | 是 |
nodejieba | 60 | 1000 | 100 | 2774.89 | 36.038 | 是 |
segment | 60 | 1000 | 200 | 134.05 | 1491.937 | 是 |
nodejieba | 60 | 1000 | 200 | 2846.30 | 70.267 | 是 |
---- | ---- | — | – | – | – | – |
segment | 180 | 1000 | 200 | 67.40 | 2967.466 | 是 |
nodejieba | 180 | 1000 | 200 | 2390.53 | 83.664 | 是 |
segment | 360 | 1000 | 200 | 42.55 | 4700.340 | 是 |
nodejieba | 360 | 1000 | 200 | 1801.97 | 110.989 | 是 |
---- | ---- | — | – | – | – | – |
segment | 60 | 1000 | 200 | 88.26 | 2265.952 | 否 |
nodejieba | 60 | 1000 | 200 | 2860.95 | 69.907 | 否 |
segment | 60 | 200 | 50 | 101.18 | 494.188 | 否 |
nodejieba | 60 | 200 | 50 | 2688.39 | 18.598 | 否 |
segment | 120 | 200 | 50 | 1.73 | 28933.129 | 否 |
nodejieba | 120 | 200 | 50 | 1551.11 | 32.235 | 否 |
segment | 360 | 200 | 50 | error(棧溢出) | error(棧溢出) | 否 |
nodejieba | 360 | 200 | 50 | 1117.46 | 44.744 | 否 |
- 測(cè)試結(jié)果點(diǎn)評(píng)
- 從測(cè)試結(jié)果來(lái)看,nodejieba是node-sgement的20-30倍,這得益于nodejieba依賴的CppJieba,而node-sgement使用的純JS編寫,所以性能要遜色很多
- nodejieba和node-sgement在處理分詞的請(qǐng)求都比較穩(wěn)定,但面對(duì)長(zhǎng)文本,即便性能很強(qiáng)的nodejieba依然會(huì)有性能瓶頸問題
- 面對(duì)不包含標(biāo)點(diǎn)符號(hào)的文本,nodejieba和node-sgement性能都會(huì)有所影響
- node-sgement在對(duì)長(zhǎng)文本(360個(gè)無(wú)標(biāo)點(diǎn)符號(hào)的字符)進(jìn)行分詞時(shí)會(huì)導(dǎo)致棧溢出
- 測(cè)試過程說(shuō)明
查看node-sgement源碼發(fā)現(xiàn) 作者建議不要對(duì)較長(zhǎng)且無(wú)任何標(biāo)點(diǎn)符號(hào)的文本進(jìn)行分詞的是發(fā)生在字典分詞階段
因?yàn)榉衷~是通過空格換行,通配符,特殊字符進(jìn)行分離處理的(正是因?yàn)橛辛朔蛛x,所以每次分詞的文本不會(huì)很長(zhǎng))
而segment處理一串字符是通過循環(huán)每一個(gè)字符進(jìn)行遞增匹配的,從而導(dǎo)致匹配的的次數(shù)是成倍的增長(zhǎng)
…一開始想問原作者沒有特殊符號(hào)的長(zhǎng)文導(dǎo)致時(shí)間倍增的具體原因,但是他可能很久沒有維護(hù)這個(gè)包了,就沒有提供有用的信息,然后只能看源碼去了解了
- node-segment 分詞邏輯
- 例子 你好我是七陌,循環(huán)次數(shù)是6+5+4+3+2+1 = (1+n)*n/2
- 第一輪對(duì)比
你
你好
你好我
…- 第二輪對(duì)比
好
好我
好我是
好我是七
…
一共分為12個(gè)分詞模塊
- 6個(gè)分詞模塊
url識(shí)別
通配符識(shí)別
標(biāo)點(diǎn)符號(hào)
字母/數(shù)字識(shí)別
字典識(shí)別- 6個(gè)優(yōu)化模塊
中文姓
人名
中文人名
日期時(shí)間
詞典
郵箱地址
- 整體執(zhí)行邏輯
每次分詞會(huì)把紅框中的所有模塊文件執(zhí)行一遍
- 將(空格/換行)進(jìn)行分組,用于后續(xù)循環(huán)匹配字符和詞性,然后進(jìn)行六個(gè)階段的分詞計(jì)算
- url識(shí)別 地址匹配,https/http… 如果命中則進(jìn)行轉(zhuǎn)換,并返回?cái)?shù)據(jù)和詞性,每次從當(dāng)前下標(biāo)開始截取協(xié)議名稱字符數(shù),如果是http則是7個(gè)。將截取的字符進(jìn)行對(duì)比,直到循環(huán)非url字符時(shí)結(jié)束。此時(shí)將這段字符的前后分成三段返回
- 通配符識(shí)別 通過循環(huán)遍歷將命中的通配符返回,并在上層匹配通配符詞性
- 標(biāo)點(diǎn)符號(hào) 和通配符識(shí)別邏輯相同
- 字母/數(shù)字識(shí)別 將匹配的英文和數(shù)字字符使用ASCII轉(zhuǎn)換,如果命中ASCII區(qū)間,則將區(qū)間對(duì)應(yīng)的16進(jìn)制轉(zhuǎn)換為10進(jìn)制作為詞性并分離返回,分離后的結(jié)構(gòu)和請(qǐng)求協(xié)議的結(jié)構(gòu)相同
- 字典識(shí)別 將字符串的字符循環(huán)和每一個(gè)字典的內(nèi)的字符進(jìn)行包含性對(duì)比
第一次先從0下標(biāo)開始查找,如果未命中則字符串截取數(shù)++,如果都沒有命中就將對(duì)比下標(biāo)向前進(jìn)一位,其中重復(fù)命中的會(huì)經(jīng)過一層優(yōu)化(去重打分),留下詞頻率最高,未識(shí)別詞最少的等等- 中文人名識(shí)別
- 人名優(yōu)化
…
總結(jié)
當(dāng)然是推薦使用nodejieba了,畢竟是目前nodejs性能最強(qiáng)的分詞,但對(duì)機(jī)器環(huán)境依賴較為苛刻,所以建議使用docker將分詞服務(wù)單獨(dú)部署,進(jìn)行環(huán)境依賴的隔離。
但如果更改機(jī)器配置對(duì)其他服務(wù)影響較大,且服務(wù)對(duì)性能要求沒那么高的話,可以考慮使用node-segment,然后加一些避免棧溢出的代碼,效果還是不錯(cuò)的~~
PS:筆者比較喜歡實(shí)用nodejieba分詞工具,但是針對(duì)某些領(lǐng)域(如:區(qū)塊鏈相關(guān))的專業(yè)術(shù)語(yǔ)會(huì)出現(xiàn)分詞錯(cuò)誤的情況,這個(gè)時(shí)候就需要去尋找相關(guān)的詞庫(kù)并手動(dòng)加載進(jìn)來(lái)使用。
- 使用 Node.js 對(duì)文本內(nèi)容分詞和關(guān)鍵詞抽取
- 讓nodeJS支持ES6的詞法----babel的安裝和使用方法
- python中jieba庫(kù)(中文分詞庫(kù))使用安裝教程
- Python基于jieba分詞實(shí)現(xiàn)snownlp情感分析
- Python第三方庫(kù)jieba庫(kù)與中文分詞全面詳解
- python?中的jieba分詞庫(kù)
- Django實(shí)現(xiàn)whoosh搜索引擎使用jieba分詞
- Node.js中Express框架使用axios同步請(qǐng)求(async+await)實(shí)現(xiàn)方法
- Node.js使用express寫接口的具體代碼
相關(guān)文章
詳解如何在Node.js中執(zhí)行CPU密集型任務(wù)
Node.js通常被認(rèn)為不適合CPU密集型應(yīng)用程序,Node.js的工作原理使其在處理I/O密集型任務(wù)時(shí)大放異彩,雖然執(zhí)行CPU密集型任務(wù)肯定不是Node的主要使用場(chǎng)景,但是我們依舊有方法來(lái)改善這些問題,本文詳細(xì)給大家介紹了如何在Node.js中執(zhí)行CPU密集型任務(wù)2023-12-12nodejs腳本centos開機(jī)啟動(dòng)實(shí)操方法
在本篇文章里小編給大家整理的是關(guān)于nodejs腳本centos開機(jī)啟動(dòng)實(shí)操方法,有興趣的朋友們參考下。2020-03-03用nodejs實(shí)現(xiàn)json和jsonp服務(wù)的方法
本篇文章主要介紹了用nodejs實(shí)現(xiàn)json和jsonp服務(wù)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-08-08基于Node.js構(gòu)建一個(gè)靈活的CLI命令行工具
在軟件開發(fā)中,命令行界面(CLI)工具是必不可少的助手,本文主要介紹了如何使用Node.js構(gòu)建一個(gè)靈活的CLI工具,涵蓋從基礎(chǔ)命令處理到復(fù)雜的交互式問答和遠(yuǎn)程模板下載,需要的可以參考下2024-03-03express.js如何做mysql注入與node-mysql中防止SQL注入方法解析
這篇文章主要介紹了express.js如何做mysql注入與node-mysql中防止SQL注入方法,結(jié)合實(shí)例形式分析了express框架使用mysql數(shù)據(jù)庫(kù)過程中SQL注入的原理與防范技巧,需要的朋友可以參考下2023-05-05nodejs連接mongodb數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查
本篇文章主要結(jié)合了nodejs操作mongodb數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查,包括對(duì)數(shù)據(jù)庫(kù)的增加,刪除,查找和更新,有興趣的可以了解一下。2016-12-12