Skip to content

提取小说的主角名-Java

Python 中有个 jieba 组件可以进行分词,项目使用的 Java,找到一个 Java 版的 jieba,但是功能比较简单,只有分词,不支持人名识别。
另外还找到一个 HanLP 汉语言处理包,这个功能比较多,支持人名识别、关键词提取、自动摘要等功能,用法可以参考官方的 1.x 版文档

这里基于上述两个工具测试了一下提取小说中的主角名(人名出现次数最多的两个算主角)。

1. 分别添加 2 个依赖

xml
<!-- Jieba -->
<dependency>
    <groupId>com.huaban</groupId>
    <artifactId>jieba-analysis</artifactId>
    <version>1.0.2</version>
</dependency>

<!-- Hanlp -->
<dependency>
    <groupId>com.hankcs</groupId>
    <artifactId>hanlp</artifactId>
    <version>portable-1.8.3</version>
</dependency>

2. 测试代码

这里省略了小说章节的内容,另外 jieba 没有人名识别,参考这篇博客用正则表达式验证是否是人名。

java
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.seg.Segment;
import com.hankcs.hanlp.seg.common.Term;
import com.huaban.analysis.jieba.JiebaSegmenter;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class NameTest {

    private final String chapter = "{这里省略了章节内容}";

    private final Pattern namePattern = Pattern.compile(
            "(王 | 李 | 张 | 刘 | 陈 | 杨 | 黄 | 赵 | 吴 | 周 | 徐 | 孙 | 马 | 朱 | 胡 | 郭 | 何 | 高 | 林 | 罗 | 郑 | 梁 | 谢 | 宋 | 唐 | 许 | 韩 | 冯 | 邓 | 曹 | 彭 | 曾"
                    + "|肖 | 田 | 董 | 袁 | 潘 | 于 | 蒋 | 蔡 | 余 | 杜 | 叶 | 程 | 苏 | 魏 | 吕 | 丁 | 任 | 沈 | 姚 | 卢 | 姜 | 崔 | 钟 | 谭 | 陆 | 汪 | 范 | 金 | 石 | 廖 | 贾 | 夏 | 韦 | 傅"
                    + "|方 | 白 | 邹 | 孟 | 熊 | 秦 | 邱 | 江 | 尹 | 薛 | 闫 | 段 | 雷 | 侯 | 龙 | 史 | 黎 | 贺 | 顾 | 毛 | 郝 | 龚 | 邵 | 万 | 钱 | 武 | 戴 | 孔 | 汤 | 庞 | 樊 | 兰 | 殷"
                    + "|施 | 陶 | 洪 | 翟 | 安 | 颜 | 倪 | 严 | 牛 | 温 | 芦 | 季 | 俞 | 章 | 鲁 | 葛 | 伍 | 申 | 尤 | 毕 | 聂 | 柴 | 焦 | 向 | 柳 | 邢 | 岳 | 齐 | 沿 | 梅 | 莫 | 庄 | 辛 | 管"
                    + "|祝 | 左 | 涂 | 谷 | 祁 | 时 | 舒 | 耿 | 牟 | 卜 | 路 | 詹 | 关 | 苗 | 凌 | 费 | 纪 | 靳 | 盛 | 童 | 欧 | 甄 | 项 | 曲 | 成 | 游 | 阳 | 裴 | 席 | 卫 | 查 | 屈 | 鲍 | 位"
                    + "|覃 | 霍 | 翁 | 隋 | 植 | 甘 | 景 | 薄 | 单 | 包 | 司 | 柏 | 宁 | 柯 | 阮 | 桂 | 闵 | 欧阳 | 解 | 强 | 丛 | 华 | 车 | 冉 | 房 | 边 | 辜 | 吉 | 饶 | 刁 | 瞿 | 戚 | 丘"
                    + "|古 | 米 | 池 | 滕 | 晋 | 苑 | 邬 | 臧 | 畅 | 宫 | 来 | 嵺 | 苟 | 全 | 褚 | 廉 | 简 | 娄 | 盖 | 符 | 奚 | 木 | 穆 | 党 | 燕 | 郎 | 邸 | 冀 | 谈 | 姬 | 屠 | 连 | 郜 | 晏"
                    + "|栾 | 郁 | 商 | 蒙 | 计 | 喻 | 揭 | 窦 | 迟 | 宇 | 敖 | 糜 | 鄢 | 冷 | 卓 | 花 | 艾 | 蓝 | 都 | 巩 | 稽 | 井 | 练 | 仲 | 乐 | 虞 | 卞 | 封 | 竺 | 冼 | 原 | 官 | 衣 | 楚"
                    + "|佟 | 栗 | 匡 | 宗 | 应 | 台 | 巫 | 鞠 | 僧 | 桑 | 荆 | 谌 | 银 | 扬 | 明 | 沙 | 伏 | 岑 | 习 | 胥 | 保 | 和 | 蔺 | 水 | 云 | 昌 | 凤 | 酆 | 常 | 皮 | 康 | 元 | 平"
                    + "|萧 | 湛 | 禹 | 无 | 贝 | 茅 | 麻 | 危 | 骆 | 支 | 咎 | 经 | 裘 | 缪 | 干 | 宣 | 贲 | 杭 | 诸 | 钮 | 嵇 | 滑 | 荣 | 荀 | 羊 | 於 | 惠 | 家 | 芮 | 羿 | 储 | 汲 | 邴 | 松"
                    + "|富 | 乌 | 巴 | 弓 | 牧 | 隗 | 山 | 宓 | 蓬 | 郗 | 班 | 仰 | 秋 | 伊 | 仇 | 暴 | 钭 | 厉 | 戎 | 祖 | 束 | 幸 | 韶 | 蓟 | 印 | 宿 | 怀 | 蒲 | 鄂 | 索 | 咸 | 籍 | 赖 | 乔"
                    + "|阴 | 能 | 苍 | 双 | 闻 | 莘 | 贡 | 逢 | 扶 | 堵 | 宰 | 郦 | 雍 | 却 | 璩 | 濮 | 寿 | 通 | 扈 | 郏 | 浦 | 尚 | 农 | 别 | 阎 | 充 | 慕 | 茹 | 宦 | 鱼 | 容 | 易 | 慎 | 戈"
                    + "|庚 | 终 | 暨 | 居 | 衡 | 步 | 满 | 弘 | 国 | 文 | 寇 | 广 | 禄 | 阙 | 东 | 殴 | 殳 | 沃 | 利 | 蔚 | 越 | 夔 | 隆 | 师 | 厍 | 晃 | 勾 | 融 | 訾 | 阚 | 那 | 空 | 毋 | 乜"
                    + "|养 | 须 | 丰 | 巢 | 蒯 | 相 | 后 | 红 | 权逯 | 盖益 | 桓 | 公 | 万俟 | 司马 | 上官 | 夏侯 | 诸葛 | 闻人 | 东方 | 赫连 | 皇甫 | 尉迟 | 公羊 | 澹台"
                    + "|公冶 | 宗政 | 濮阳 | 淳于 | 单于 | 太叔 | 申屠 | 公孙 | 仲孙 | 轩辕 | 令狐 | 钟离 | 宇文 | 长孙 | 慕容 | 鲜于 | 闾丘 | 司徒 | 司空 | 亓官"
                    + "|司寇 | 仉 | 督 | 子车 | 颛孙 | 端木 | 巫马 | 公西 | 漆雕 | 乐正 | 壤驷 | 公良 | 拓跋 | 夹谷 | 宰父 | 谷粱 | 法 | 汝 | 钦 | 段干 | 百里 | 东郭"
                    + "|南门 | 呼延 | 归海 | 羊舌 | 微生 | 帅 | 缑 | 亢 | 况 | 郈 | 琴 | 梁丘 | 左丘 | 东门 | 西门 | 佘 | 佴 | 伯 | 赏 | 南宫 | 墨 | 哈 | 谯 | 笪 | 年 | 爱 | 仝 | 代)[\u4E00-\u9FA5]{1,6}");

    @Test
    public void segmentByJieba() {
        JiebaSegmenter segmenter = new JiebaSegmenter();

        List<String> words = segmenter.sentenceProcess(chapter);
        // System.out.println(words);

        List<Tuple2<String, Long>> names = words.stream()
                .filter(StringUtils::isNotEmpty)
                .filter(s -> s.length() > 1)
                .filter(this::isName)
                .collect(Collectors.groupingBy(v -> v, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(s -> s.getValue() > 1)
                .map(s -> Tuples.of(s.getKey(), s.getValue()))
                .sorted(Comparator.comparingLong(Tuple2<String, Long>::getT2).reversed())
                .limit(2)
                .collect(Collectors.toList());

        System.out.print("使用 Jieba 分词:");
        System.out.println(names);
    }

    private boolean isName(String str) {
        return namePattern.matcher(str).find();
    }

    @Test
    public void segmentByHanLP() {
        Segment segment = HanLP.newSegment().enableNameRecognize(true);
        List<Term> terms = segment.seg(chapter);
        // System.out.println(terms);

        List<Tuple2<String, Long>> names = terms.stream()
                .filter(m -> m.nature == Nature.nr)
                .collect(Collectors.groupingBy(v -> v.word, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(s -> s.getValue() > 1)
                .map(s -> Tuples.of(s.getKey(), s.getValue()))
                .sorted(Comparator.comparingLong(Tuple2<String, Long>::getT2).reversed())
                .limit(2)
                .collect(Collectors.toList());

        System.out.print("使用 HanLP 分词:");
        System.out.println(names);
    }
}

3. 测试结果

这是使用一个章节的测试结果:

javascript
使用 HanLP 分词:[[怀素,9], [顾清,9]]
使用 Jieba 分词:[[怀素,16], [顾清,9]]

可以看到,具体的分词结果上还是有些区别的,第一个人名的统计结果差的有点多。
不过感觉增加统计的章节内容的话,准确率应该会高很多。

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.