作者:吴峻申
原文:http://www.wujunshen.cn/posts/279953901.html
写在前面
项目配置
- JAVA GraalVM 17
- ElasticSearch 8.3.3
- Junit5 5.9.0
- lombok 1.8.24
- logback 1.2.11
- hanlp汉语自然语言处理工具包 1.8.3
如何使用
获取hanlp语料
直接下载 data.zip:http://nlp.hankcs.com/download.php?file=data
后续会使用到
本地搭建nginx网站显示静态内容
快速安装nginx
以我的mac为例
代码语言:javascript复制brew install nginxCopy
然后根据安装成功的提示去找nginx.conf文件,我这里提示如下图
配置nginx
根据上图里提示去编辑nginx.conf文件
代码语言:javascript复制vi /opt/homebrew/etc/nginx/nginx.confCopy
编辑内容可以下面文件内容为准,当然读者也可以根据自己情况修改
代码语言:javascript复制worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8080;
server_name local.wujunshen.com;
location / {
root /usr/local/var/www;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include servers/*;
}Copy
其中特别注意的是下面这段
代码语言:javascript复制listen 8080;
server_name local.wujunshen.com;
location / {
root /usr/local/var/www;
index index.html index.htm;
}Copy
首先我自定义了一个local.wujunshen.com
本地域名,并且和127.0.0.1绑定在一起。然后我把前述的data.zip解压到/usr/local/var/www
目录,并让root指向它
提示
local.wujunshen.com
绑定127.0.0.1是这样的 我先vi /etc/hosts
,打开hosts文件 然后copy127.0.0.1 local.wujunshen.com
这一行到任意位置 最后source /etc/hosts
使其生效 这样就绑定成功了
编辑nginx.conf完成后,执行
代码语言:javascript复制brew services restart nginxCopy
重启nginx,让其生效
静态网站内容验证
打开浏览器输入 http://local.wujunshen.com:8080/
看见下列界面,证明nginx安装成功
然后接着输入 http://local.wujunshen.com:8080/data/dictionary/custom/CustomDictionary.txt
显示如下界面
这表明nginx网站已经能显示静态内容了
插件安装
打包代码
执行
代码语言:javascript复制mvn clean packageCopy
注意 代码中的hanlp-hot-update.cfg.xml文件内容 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>HanLP 扩展配置</comment> <!--用户可以在这里配置远程扩展字典 --> <entry key="remote_ext_dict"> http://local.wujunshen.com:8080/data/dictionary/custom/CustomDictionary.txt </entry> <!--用户可以在这里配置远程扩展停止词字典--> <entry key="remote_ext_stopwords"> http://local.wujunshen.com:8080/data/dictionary/stopwords.txt </entry> <!--用户可以在这里配置远程同义词字典--> <entry key="remote_ext_synonyms"> http://local.wujunshen.com:8080/data/dictionary/synonym/CoreSynonym.txt </entry> </properties>Copy 其中
remote_ext_dict
,remote_ext_stopwords
和remote_ext_synonyms
都是前述搭建的nginx网站静态内容
发布插件
将打包成zip格式的插件包(在/target/releases目录下)
解压到ElasticSearch下的plugins子目录下,这样就发布完成了
运行插件
重新启动ElasticSearch
在ElasticSearch目录下的bin子目录启动ElasticSearch
注意 不能用root账号启动 需要新建账号并赋权,然后启动ElasticSearch
查看插件执行结果
使用head插件,在“复合查询”里输入如下截图内容(事先创建了一个叫index-test
的索引) ,并按下方“提交请求”按钮执行,可见右侧执行结果
也可在命令窗口输入下列命令执行,结果和上图是一样的。
代码语言:javascript复制curl -H "Content-Type:application/json" -X POST -d '{
"analyzer": "hanlp_synonym",
"text": "英特纳雄耐尔"
}' http://localhost:9200/index-test/_analyze?pretty=trueCopy
见下图命令行执行结果
开发介绍
ES分词器简单介绍
ElasticSearch默认就有标准的英文分词器。
但是对于母语是非英语的人来说,光有英文分词器是远远不够的。
因此各国家的程序员都会开发对应自己母语的分词插件来增强ElasticSearch的分词功能
不管何种自然语言的分词器,无外乎由下列三部分组成
- 分词器(Analyzer)
- 分解器(Tokenizer)
- 词元过滤器(TokenFilter)
而底层依赖的都是分词算法。
本项目使用的分词算法是 HanLP
,作者何晗。
具体官网地址可见 HanLP(https://www.hanlp.com/),号称是最好的中文分词算法。
除此之外,分词器应该还具有一些附加功能,比如下列两个功能
- 支持用户自定义字典
- 支持字典的热更新功能
HanLP
简单介绍
HanLP
是一系列模型与算法组成的 NLP
(自然语言处理) 工具包,具备功能完善、性能高效、架构清晰、语料时新、可自定义特点,详情可参考 HanLP GitHub:https://github.com/hankcs/HanLP
选择它作为本项目底层分词算法理由如下
- Java 分词包中最流行的分词算法
- 提供多种分词器,既可基于字典也可基于分词模型
- 坚持使用明文字典,可借助社区力量对字典进行不断完善
- 开发文档和代码样例丰富
项目代码结构
见下图
assemblies
: 插件打包(plugin.xml
)配置文件com.wujunshen.core
: 分词插件核心类com.wujunshen.dictionary
: 同义词字典类com.wujunshen.enumation
: 涉及的枚举com.wujunshen.exception
: 自定义异常com.wujunshen.nature
: 自然分词属性com.wujunshen.plugin
: 分词插件定义com.wujunshen.update
: 热词更新处理类com.wujunshen.utils
: 涉及的工具类resources
: 插件属性文件所在目录。包括插件配置、HanLP
的热词更新配置、Java
安全策略、logback
日志配置等文件test
下的com.wujunshen.entity
和MyAnalyzerTest
: 使用JUnit5
编写的单元测试方法
单元测试类介绍
具体见MyAnalyzerTest.java
其中具体说明一下私有方法 analyze
private List<Token> analyze(SegmentationType segmentationType, String text) throws IOException {
Tokens result = new Tokens();
List<Token> resultList = new ArrayList<>();
Analyzer analyzer = new MyAnalyzer(segmentationType);
TokenStream tokenStream = analyzer.tokenStream("text", text);
tokenStream.reset();
while (tokenStream.incrementToken()) {
CharTermAttribute charTermAttribute = tokenStream.getAttribute(CharTermAttribute.class);
TypeAttribute typeAttribute = tokenStream.getAttribute(TypeAttribute.class);
OffsetAttribute offsetAttribute = tokenStream.getAttribute(OffsetAttribute.class);
PositionIncrementAttribute positionIncrementAttribute =
tokenStream.getAttribute(PositionIncrementAttribute.class);
Token token = new Token();
token.setToken(charTermAttribute.toString());
token.setStartOffset(offsetAttribute.startOffset());
token.setEndOffset(offsetAttribute.endOffset());
token.setType(typeAttribute.type());
token.setPosition(positionIncrementAttribute.getPositionIncrement());
resultList.add(token);
}
tokenStream.close();
result.setTokens(resultList);
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
log.info("{}n", objectMapper.writeValueAsString(result));
return resultList;
}Copy
Analyzer
类是一个抽象类,是所有分词器基类,通过 TokenStream
类将文本转换为词汇单元流。
TokenStream
使用流程
- 实例化
TokenStream
, 向AttributeSource
添加属性(词汇单元文本text
、位置增量position
、偏移量offset
、词汇类型type
等) - 调用
reset
方法, 将流(stream
)重置到原始(clean
)状态 - 循环调用
incrementToken
方法,处理Attribute
属性信息 - 调用
close
方法释放资源
注意 由上可知 我们需要重点关注
TokenStream
的实例化、reset
、incrementToken
和close
这几个方法实现
还需重点关注安全策略文件
plugin-security.policy
文件可见前述代码结构的图里,需要放置在 resources
目录下。
这样打包后才会在插件根目录下。
但是实际执行时,ElasticSearch
的日志会报 AccessControlException
错误,这个可能是远程加载自定义分词字典(见README.md
文件中所述的nginx
静态内容网站搭建内容) 时,需要网路连接权限。
因此我在 MyTokenizer.java
中,加入了下列代码,如果显示正常,则说明远程加载分词字典成功
static {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
Nature.create("auxiliary");
return null;
});
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
nlpSegment = HanLP.newSegment()
// 词性标注
.enablePartOfSpeechTagging(true)
// 计算偏移量
.enableOffset(true)
// 中文人名识别
.enableNameRecognize(true)
// 日本人名识别
.enableJapaneseNameRecognize(true)
// 数量词识别
.enableNumberQuantifierRecognize(true)
// 机构名识别
.enableOrganizationRecognize(true)
// 音译人名识别
.enableTranslatedNameRecognize(true);
indexSegment = HanLP.newSegment()
.enableIndexMode(true)
// 词性标注
.enablePartOfSpeechTagging(true)
// 计算偏移量
.enableOffset(true);
// 在此处显示调用一下分词,使得加载词典、缓存词典的操作可以正确执行
log.info(String.valueOf(nlpSegment.seg("HanLP中文分词工具包!")));
log.info(String.valueOf(indexSegment.seg("HanLP中文分词工具包!")));
return null;
});
}Copy
总结
本项目功能可总结为下列这些
- 内置3种分词模式,适合不同场景(索引分词、nlp分词、同义词索引分词)
- 支持外置字典(需要搭建
nginx
静态内容网站) - 支持分词器级别的自定义字典
- 支持远程字典热更新
参考资料
项目源码:https://gitee.com/darkranger/hanlp-plugin
特别感谢
本项目单元测试类 ,MyAnalyzerTest.java:
https://gitee.com/darkranger/hanlp-plugin/blob/master/src/test/java/com/wujunshen/core/MyAnalyzerTest.java,所使用的文本解析内容,来源于倪匡老先生的小说: 卫斯理系列中的《透明光》第一章
老先生的具体生平可见,百度百科:https://baike.baidu.com/item/倪匡/333092
向刚去世不久的倪匡老先生致以崇高的敬意~
------
我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!
推荐阅读
- try - catch 语句真的会影响性能吗?
- 如何用一个注解来轻松搞定接口的数据脱敏?
- 1 亿巨资开发的防疫 APP,两年多只找到 2 例确诊
··································
你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书创过业、国企4年互联网6年。从普通开发到架构师、再到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。
点击领取2022最新10000T学习资料