
对于一个拼音输入法来说, 最重要也最基础的数据, 就是拼音和汉字的对应关系, 或者说拼音和汉字的对照表.
获取拼音数据有多种方法, 本文介绍其中的一种: 从 Unicode 标准 (Unihan 数据库) 获取拼音数据.
+ 1 Unihan 数据库
+ 2 读取拼音数据
+ 3 处理拼音数据
+ 4 测试
+ 5 总结与展望
+ 附录 1 完整代码
<https://home.unicode.org/>
Unicode 是一种编码方式 (encoding, 比如 utf-8), 也是一种字符集 (charset). Unicode 的目标是收集地球上所有语言使用的字符.
Unihan 数据库是一个关于汉字的数据库, 收录了几万个汉字, 包含多种数据.
<https://www.unicode.org/charts/unihan.html>
此处我们只关心拼音数据, 先把这个数据库下载下来:
wget &quot;https://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip&quot; 然后解压:
unzip Unihan.zip 解压后可以看到几个文件:
&gt; ls
Unihan_DictionaryIndices.txt Unihan_OtherMappings.txt
Unihan_DictionaryLikeData.txt Unihan_RadicalStrokeCounts.txt
Unihan_IRGSources.txt Unihan_Readings.txt
Unihan_NumericValues.txt Unihan_Variants.txt 其中 Unihan_Readings.txt 就包含了汉字的读音 (拼音) 数据.
打开 Unihan_Readings.txt 文件 (这个文件有点大, 有 265481 行), 可以看到:
#
# Unihan_Readings.txt
# Date: 2023-07-15 00:00:00 GMT [KL]
# Unicode version: 15.1.0
#
# Unicode Character Database
# © 2023 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
# For documentation, see http://www.unicode.org/reports/tr38/
#
# This file contains data on the following fields from the Unihan database:
# kCantonese
# kDefinition
# kHangul
# kHanyuPinlu
# kHanyuPinyin
# kJapanese
# kJapaneseKun
# kJapaneseOn
# kKorean
# kMandarin
# kSMSZD2003Readings
# kTang
# kTGHZ2013
# kVietnamese
# kXHC1983
#
# For details on the file format, see http://www.unicode.org/reports/tr38/
#
U+3400 kCantonese jau1
U+3400 kDefinition (same as U+4E18 丘) hillock or mound
U+3400 kJapanese キュウ おか
U+3400 kMandarin qiū
U+3401 kDefinition to lick; to taste, a mat, bamboo bark
U+3401 kHanyuPinyin 10019.020:tiàn
U+3401 kJapanese テン
U+3401 kMandarin tiàn
U+3402 kDefinition (J) non-standard form of U+559C 喜, to like, love, enjoy; a joyful thing
U+3402 kJapanese キ よろこぶ
U+3403 kCantonese zim1
U+3404 kCantonese kwaa1
U+3404 kJapanese カ ケ
U+3404 kMandarin kuà
U+3405 kCantonese ng5
U+3405 kDefinition (an ancient form of U+4E94 五) five
U+3405 kJapanese ゴ
U+3405 kMandarin wǔ 以 # 开头的行是注释. 其中 kMandarin 表示汉语拼音 (普通话), 比如:
U+3400 kMandarin qiū 这一行有 3 列, 之间以制表符 (tab) 分隔. 其中 U+3400 是字符编码, qiū 是拼音.
弄清楚文件格式之后, 就可以写代码 (python) 来读取拼音数据了:
# 读取 Unihan_Readings.txt
def 读取数据(文件名):
print(文件名)
文本 = 读文件(文件名)
o = []
for i in 文本.split(&quot;\n&quot;):
# 忽略注释
if i.startswith(&quot;#&quot;):
continue
# 忽略空行
if len(i.strip()) &lt; 1:
continue
# 处理 kMandarin
p = i.split(&quot; &quot;) # 分隔符: 制表符 (tab)
# 汉语拼音 (普通话)
if p[1] == &quot;kMandarin&quot;:
汉字 = chr(int(p[0][2:], 16))
o.append([汉字, p[2]])
return o 读取整个文件, 依次处理每一行. 忽略注释和空行, 然后只关心 kMandarin 所在的行. 将 U+ 的格式转换成对应的字符.
上面获得的拼音是带有声调的, 比如 qióng. 一般拼音输入法是不带声调的, 因此要进行转换处理.
# 统计拼音中出现的字符
def 统计拼音(数据):
o = {}
for i in 数据:
for c in i[1]:
if (ord(c) &gt; ord(&quot;z&quot;)) or (ord(c) &lt; ord(&quot;a&quot;)):
o[c] = 1
字符 = list(o.keys())
字符.sort()
print(字符) 我们先来看看拼音中有哪些字符. 上面这段代码找出拼音中除 a ~ z 之外的所有字符.
def main():
# 获取命令行参数
输入 = sys.argv[1]
输出 = sys.argv[2] if len(sys.argv) &gt; 2 else None
数据 = 读取数据(输入)
if 输出 != None:
结果 = 处理拼音(数据)
写文件(输出, 结果)
else:
统计拼音(数据)
if __name__ == &quot;__main__&quot;:
main() 主函数这么写, 然后运行一下:
&gt; python unihan_readings.py Unihan_Readings.txt
Unihan_Readings.txt
[&#39; &#39;, &#39;à&#39;, &#39;á&#39;, &#39;è&#39;, &#39;é&#39;, &#39;ì&#39;, &#39;í&#39;, &#39;ò&#39;, &#39;ó&#39;, &#39;ù&#39;, &#39;ú&#39;, &#39;ü&#39;, &#39;ā&#39;, &#39;ē&#39;, &#39;ě&#39;, &#39;ī&#39;, &#39;ń&#39;, &#39;ň&#39;, &#39;ō&#39;, &#39;ū&#39;, &#39;ǎ&#39;, &#39;ǐ&#39;, &#39;ǒ&#39;, &#39;ǔ&#39;, &#39;ǘ&#39;, &#39;ǚ&#39;, &#39;ǜ&#39;, &#39;ǹ&#39;, &#39;ḿ&#39;]
我们就得到了所有需要特殊处理的字符, 写一个对照表:
# 拼音字符对照表 (声调)
拼音表 = {
&quot;ā&quot;: (&quot;a&quot;, 1),
&quot;á&quot;: (&quot;a&quot;, 2),
&quot;ǎ&quot;: (&quot;a&quot;, 3),
&quot;à&quot;: (&quot;a&quot;, 4),
&quot;ē&quot;: (&quot;e&quot;, 1),
&quot;é&quot;: (&quot;e&quot;, 2),
&quot;ě&quot;: (&quot;e&quot;, 3),
&quot;è&quot;: (&quot;e&quot;, 4),
&quot;ī&quot;: (&quot;i&quot;, 1),
&quot;í&quot;: (&quot;i&quot;, 2),
&quot;ǐ&quot;: (&quot;i&quot;, 3),
&quot;ì&quot;: (&quot;i&quot;, 4),
&quot;ō&quot;: (&quot;o&quot;, 1),
&quot;ó&quot;: (&quot;o&quot;, 2),
&quot;ǒ&quot;: (&quot;o&quot;, 3),
&quot;ò&quot;: (&quot;o&quot;, 4),
&quot;ū&quot;: (&quot;u&quot;, 1),
&quot;ú&quot;: (&quot;u&quot;, 2),
&quot;ǔ&quot;: (&quot;u&quot;, 3),
&quot;ù&quot;: (&quot;u&quot;, 4),
&quot;ü&quot;: (&quot;v&quot;, 0),
&quot;ǘ&quot;: (&quot;v&quot;, 2),
&quot;ǚ&quot;: (&quot;v&quot;, 3),
&quot;ǜ&quot;: (&quot;v&quot;, 4),
&quot;ń&quot;: (&quot;n&quot;, 2),
&quot;ň&quot;: (&quot;n&quot;, 3),
&quot;ǹ&quot;: (&quot;n&quot;, 4),
&quot;ḿ&quot;: (&quot;m&quot;, 2),
} 然后就可以对单个拼音进行处理:
# 对拼音的每个字符进行处理
def 处理单个拼音(文本):
声调 = 0
o = &quot;&quot;
for c in 文本:
if ord(c) &gt; ord(&quot;z&quot;):
数据 = 拼音表[c]
声调 = 数据[1]
o += 数据[0]
else:
o += c
# 标注声调
if 声调 &gt; 0:
o += str(声调)
return o 检查拼音中的每个字符, 如果是特殊字符, 就按照上表转换处理.
# 对原始拼音数据进行处理
def 处理拼音(数据):
o = {}
# 集中每个汉字的所有拼音
for i in 数据:
# 多音字
拼音列表 = i[1].split(&quot; &quot;)
for j in 拼音列表:
拼音 = 处理单个拼音(j)
汉字 = i[0]
if o.get(汉字) == None:
o[汉字] = [拼音]
else:
o[汉字].append(拼音)
# 转换输出数据格式
输出 = []
列表 = list(o.keys())
列表.sort()
for i in 列表:
行 = [i] + o[i]
# DEBUG
if len(o[i]) &gt; 1:
print(行)
输出.append((&quot; &quot;).join(行))
return (&quot;\n&quot;).join(输出) + &quot;\n&quot; 对所有拼音进行处理, 然后保存结果.
&gt; python unihan_readings.py unihan/Unihan_Readings.txt pinyin.txt
unihan/Unihan_Readings.txt
[&#39;㪅&#39;, &#39;geng4&#39;, &#39;geng1&#39;]
[&#39;万&#39;, &#39;wan4&#39;, &#39;mo4&#39;]
[&#39;乾&#39;, &#39;qian2&#39;, &#39;gan1&#39;]
[&#39;俾&#39;, &#39;bi3&#39;, &#39;bi4&#39;]
[&#39;剋&#39;, &#39;kei1&#39;, &#39;ke4&#39;]
[&#39;剖&#39;, &#39;pou1&#39;, &#39;po3&#39;]
[&#39;剽&#39;, &#39;piao1&#39;, &#39;piao4&#39;]
[&#39;卜&#39;, &#39;bo&#39;, &#39;bu3&#39;]
[&#39;叚&#39;, &#39;xia2&#39;, &#39;jia3&#39;]
[&#39;嘸&#39;, &#39;fu3&#39;, &#39;wu3&#39;]
[&#39;噠&#39;, &#39;da1&#39;, &#39;da2&#39;]
[&#39;地&#39;, &#39;de&#39;, &#39;di4&#39;]
[&#39;堤&#39;, &#39;di1&#39;, &#39;ti2&#39;]
[&#39;差&#39;, &#39;cha4&#39;, &#39;cha1&#39;]
[&#39;帆&#39;, &#39;fan1&#39;, &#39;fan2&#39;]
[&#39;徵&#39;, &#39;zhi3&#39;, &#39;zheng1&#39;]
[&#39;擘&#39;, &#39;bai1&#39;, &#39;bo4&#39;]
[&#39;斗&#39;, &#39;dou4&#39;, &#39;dou3&#39;]
[&#39;杓&#39;, &#39;biao1&#39;, &#39;shao2&#39;]
[&#39;柏&#39;, &#39;bai3&#39;, &#39;bo2&#39;]
[&#39;氾&#39;, &#39;fan2&#39;, &#39;fan4&#39;]
[&#39;沈&#39;, &#39;shen3&#39;, &#39;chen2&#39;]
[&#39;沓&#39;, &#39;da2&#39;, &#39;ta4&#39;]
[&#39;甸&#39;, &#39;dian1&#39;, &#39;dian4&#39;]
[&#39;瞭&#39;, &#39;liao4&#39;, &#39;liao3&#39;]
[&#39;筽&#39;, &#39;ou1&#39;, &#39;wu2&#39;]
[&#39;繃&#39;, &#39;beng3&#39;, &#39;beng1&#39;]
[&#39;耙&#39;, &#39;ba4&#39;, &#39;pa2&#39;]
[&#39;舍&#39;, &#39;she3&#39;, &#39;she4&#39;]
[&#39;薄&#39;, &#39;bao2&#39;, &#39;bo2&#39;]
[&#39;袷&#39;, &#39;qia1&#39;, &#39;jia2&#39;]
[&#39;誰&#39;, &#39;shui2&#39;, &#39;shei2&#39;]
[&#39;諞&#39;, &#39;pian3&#39;, &#39;pian2&#39;]
[&#39;諷&#39;, &#39;feng3&#39;, &#39;feng4&#39;]
[&#39;識&#39;, &#39;shi2&#39;, &#39;shi4&#39;]
[&#39;讽&#39;, &#39;feng3&#39;, &#39;feng4&#39;]
[&#39;识&#39;, &#39;shi2&#39;, &#39;shi4&#39;]
[&#39;跌&#39;, &#39;die1&#39;, &#39;die2&#39;]
[&#39;蹣&#39;, &#39;pan2&#39;, &#39;man2&#39;]
[&#39;蹬&#39;, &#39;deng1&#39;, &#39;deng4&#39;]
[&#39;适&#39;, &#39;shi4&#39;, &#39;kuo4&#39;]
[&#39;都&#39;, &#39;dou1&#39;, &#39;du1&#39;]
[&#39;醱&#39;, &#39;fa1&#39;, &#39;po4&#39;]
[&#39;釐&#39;, &#39;xi1&#39;, &#39;li2&#39;]
[&#39;陂&#39;, &#39;bei1&#39;, &#39;pi2&#39;]
[&#39;隄&#39;, &#39;di1&#39;, &#39;ti2&#39;]
[&#39;隗&#39;, &#39;kui2&#39;, &#39;wei3&#39;]
[&#39;頗&#39;, &#39;po1&#39;, &#39;po3&#39;]
[&#39;髪&#39;, &#39;fa4&#39;, &#39;fa3&#39;]
[&#39;髮&#39;, &#39;fa4&#39;, &#39;fa3&#39;]
[&#39;麃&#39;, &#39;pao2&#39;, &#39;biao1&#39;]
[&#39;𪟝&#39;, &#39;ji4&#39;, &#39;ji1&#39;] 这里输出的是所有的多音字.
我们检查一下结果文件 pinyin.txt:
&gt; wc -l pinyin.txt
41419 pinyin.txt 一共有 41419 个汉字.
&gt; head pinyin.txt
㐀 qiu1
㐁 tian4
㐄 kua4
㐅 wu3
㐆 yin3
㐌 yi2
㐖 xie2
㐜 chou2
㐡 nuo4
㐤 dan1 结果的前几行看起来是正确的.
&gt; cat pinyin.txt | grep &quot;[穷人小水滴]&quot;
人 ren2
小 xiao3
水 shui3
滴 di1
穷 qiong2 随机抽查, 发现都获取了拼音.

Unihan 是 Unicode 标准中的数据库, 这些数据开源开放, 使用起来没有版权问题. 同时这个数据库的覆盖率较高, 收录了 4 万多个汉字的拼音.
但是这个数据的准确度和完整度是很值的怀疑的, 后续还需要手动修正.
+ 修正: Unihan_Readings.txt 数据中, 除了 kMandarin (普通话汉语拼音), 还有一种重要数据 kTGHZ2013, 这是 2013 年国家发布的 《通用规范汉字表》, 共收录汉字 8105 个.
虽然这个拼音数据的覆盖范围小一些, 但是准确性和规范性都很高, 建议优先使用.
拼音数据对比: kMandarin 有 41419 个汉字, kTGHZ2013 有 8105 个汉字. 相比 kMandarin, kTGHZ2013 新增 0 个汉字, 缺失 33314 个汉字. 其中 7304 个拼音相同, 801 个拼音不同. kTGHZ2013 增加了许多 多音字.
+ unihan_readings.py
#!/usr/bin/env python
# pmim-data/tool/unicode/unihan_readings.py
#
# &gt; python --version
# Python 3.11.7
#
# 命令行格式 (举例):
# &gt; python unihan_readings.py Unihan_Readings.txt pinyin.txt
import sys
import io
# 读取文本文件
def 读文件(文件名):
with io.open(文件名, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
return f.read()
# 写文本文件
def 写文件(文件名, 文本):
with io.open(文件名, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
f.write(文本)
# 读取 Unihan_Readings.txt
def 读取数据(文件名):
print(文件名)
文本 = 读文件(文件名)
o = []
for i in 文本.split(&quot;\n&quot;):
# 忽略注释
if i.startswith(&quot;#&quot;):
continue
# 忽略空行
if len(i.strip()) &lt; 1:
continue
# 处理 kMandarin
p = i.split(&quot; &quot;) # 分隔符: 制表符 (tab)
# 汉语拼音 (普通话)
if p[1] == &quot;kMandarin&quot;:
汉字 = chr(int(p[0][2:], 16))
o.append([汉字, p[2]])
return o
# 统计拼音中出现的字符
def 统计拼音(数据):
o = {}
for i in 数据:
for c in i[1]:
if (ord(c) &gt; ord(&quot;z&quot;)) or (ord(c) &lt; ord(&quot;a&quot;)):
o[c] = 1
字符 = list(o.keys())
字符.sort()
print(字符)
# 拼音字符对照表 (声调)
拼音表 = {
&quot;ā&quot;: (&quot;a&quot;, 1),
&quot;á&quot;: (&quot;a&quot;, 2),
&quot;ǎ&quot;: (&quot;a&quot;, 3),
&quot;à&quot;: (&quot;a&quot;, 4),
&quot;ē&quot;: (&quot;e&quot;, 1),
&quot;é&quot;: (&quot;e&quot;, 2),
&quot;ě&quot;: (&quot;e&quot;, 3),
&quot;è&quot;: (&quot;e&quot;, 4),
&quot;ī&quot;: (&quot;i&quot;, 1),
&quot;í&quot;: (&quot;i&quot;, 2),
&quot;ǐ&quot;: (&quot;i&quot;, 3),
&quot;ì&quot;: (&quot;i&quot;, 4),
&quot;ō&quot;: (&quot;o&quot;, 1),
&quot;ó&quot;: (&quot;o&quot;, 2),
&quot;ǒ&quot;: (&quot;o&quot;, 3),
&quot;ò&quot;: (&quot;o&quot;, 4),
&quot;ū&quot;: (&quot;u&quot;, 1),
&quot;ú&quot;: (&quot;u&quot;, 2),
&quot;ǔ&quot;: (&quot;u&quot;, 3),
&quot;ù&quot;: (&quot;u&quot;, 4),
&quot;ü&quot;: (&quot;v&quot;, 0),
&quot;ǘ&quot;: (&quot;v&quot;, 2),
&quot;ǚ&quot;: (&quot;v&quot;, 3),
&quot;ǜ&quot;: (&quot;v&quot;, 4),
&quot;ń&quot;: (&quot;n&quot;, 2),
&quot;ň&quot;: (&quot;n&quot;, 3),
&quot;ǹ&quot;: (&quot;n&quot;, 4),
&quot;ḿ&quot;: (&quot;m&quot;, 2),
}
# 对拼音的每个字符进行处理
def 处理单个拼音(文本):
声调 = 0
o = &quot;&quot;
for c in 文本:
if ord(c) &gt; ord(&quot;z&quot;):
数据 = 拼音表[c]
声调 = 数据[1]
o += 数据[0]
else:
o += c
# 标注声调
if 声调 &gt; 0:
o += str(声调)
return o
# 对原始拼音数据进行处理
def 处理拼音(数据):
o = {}
# 集中每个汉字的所有拼音
for i in 数据:
# 多音字
拼音列表 = i[1].split(&quot; &quot;)
for j in 拼音列表:
拼音 = 处理单个拼音(j)
汉字 = i[0]
if o.get(汉字) == None:
o[汉字] = [拼音]
else:
o[汉字].append(拼音)
# 转换输出数据格式
输出 = []
列表 = list(o.keys())
列表.sort()
for i in 列表:
行 = [i] + o[i]
# DEBUG
if len(o[i]) &gt; 1:
print(行)
输出.append((&quot; &quot;).join(行))
return (&quot;\n&quot;).join(输出) + &quot;\n&quot;
def main():
# 获取命令行参数
输入 = sys.argv[1]
输出 = sys.argv[2] if len(sys.argv) &gt; 2 else None
数据 = 读取数据(输入)
if 输出 != None:
结果 = 处理拼音(数据)
写文件(输出, 结果)
else:
统计拼音(数据)
if __name__ == &quot;__main__&quot;:
main()
本文使用 CC-BY-SA 4.0 许可发布.