超长文本分词前必须预处理,否则会panic;需trim空白、清除零宽字符、粗滤HTML标签,并按标点切分控制单次输入≤50KB(UTF-8字节数),未做则gse可能卡死、gojieba返回空切片不报错。

超长文本分词前必须做预处理,否则 seg 会 panic
Go 标准库不带中文分词能力,主流方案是用 gojieba 或 gse。但两者对超长文本(比如 >1MB 的日志或文档)都容易触发内存溢出或无限循环——根本原因是原始文本含大量空白符、控制字符、嵌套 HTML 标签或未闭合的引号。
实际项目中,必须在调用分词器前做三件事:
- 用
strings.TrimSpace去首尾空白,别只用TrimSpace就完事,要配合strings.Map清除 \u200b、\ufeff 等零宽字符 - 用正则
regexp.MustCompile(`?[^>]*>`)粗粒度过滤 HTML 标签(不需要完整解析,避免引入golang.org/x/net/html增加复杂度) - 按句号、换行、分号等切分长段,单次传入分词器的长度控制在 50KB 以内——不是字符数,是 UTF-8 字节数,用
len([]byte(text))判断
gse.Segment 可能卡死,gojieba.Cut 会返回空切片且不报错,排查时容易误判为“没关键词”。
gojieba 和 gse 提取关键字的逻辑完全不同
gojieba 默认用 TF-IDF + 词性过滤(需加载词性模型),gse 默认只做基础分词,关键字提取得自己实现统计逻辑。选哪个取决于你是否需要“重要性排序”:
- 要直接拿到 top-K 关键词?用
gojieba的ExtractWithWeight(text, topK),但注意它内部会缓存词频,超长文本分多次调用时得手动重置jb.Reset(),否则权重失真 - 只要高频词?用
gse的Segments结果,遍历后用map[string]int统计,但得过滤掉停用词——gse自带停用词表路径是data/stopwords.txt,必须显式调用seg.LoadStopwords("data/stopwords.txt"),否则停用词无效 - 两者都默认不识别新词(如人名、术语),
gojieba支持AddWord动态注入,gse要改dict/dict.txt文件并重新加载,线上热更新麻烦
并发处理超长文本时,sync.Pool 比全局变量更安全
分词器实例本身不是 goroutine-safe 的:gojieba.Jieba 内部有共享的 trie 和缓存,gse.Segmenter 的 dict 是只读但 seg.cache 会写。如果用全局单例+并发调用,会出现分词结果错乱或 panic。
正确做法是:
- 用
sync.Pool管理分词器实例,比如var jiebaPool = sync.Pool{New: func() interface{} { return gojieba.NewJieba() }} - 每次处理前
jb := jiebaPool.Get().(*gojieba.Jieba),结束后defer jiebaPool.Put(jb) - 别用
init()预加载词典——gojieba的NewJieba()已包含加载,重复调用LoadDict会导致内存泄漏
sync.Pool 后稳定。
关键词去重和归一化比想象中更难
分词结果里常出现“人工智能”“AI”“AI技术”“人工智能技术”并存,单纯字符串匹配去重会丢语义。真实场景要分两层处理:
- 表层归一:用同义词映射表,比如
map[string]string{"AI": "人工智能", "深度学习": "机器学习"},这个表得业务方维护,别依赖开源词林(覆盖率低) - 向量聚类(可选):对每个词用
hanlp或bert4go提取 768 维向量,再用余弦相似度合并相似词——但这一步 I/O 开销大,只建议对最终 top-20 关键词做,别对全部分词结果跑 - 注意大小写:“iPhone”和“iphone”在中文场景下通常算同一个词,但
strings.EqualFold会把“SQL”和“sql”也合并,得加白名单判断
[]string 里可能含空字符串或单字(如“的”“了”),这些必须在统计前过滤,否则“的”永远排第一。立即学习“go语言免费学习笔记(深入)”;


















