
简介面向粤语语言学与自然语言处理的pycantonese开源库完整资源包适合作语言学研究、中文NLP方向的开发者、学术研究者及学习者使用。该库围绕粤语独特语法特点展开提供汉字与Jyutping拼音转换、词语分割、停用词过滤、词性标注等核心工具并能结合NLTK、spaCy进行情感分析、语义理解等高级任务特别针对粤语多音字和缺乏明显词边界的难点给出可行方案。压缩包共294个文件涵盖Python源码、RST/Markdown格式文档、CHA/TXT语料数据、HTML页面与字体资源等大小约15.46MB目录结构清晰便于按模块调用和二次开发。目前已有689人浏览学习。通过该资源可以快速搭建粤语分析流程掌握从原始文本到分词语料再到句法标注的完整链路同时借助内置语料与脚本深入理解Jyutping体系并将其应用到教学、词典工具或定制化NLP系统中是粤语计算语言学领域难得的高价值参考资料。1. 为什么处理粤语时通用NLP工具会“翻车”如果你试着把“我哋今日去茶餐廳食飯”丢给常见的普通话分词和拼音工具结果通常很魔幻分词把“茶餐廳”切成“茶/餐/厅”注音更是直接把“食飯”标成普通话读音。原因不复杂粤语书面语和普通话书面语在词汇、虚词、声调系统上差异太大通用工具的词表和发音规则压根没有覆盖“哋”“嘅”“冇”这类字。这就是粤语NLP项目里最常见的翻车现场。pycantonese 是一个面向粤语的 Python 库它内置了粤语常用词典和粤语语料库主要提供分词、粤拼Jyutping标注、词典查询这几项基本功。和通用 NLP 工具不一样它的词表、读音映射、语料来源都围绕粤语本身设计所以上面的句子交给它能得到符合粤语习惯的切分和读音。它的定位不是一个大而全的平台而是粤语文本处理链路里最该先补上的一块短板。如果你在做粤语歌词字幕、粤语教学注音、语音合成前端标注或者只是需要批量处理微博、论坛里的粤语文本这个库是值得先花半天时间试跑的方向。这篇文章会从安装一路讲到接入生产管道包括词典匹配的原理、多音字和异体字的坑、以及我自己遇到过的几次翻车经历。2. 装上就能用pycantonese 的最小运行环境与第一个分词任务2.1 安装前先确认三件事先别急着pip install装之前花两分钟确认三件事能省掉后面不少玄学问题。第一是 Python 版本。pycantonese 属于典型的学术型小库维护节奏不像商业库那么快新版本语法特性基本不会用但太老的 Python 又可能带不动它的依赖。我一般建议直接用 3.8 以上的版本没必要在旧环境里折腾兼容性。第二是确认你当前在哪个虚拟环境里别把包装进系统 Python后面导入时才发现找不到模块。第三是心理预期这个库首次导入时会初始化语料数据不是一行代码秒出的那种轻量工具第一次跑起来比普通库慢几秒是正常现象。安装命令很简单pip install pycantonese python - PY import pycantonese as pc print(pycantonese 安装路径:, pc.__file__) PY第二段脚本用来验证包能不能正常导入。如果能打印出安装路径说明环境没问题如果报ModuleNotFoundError先检查虚拟环境是否激活再用pip list看包到底装到哪了。Windows 用户如果终端打印中文乱码可以把终端编码切到 UTF-8或者在脚本开头加一句sys.stdout.reconfigure(encodingutf-8)这属于环境问题不是库本身的问题。2.2 最小分词脚本安装验证通过之后第一个可以直接抄的脚本就是分词。pycantonese 的分词器是经典的词典最大匹配思路查内置粤语词典能匹配到的最长词优先切出来匹配不到的字就单独成词。这种方案对常用粤语词覆盖不错但对新词、网络词会比较笨这是后面避坑章节要讲的重点。import pycantonese as pc text 我哋今日去茶餐廳食飯 tokens pc.segment(text) # segment() 返回的是字词列表直接打印或按需取用 print(tokens)输出大致是[我哋, 今日, 去, 茶餐廳, 食飯]其中“茶餐廳”会作为一个整体切出来这正是通用工具做不到的地方。这里要注意两点segment()的入参是字符串不是列表返回结果是普通列表可以直接for遍历也可以 .join(tokens)拼成空格分隔的文本。如果拿到的切分结果和你预期的词边界不一样先别急着怀疑库看是不是文本里混入了全角空格或不可见字符。提示segment()不认识的字会切成单字。这不是报错是词典匹配的正常表现后面用自定义词典补。2.3 从“能跑”到“能用”看返回结构与空结果很多人的第一个疑惑是为什么单字能切对整句却切得和我想的不一样。这源于词典最大匹配的固有特性切分结果依赖匹配顺序和词典覆盖同一个词放在不同上下文里边界可能变化。我一般会写一段小脚本批量验证边界场景texts [我哋, 今日, 去茶餐廳, 食飯, 茶餐廳今日好多人] for line in texts: print(line, -, pc.segment(line))这段代码不是为了跑通而是让你观察“茶餐廳”在不同位置是否都被整体切出。如果大部分都切对了说明词典覆盖正常如果同一个词在句子中间被切开通常是因为前后字组成了词典里的另一个更长词属于匹配优先级问题。生产环境里我习惯把这类文本单独抽出来维护进一个“强制成词”的覆盖列表而不是反过来怀疑分词器坏了。跑完这几个用例你对这个库的脾气就基本有数了它擅长粤语常用词但对上下文语义不敏感。明白这一点后面接语音合成或歌词注音时就知道哪些地方要加后处理。3. 用 pycantonese 标粤拼词典匹配的工作原理与三个必调参数3.1 为什么普通话拼音工具标不了粤语把粤语文本丢给普通话拼音工具基本等于让一个只懂普通话发音的人去读广东话结果当然是按普通话字音硬读。粤语和普通话的声调系统完全不是一回事粤拼有六个声调部分分析方案还细分阴入、中入、阳入有入声韵尾 -p/-t/-k还有普通话里根本不存在的声母和韵母组合。更重要的是粤语有一批常用字在普通话里根本不是同一套用法比如“佢”他、“冇”没有、“係”是、“嘅”的这些字在普通话拼音工具里可能压根没有对应读音。我曾经帮某开发者调试一个粤语语音合成前端他图省事直接用了普通话的拼音转换接口结果“唔該”被标成了普通话的“wú gāi”合成出来的音频完全没法听。这个教训很直接粤语文本处理第一步就应该锁定粤语专用工具普通话工具在这个场景里没有平替空间。3.2 先分词再注音两行代码拿逐字粤拼pycantonese 的读音查询接口是按“词”而不是按整句工作的所以标准化流程是先segment()分词再对每个词查粤拼。这里分享一个可以直接跑的最小脚本import pycantonese as pc text 佢哋好鍾意食蛋撻 for token in pc.segment(text): jp pc.jyutping(token) print(f{token} : {jp})jyutping()的输入是单个词输出结构在不同版本里略有差异有的版本返回包含(字, 读音)元组的列表有的版本直接返回字典。这个差异让不少初学者栽过跟头所以我一般会写一层兼容函数把不同返回结构统一成字符串import pycantonese as pc def first_reading(result): # 有的版本返回 [(字, zyut6), (詞, ci4)] 这种列表 # 有的版本返回 {字: zyut6} 这种字典 if isinstance(result, dict): return .join(result.values()) if isinstance(result, list): return .join(pair[1] for pair in result) return str(result) text 佢哋好鍾意食蛋撻 for token in pc.segment(text): print(token, first_reading(pc.jyutping(token)))这段代码就是典型的“面向返回结构编程”。初学者最容易犯的错是把整句直接传给jyutping()得到的结果要么为空要么结构混乱。正确姿势永远是先分词、再逐词查询。实际项目里如果需要控制输出格式比如只要第一个读音可以在first_reading()里加split()[0]如果要做逐字对齐就保留列表结构不转成字符串。3.3 多音字与字形归一把“中”读对的三步配置粤拼查询本质是查词典所以它没有上下文消歧能力。最典型的例子是“中”字在“中間”里读zung1在“中意”里读zung3但词典查询只会按词条返回如果词库里没有“中意”这个完整词条就会退回到默认读音。这不是 bug是词典匹配方案的天然边界。要解决多音字问题我一般会维护一个项目级优先读音表PRIORITY { 中意: [(中, zung3), (意, ji3)], 中間: [(中, zung1), (間, gaan1)], 係: [(係, hai6)], } def resolve_reading(word): # 优先查项目自定义表查不到再走库默认查询 if word in PRIORITY: return PRIORITY[word] return pycantonese.jyutping(word)这段代码看起来简单但它是粤语注音管道里最容易出效果的一个环节。优先表的意义不是替代词典而是把你业务里真正关心的多音词钉死避免批量跑完后出现成片的错音。除了多音字还有一类问题也常被忽略粤语文本里的异体字和香港字。最常见的如“裏”“裡”“里”三套写法、“臺”“台”混用如果文本不统一词典查询时一个都匹配不上。我写过一个很简单的归一化函数在分词之前先把字形统一到词典收录的写法NORM {裡: 裏, 裏: 裏, 臺: 台, 爲: 為} def normalize(text): for k, v in NORM.items(): text text.replace(k, v) return text注意归一化要放在segment()之前做否则分词边界会因为异体字被打乱。这一步处理完再跑分词和读音查询空结果率通常会明显下降。“中意”这类多音词的优先表 字形归一化是我做任何粤语文本标注任务都会先配好的两个前置环节。3.4 批量文本注音缓存让速度提升两个量级当你从几行示例代码切换到几千行的真实语料时第一个遇到的性能问题就是重复查询。一句歌词里“嘅”可能出现五六次每次都重新查一遍词典纯属浪费。解决方案是用functools.lru_cache包一层缓存from functools import lru_cache import pycantonese as pc lru_cache(maxsize2048) def jyutping_cached(word): return pc.jyutping(word) def annotate(text): tokens pc.segment(text) return [(word, jyutping_cached(word)) for word in tokens] print(annotate(佢哋好鍾意食蛋撻))缓存命中率在歌词、字幕这类重复度高的语料里非常可观。maxsize我一般设 2048 到 8192 之间太小了频繁淘汰太大了内存白白占用。这里要说清楚一个边界lru_cache只是进程内缓存不跨进程共享。如果你用多进程并行处理大量文本每个进程还是各自查一遍不会因为缓存而节省整体耗时。注意jyutping_cached()的入参必须是单个词不要把整句传进去。整句缓存不仅命中率低还会让词典查询找不到结果。4. 从粤拼到 IPA自建转换层的实践比想象中简单4.1 什么时候需要 IPA粤拼Jyutping是罗马字母拼写方案适合人读和程序处理但语音合成、音系对比、语料库标注这些场景往往要求国际音标IPA。比如你拿粤拼文本去驱动一个基于 IPA 的语音合成前端或者做方言音系对比分析都需要把zyut6这类拼写转成对应的音标符号。这里有个常见误区很多人以为一定有现成工具一键转换实际工程里我很少找到完全满意的方案。pycantonese 的重点放在词语切分和读音查询上要从粤拼到 IPA我更倾向于在 pycantonese 的输出结果上自建一层几十行的规则这比四处找转换库更可控也更容易针对自己的音系方案调整。4.2 最小映射表先按最长键匹配再处理声调粤拼到 IPA 的转换可以拆成两步声母韵母的字母组合映射加声调符号映射。最容易翻车的坑是字母组合的匹配顺序比如aa必须优先于a被匹配否则长元音会被拆成两个短音。我的做法是把映射表按键长度排序先查长键# 示例映射展示思路实际音系方案请按你的标注体系调整 JYUTPING_MAP { aa: aː, a: ɐ, i: iː, u: uː, e: ɛː, o: ɔː, oe: œː, yu: yː, ng: ŋ, } def jyutping_to_ipa(syllable): # 先去掉末尾的声调数字单独处理 tone syllable[-1] if syllable[-1].isdigit() else body syllable[:-1] if tone else syllable # 按键长度降序匹配保证 aa 优先于 a result i 0 while i len(body): matched False for key in sorted(JYUTPING_MAP, keylen, reverseTrue): if body.startswith(key, i): result JYUTPING_MAP[key] i len(key) matched True break if not matched: result body[i] i 1 if tone: result f^{tone} # 声调用上标数字表示工程上最直观 return result print(jyutping_to_ipa(zyut6)) # 输出形如 zyːt^6 的结果这段代码的核心逻辑是两层声调先拆走避免数字影响字母匹配字母部分用降序匹配保证多字母组合优先命中。真实使用中你还需要把声母表比如z、c、s、gw、kw和韵尾p、t、k、m、n、ng也加进映射表逐个音位补全。这个表听起来工作量大实际也就几十行一次配好后面所有文本都能复用。4.3 变调规则之外的事交给后处理IPA 转换最怕的是只按词典音标转完全忽略变调。粤语口语里有不少常见变调现象比如“女”在“女仔”一词里受后字影响可能改变调值“唔該”在句末语气场景下也常有语调漂移。词典里存的是单字本调不是口语实际调值所以拼音转 IPA 的管道必须预留一层后处理。我的做法是维护一张“上下文变调覆盖表”在转换成 IPA 之前先查这张表TONE_OVERRIDE { 女仔: [(女, neoi5), (仔, zai2)], 唔該: [(唔, m4), (該, goi1)], } def apply_tone_override(word): return TONE_OVERRIDE.get(word, None)注意这张表的作用域是“项目相关”。歌词注音项目只需要覆盖歌词里出现的口语词语音合成项目则需要根据合成引擎的韵律模型决定要不要改调。这种后处理方案无法一劳永逸但它在工程上是可控的词典给基础读音覆盖表处理异常场景两者互不干扰。5. 避坑指南5 个让 pycantonese 不好用的真实原因5.1 生僻字与网络新词查不到读音现象批量标注时某些字返回None或空列表比如网络新词、古字、香港本地造字。原因pycantonese 的词表来自其内置语料和词典覆盖面以常用粤语词为主。新词和生僻字不在词表里查询自然落空。解决建一个项目级后备音表。常见做法是手写一个FALLBACK {字: jyut6}字典在词典查询返回空时兜底FALLBACK {嗰: go2, 啲: di1} def safe_jyutping(word): result pycantonese.jyutping(word) if not result: return FALLBACK.get(word, ) return result后备表不需要大只维护你真实语料里出现的缺失项每补一个都能立刻看到效果。跑完一批文本后我会把空结果单独导出来做一次人工核对再决定哪些进后备表。5.2 分词结果和 jieba 差很多别急着换工具现象同一句粤语pycantonese 切出来的词和 jieba 默认词表切出来完全不同比如“我哋”被 jieba 切成了“我/哋”。原因jieba 的默认词典面向普通话书面语“我哋”“茶餐廳”“食飯”这些粤语词不在其词表里被拆碎是正常现象。解决粤语场景优先用 pycantonese 的分词结果。如果你需要在一个管道里同时处理普通话和粤语不要直接换回 jieba而是让文本先走一遍语言判断粤语文本走 pycantonese。混合场景下给 jieba 加粤语自定义词表也可以但要清楚这是你的工程改造不是 jieba 开箱即用的能力。5.3 多音字只回单一读音上下文救不了现象“中意”读成了zung1 ji3实际粤语口语应该读zung3 ji3。原因词典查询按词条匹配词库里没有收录“中意”或者没有为它标注多音于是退回到默认读音。解决不要指望加参数直接用优先级覆盖表。把“中意”这类词钉死在PRIORITY字典里查询前先查优先表。这是我在第 3 章讲过的方法也是所有多音字问题最直接有效的路径。批量跑完后我会按出现频次抽检多音字优先表本身也需要跟着语料迭代。5.4 繁简与异体字导致查询失败现象文本里写的是“裏”词典查不到改成“裡”或“里”就能查到。原因词表收录的是某一套字形写法异体字不在其中。粤语文本里“裏/裡”“臺/台”“爲/為”混用很常见不归一化就会频繁落空。解决在分词和注音前做一步字形归一化。把常用异体字统一到你选定的一套写法再进管道。我这里有一条血泪经验归一化映射表要放在所有处理的最前面千万不要放在分词之后否则边界已经乱了后面对齐全是错的。5.5 首次运行慢、内存占用高现象脚本启动后第一次调用segment()或jyutping()时卡住几秒第二次就快了。原因首次调用要加载语料库和词典到内存初始化成本高。解决把加载步骤放到程序启动阶段用“预热”解决线上首次请求的卡顿。做法很简单程序启动后主动先跑一次空分词让语料库完成加载import pycantonese as pc # 预热启动阶段主动触发一次词典加载 pc.segment(我哋) # 后续业务调用不会再触发加载 tokens pc.segment(今日去茶餐廳)如果是在 Web 服务里用这个预热要放在 Worker 启动函数里而不是放在请求处理函数里。放在请求里第一个用户就要替你承担这几秒的初始化延迟。6. 进阶把它接进歌词字幕标注管道顺便救回多音字把前面的技巧串起来就是一个可以直接放进项目的歌词或字幕注音管道。我平时做这类任务时会把管道封装成纯函数输入一行文本输出逐词注音结果方便单测也方便复用import re from functools import lru_cache import pycantonese as pc PRIORITY { 中意: [(中, zung3), (意, ji3)], 唔該: [(唔, m4), (該, goi1)], } lru_cache(maxsize4096) def lookup(word): if word in PRIORITY: return PRIORITY[word] return pycantonese.jyutping(word) def annotate_line(line): # 去除歌词常见的标点只保留正文 cleaned re.sub(r[。、\s], , line) words pc.segment(cleaned) return [(word, lookup(word)) for word in words] for line in [佢哋好鍾意食蛋撻, 唔該要一杯凍奶茶]: print(line, , annotate_line(line))这个管道的核心设计是三层结构分词负责切词优先表负责钉死多音字缓存负责扛批量。每一层都可以单独测试和替换比如某天你换了一个更大的粤语词典分词层和缓存层不受影响。它的边界也很清楚只处理正文文本不负责分离歌词里的时间轴和歌曲信息那些需要在管道外面单独解析。如果跑出来的注音要用于语音合成前端还有一个额外动作把结果按“字”而不是按“词”对齐因为合成引擎通常需要逐字读音。此时只要把列表里的二元组展开成单字级对应即可逻辑不复杂但必须确认分词结果里每个字都被覆盖到漏字的话后面韵律标注全是错位。说句实话这种管道跑通不难难的是后续维护。我之前做一个模拟粤语字幕项目第一批批量注音出来错误率不到百分之几我以为没问题直到测试用户反馈“中意”读成了“中间”的意思才发现多音字错音是批量跑完后最刺眼的硬伤。从那以后我养成一个习惯每次批量注音完按字频从高到低抽 20 条人工扫一遍再决定要不要补优先表。词典会漏规则会错但人工抽检能把翻车概率压到可接受的水平。希望这个思路帮到你。本文还有配套的精品资源点击获取