资讯详情

韩语词库构建实战:数据清洗、词条建模与Anki导出

📅 2026/10/11 2:38:39 | 华诺云谱 👁 阅读
韩语词库构建实战:数据清洗、词条建模与Anki导出
简介一份面向韩语学习与词典开发的词汇表资源适合韩语学习者、词典应用开发者和语料分析人员使用。压缩包内共有五个文件包含两个词典数据文件、两个词表文本文件和一个说明文档压缩包大小约28.37MB。词表文件分别提供包含重复词的完整列表和去重后的独立列表便于批量记忆或构建词库两个词典数据文件存储了整理前后的词条与释义可配合韩国国立国语院标准词典的检索链接查询对应词汇。资源已有三百九十一人学习下载适合需要系统梳理韩语单词、开发检索工具或进行自然语言处理的学习者。通过对比整理前后的词典数据可以更直观地掌握每一条词汇的用法变化同时借助说明文档中的查询示例能够快速验证单词的准确含义从而高效搭建个人韩语学习语料库。1. korean_wordlist到底是什么不是词表是词库工程korean_wordlist这个标题听起来像是“把韩语单词整理进一张表”的轻量任务但真下手做一遍你会发现它是文本清洗、数据建模、检索设计三件事的合体。我最早为某个模拟备考工具做底层词库时第一版方案朴素得可笑抓一份公开词表、转CSV、直接入库。结果同一单词以不同词性重复出现、动词全是-다结尾导致没法背、CSV一用Excel打开就乱码前后返工三次才把数据理干净。这个项目要做的事是用一套可复现的流水线把散落各处的韩语词汇整理成字段统一、分级明确、能直接对接Anki、App或NLP任务的结构化词表。适合三类人做韩语学习工具的开发者、要干净词表做NLP实验的入门者、想自建背诵数据的韩语学习者。2. 词条建模先于写代码韩语词汇表该有的字段与分级词库最贵的决定是字段设计。字段定死清洗、去重、导出都在轨道上跑字段没定死每加一个功能都意味着全量返工。我见过不止一个项目把“单词-释义”两列当作词库全部家当后来要加发音时发现没罗马音字段、要按TOPIK等级推送时发现没等级字段只能重新处理一遍整个数据。所以这一章先把词条结构讲透再谈实现。2.1 最少字段集谚文、罗马音、词性、释义缺一不可一份面向韩语学习者的词表最少得四个字段谚文原词word、罗马音romanized、词性pos、中文释义meaning。这四个分别回答“怎么写”“怎么读”“什么词类”“什么意思”哪一个缺失下游都会有问题。释义缺了在流水线里立刻能发现罗马音缺了往往要等做发音功能才暴露两次返工的成本远高于一开始就留好位置。{ word: 학교, romanized: hakgyo, pos: noun, meaning: 学校, level: topik1, frequency_rank: 87 }我一般还会追加三个可选字段levelTOPIK等级、frequency_rank频次排名、examples例句数组。等级和频次是学习路径排序的依据例句是语境理解的载体对黏着语尤其重要因为单词在句子里的形态常常和词典形长得不一样。还有一个细节容易忽略word字段必须是词典形动词形容词用-다结尾。比如“가다”是合法词条“갑니다”就不该出现它是“가다”的敬语活用形放进词表会污染去重和搜索逻辑。词性的粒度也要提前谈好。韩语词性粗分有名词、代词、数词、动词、形容词、冠形词、副词、感叹词但学习工具最常用的粒度就是四类noun、verb、adj、adv。初版词库不必陷入他动词/自动词的细分——那些留给NLP场景后续再加学习场景里动词就是动词欠一个级别的精确度换到的是一大幅度降低的维护成本。2.2 分级与来源TOPIK等级和高频词表怎么对齐韩语学习领域公认的难度标尺是TOPIK考试体系TOPIK I对应1-2级TOPIK II对应3-6级。市场上流传的“TOPIK必备词表”大多按这个体系做Tag一份完整的korean_wordlist应当给每个词打上level标签这也是后续所有按难度切分功能的前提。做分级时要特别核对来源的原始名称。有些表叫“TOPIK初级词表”内部却混入大量中高级词有些叫“高频6000词”但高频词和考试词的重合率并没有想象中高——前者按语料库词频排序后者按考点重要性排序两套逻辑不能混着用。我实践下来可行的对齐路径是拿一份公开的TOPIK分级词表做主集另一份开放语料的高频词表做排序辅助遇到两表等级冲突某词在A表是2级、在B表是3级就人工去查考试真题里该词出现的篇数来裁定。这个过程自动化能做一半先把两表都转成统一词条结构再按word做join冲突记录落盘最后人工抽检被标记的冲突词。不要迷信任何一份来源的完整性多源交叉才能过滤出可信的等级数据。初版词库选多大容量也是个决定项。常见做法是直接做全量考试词表约6000词但如果目标是快速上线一个可用版本优先收录高频1000词加初级词表保证前两个月的内容质量。容量小一点没关系字段设计正确后后续扩充只是往流水线里喂数据的问题。2.3 活用形不搞清词库就是个空壳韩语是黏着语动词和形容词的活用体系是词库建模里最绕不开的结构性问题。一个韩语动词如果只存词典形不标记词干后续做例句对齐或搜索映射都会很吃力。以“가다”去为例词干是“가”加连接词尾-ㅂ니다得“갑니다”去敬语加过去时词尾-았어요得“갔어요”去了。同一个词目下挂着一整族不同结尾的词形。通常做法是在词条结构里增加一个可选的stem字段存放去除-다之后的结果。这样词库既保留了学习者需要的词典形作为主入口又给检索层留了一条从其他形态映射回词典形的路径。{ word: 가다, stem: 가, romanized: gada, pos: verb, meaning: 去, level: topik1, examples: [ { sentence: 학교에 갑니다, translation: 去学校 } ] }实际搜索场景里用户在App里输入“갑니다”预处理阶段如果能先用一组词尾规则把它裁剪成“가”再拿“가”去词库匹配命中率会显著提高。不做这个设计用户一点击活用形就搜不到词这在韩语学习工具里是体验灾难。具体词尾规则的完整列表很长初版词库不需要全部实现先覆盖使用频率最高的-습니다/-ㅂ니다、-았/었어요、-는/은三类就够了剩余规则等用户反馈出来再补避免一开始就被词尾表淹没。3. 从原始词表到korean_wordlist构建清洗流水线词条结构定好后做的事情是把来自不同源头、格式混乱的原始词表清洗成符合模型的结构化数据。清洗流水线我拆成三步Unicode归一化、词典形去重、多格式导出。每一层只解决一类问题顺序不能颠倒否则后面做完了再回头补归一化去重和排序全要重做。3.1 韩文Unicode归一化NFC和全角符号的细节韩文在Unicode里的编码方式有两个层次这是所有韩文处理项目的第一个坑。现代韩文音节如“학교”在Unicode里是预先组合好的单一码点两个音节恰好是两个码点。但不少网页和文档工具会把音节拆分成初声、中声、终声的序列存放——同一面目全非的“학교”在底层是两个不同的字符串。如果不做归一化它们在字典里会各自占据不同的key去重失效、排序错乱、搜索失配全都会找上门。import unicodedata def normalize_hangul_line(text: str) - str: # NFC 把拆开的初/中/终声重新组合成完整音节 text unicodedata.normalize(NFC, text) # 清理零宽空格与 BOM text text.replace(\u200b, ).replace(\ufeff, ) # 全角符号统一为半角韩文词表常混入全角括号/句点/逗号 table {ord(full): ord(half) for full, half in zip( , ():;.,)} return text.translate(table) assert normalize_hangul_line(학교) 학교代码里值得解释的参数有三个。一是unicodedata.normalize(NFC)NFC形式把拆开的子序列重新组合成预组合音节这是韩文处理的标准起点二是零宽空格U200B和BOMUFEFF——从网页复制的词表几乎一定会带进来肉眼不可见却足以让字符串相等判断静默失败必须显式清除三是全角符号映射表有些来源把括号、逗号写成全角形式中文环境下导出的词表尤为常见统一成半角后CSV和搜索才不会出幺蛾子。韩文音节块在Unicode里的排列规律在这里顺带说明因为后面章节反复用它。音节起始于UAC00每个音节的码点值等于“初声索引×21×28 中声索引×28 终声索引”。初声19种、中声21种、终声28种含无终声。这意味着从一个音节的code point就能反推它由哪三个音素构成这个逆运算在排序和首字母搜索里是实现的关键。清洗阶段我还会用这个范围做一次全量校验把不在合法音节区间内的字符全部打印出来人工确认能拦下不少混进词表的乱码。3.2 词典形去重为什么字符串集合去重会误伤初学者最自然的去重动作是set(word)这在韩语词库上是危险的。同形异义词在韩语里不少最典型的例子是“배”它可以是“梨”也可以是“船”还可以是“肚子”。三个词条字符串一样词性一样都是名词释义不同。字符串去重会把它们合并成一条数据量一大这种静默吞词的问题会成片出现。def dedupe_entries(entries): seen set() result [] for entry in entries: # 去重键(word, pos)同形异义词靠 sense_id 再拆 key (entry[word], entry.get(pos, )) if key in seen: continue seen.add(key) result.append(entry) return result去重键用(word, pos)二元组比单纯用word安全一层但仍然罩不住“배”这种同形同词性的多义词。进一步的办法是给条目加sense_id字段两个“배”分别标fruit和ship去重键升级为(word, pos, sense_id)。sense_id的取名不能依赖自动演化必须人工介入因为自动判义的工具在韩语上并不成熟。人工量不会太大全量词表里真正的多义词大约只有几十个逐个处理是值得的。另一个隐蔽问题是合并重复来源多张原始词表拼进来时同一词条往往出现多次但各表字段完整度不同——A表有罗马音没有频次B表有频次没有罗马音。合并时应以字段质量最高的那张表作底其余表只做补全而不是后出现的完整条目直接覆盖先出现的。3.3 多格式导出JSON、CSV、Markdown一套搞定清洗完的词条要有一层稳定的导出不同下游消费的格式不一样。App和API用JSON数据交换与表格处理用CSV人工审阅与git diff用Markdown。这一层看似是体力活但字段顺序若不固定每次导出都可能列漂移下游解析就会在版本升级后崩掉。import json, csv def export_wordlist(entries, json_pathwords.json, csv_pathwords.csv, md_pathwords.md): with open(json_path, w, encodingutf-8) as f: json.dump(entries, f, ensure_asciiFalse, indent2) fieldnames [word, stem, romanized, pos, meaning, level] with open(csv_path, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() for e in entries: writer.writerow({k: e.get(k, ) for k in fieldnames}) with open(md_path, w, encodingutf-8) as f: for e in entries: f.write(f- **{e[word]}** ({e[romanized]}) f[{e[pos]}] {e[meaning]}\n)三个细节直接决定导出好不好用。第一个是json.dump的ensure_asciiFalse不设它谚文全部变成\uXXXX转义序列JSON文件人能看懂的部分就只剩键名不利于调试和审查。第二个是CSV用utf-8-sig编码它会在文件头写入BOMExcel和WPS才会正确识别UTF-8这个参数和newline缺一不可否则要么乱码要么每行后多出空行。第三个是Markdown的模板化输出——每个词条固定为“-单词(罗马音) [词性] 释义”的一行排进diff工具里能清晰看到增删改人工校对几百条词条时这个可读性救过我很多次。4. 排序、查询与性能词库用起来的四个关键点词库清洗完成只是中间态。真正把它做成可用的工具排序规则、查询参数、存储选型和性能边界这四个点会依次出现。这几条线和韩文字形结构的关联很强通用文本处理的直觉在这里经常失准。4.1 谚文字母序与码点序的差异韩文排序第一直觉是直接用sorted()按字符串排序这也是多数人卡住的地方。由于预组合音节在Unicode里按音节全表顺序排列“가나다”整体的码点序大体等于谚文字典序但以下几个例外会让你在细节处翻车词条里混入英文标签时英文小写字母、大写字母、韩文音节在码点空间里并非按你想要的顺序排队如果词库中残留NFD分解形态排序结果会和组合音节完全错开。HANGUL_BASE 0xAC00 N_CHOSEONG 19 N_JUNGSEONG 21 N_JONGSEONG 28 def hangul_sort_key(word: str): key [] for ch in word: code ord(ch) - HANGUL_BASE if 0 code N_CHOSEONG * N_JUNGSEONG * N_JONGSEONG: ch_idx code // (N_JUNGSEONG * N_JONGSEONG) jung_idx (code // N_JONGSEONG) % N_JUNGSEONG jong_idx code % N_JONGSEONG key.extend([ch_idx, jung_idx, jong_idx]) else: key.append(ord(ch) 1000) return tuple(key)这个key函数做的是把每一个韩文音节拆成初声序、中声序、终声序三个数值再按字符顺序展平。它等价于把每个音节还原到“ㄱ”到“ㅎ”的空间里逐层比较结果是严格的谚文字典序。非韩文字符统一加1000偏移量排到所有韩文音节之后这样词表里即便混入英文或数字标注也不会把韩文的排序整体打乱。这个key可以直接传给sorted(entries, keylambda e: hangul_sort_key(e[word]))性能在万级词条上完全可接受。4.2 查询过滤参数等级、词性、首字母词库超过两三千条就一定要有过滤能力。学习者最常见的诉求不是“找某个单词”而是“找ㄱ开头的动词”或“找3级名词”。korean_wordlist里的查询接口至少支持level、pos、first_letter、keyword四个过滤维度。def filter_words(entries, levelNone, posNone, first_letterNone, keywordNone): def first_hangul_letter(word): code ord(word[0]) - HANGUL_BASE if 0 code N_CHOSEONG * N_JUNGSEONG * N_JONGSEONG: return code // (N_JUNGSEONG * N_JONGSEONG) return None result [] for e in entries: if level and e.get(level) ! level: continue if pos and e.get(pos) ! pos: continue if first_letter is not None and first_hangul_letter(e[word]) ! first_letter: continue if keyword and keyword not in e[word] and keyword not in e[meaning]: continue result.append(e) return result三个参数值得强调。first_letter传入的是初声索引而不是谚文字符本身这样调用方可以传0代表ㄱ、传1代表ㄲ、传14代表ㅎ与键码或按钮的映射清晰对应。keyword子串匹配同时打word和meaning两个字段允许用户用中文释义搜词——这个功能学习者很依赖例如搜“学校”应返回“학교、초등학교、고등학교”一组词。但keyword的实现是线性扫描等词库涨到十万级这段循环就必须交给专门的索引阈值细节见4.4。4.3 存储选型SQLite vs JSON vs 内存dict词库的存储选型是一个纯粹按规模做判断的问题。一个模拟项目X里词条约9000条配齐例句和翻译后JSON文件体量在3MB上下启动时整个读进内存、当列表跑过滤循环毫无压力。但当词库扩充到带多种示例、词源、关联词JSON能膨胀到几十甚至上百MB每次都全文加载就不行了。此时SQLite是性价比最高的替代方案。CREATE TABLE words ( id INTEGER PRIMARY KEY, word TEXT NOT NULL, stem TEXT DEFAULT , romanized TEXT DEFAULT , pos TEXT DEFAULT , meaning TEXT DEFAULT , level TEXT DEFAULT , frequency_rank INTEGER DEFAULT 0 ); CREATE INDEX idx_word ON words(word); CREATE INDEX idx_level_pos ON words(level, pos);SQLite单文件、零部署、标准库自带驱动查询从Python循环换成一两句SQL在万级到十万级数据上能把耗时有数量级的压缩。词库的查询模式高度固定过滤字段就是level和pos索引按这两个字段建就够了。这个场景不需要一个独立的数据库进程SQLite一个文件跟着代码仓库走版本管理、测试、分发都方便。我一般这样判断万级以下无脑用JSON万级到十万级切SQLite再往上已经不是普通学习词库的范畴需要专门的搜索服务那时候再来谈全文索引也不迟。别在词库还只有三五千条时就引入重型组件复杂度从哪里来维护成本就从哪里涨。4.4 万级词条的性能边界最后给一组可验证的预期。万级词条、带例句与翻译的JSON体积约5到10MB内存dict加载后单次线性过滤循环耗时在几十毫秒量级人几乎无感知。如果把这套逻辑做成Web API瓶颈常在JSON序列化和网络传输上而不在数据查询本身。三个优化实践足够覆盖绝大多数场景。一是首字母过滤可以用二分法加速在已经按hangul_sort_key排序的列表上先二分出该初声字母的起始与结束下标只遍历这个子区间做后续过滤而不是扫全表。二是例句字段序列化开销大列表接口不要返回examples字段详情接口才返回可以省掉一大半传输时间。三是把多级过滤重构成SQL条件拼接下推给SQLite执行让数据库吃索引Python只做结果包装。做完这三条万级词库的查询响应普遍在几十毫秒内不需要额外组件。5. 韩语词汇表落地避坑五条值得记住的踩坑记录这章记录的是我在实际把词库交到真实场景里之后遇到的五类问题。每条按现象、原因、解决拆开写都是确定发生过的状况不是理论推演。5.1 罗马音标注差一点就差很远现象词表里“학교”的罗马音被我手写完变成了“hagyo”发给朋友试读对方读成了“哈乔”。原因韩语罗马字表记法有细密的规则塞音、紧音、送气音各有约定ㅓ和ㅗ是一对极易混淆的元音非专业人工标注在词条达到数千条时一定会出现前后不一致。解决不要手写罗马音程序化转写并按文化观光部2000年罗马字表记法执行一遍全量转换转换完成后只针对高频词做人工抽检重点看ㅓ/ㅗ、ㅡ/ㅜ两对元音以及终声ㄱ/ㄷ/ㅂ的表现。即便这样也要理解罗马音只是发音的近似映射连音、紧音化等真实语流音变它并承载不了这一点要在文档里对使用者说明白。5.2 Excel打开CSV乱码BOM的三字节教训现象words.csv导出后在Windows上双击打开中文释义全是乱码韩文直接变成问号。原因CSV以UTF-8无BOM写入Windows默认按本地代码页解读两者不匹配导致解码错误。这个问题的表现很稳定谁碰谁乱。解决写CSV时固定用encodingutf-8-sig让写出的文件以EF BB BF这三个字节开头Excel识别到UTF-8的BOM就会自动用正确编码打开。再加一个newline防止写出多余空行。这两处参数已固化在第3.3的导出函数里之后我再也没有在词库项目里见过一次Excel乱码回归。5.3 汉字词占比六成不能按外来词处理现象整理词性分布时发现名词占比异常高仔细查下来大量词目都对应汉字词学校、学生、图书馆、食堂这类我一度准备把它们单拎出来按外来词建表结果把结构折腾得很乱。原因韩语词汇中汉字词占比超过一半它们本来就是韩语正常词汇的一部分与从英语借入的词不是一回事。汉字词是韩语词表的顶梁柱不是旁支。解决汉字词和固有词同表共存不需要单建表但可以在词条上加一个可选的origin字段值为hanja或native供做溯源学习和联想记忆的进阶场景使用。默认查询不区分只在需要时按这个字段filter。加了origin字段之后词库的前向兼容能力明显增强后续有人想按词源组织课程时直接就有数据可用。5.4 -하다派生动词的分合现象词库里“공부”名词学习/功课和“공부하다”动词学习并存做关联推荐时发现两个词在例句中分布高度重叠去重与排序逻辑一度混乱。原因名词하다是韩语最高频的动词构造方式语义等价但语法词性不同。分则重复合则词性缺失怎么都不是。解决最终方案是两个词条都保留词性分别标noun和verbverb词条增加一个related字段链接到对应的名词词元。搜索“공부”时借助related链连带展示“공부하다”两个词条在UI里是一组在数据结构里仍是两条。这种“数据严格分开、展示层聚合”的做法延续到了其他几十个-하다词上结构清爽没有再反复。{ word: 공부하다, stem: 공부하, romanized: gongbuhada, pos: verb, meaning: 学习, related: [공부] }5.5 官方词表的授权边界现象某次为模拟项目X组装词库时我直接整合了一份某官方机构发布的词表做成随附数据包还没到发布环节就被合作方提醒可能涉及再分发限制。原因单纯看单词和释义属于事实信息但词表经过选择、排序、分级、附注的整理之后整体可以构成有独创性的汇编作品法定授权边界并不宽松。原因背后还有一层词表里隐含了大量人工劳动把它当公共领域数据处理既不符合规则也不公平。解决个人学习自用没有任何问题一旦做成工具分发或商业化改用开放授权的语料与开源词表作为数据来源自行统计高频词并人工分级让原始来源干干净净。开源生态里确实有可用的韩语语料只是要按授权条款逐条核实再使用。6. 导出Anki牌组词库真正进到学习闭环的最后一公里词库整理得再干净不流到学习流程里就是死数据。Anki是自建词库最通用的出口而导出Anki牌组的核心是理解导入格式的约定制表符分隔、UTF-8编码、第一行可以写字段名。常见的做法是生成一个“单词、发音、释义、等级”四字段的TSV文件。def export_anki_tsv(entries, pathkorean_deck.txt, with_exampleFalse): lines [] for e in entries: if with_example and e.get(examples): example e[examples][0][sentence] fields [e[word], e.get(romanized, ), e[meaning], e.get(level, ), example] else: fields [e[word], e.get(romanized, ), e[meaning], e.get(level, )] lines.append(\t.join(fields)) with open(path, w, encodingutf-8) as f: f.write(\n.join(lines))导入Anki后用{{单词}}和{{发音}}{{释义}}{{等级}}做卡片模板五字段版本还能多一个{{例句}}位置。这里有一点值得专门说对韩语这种黏着语带例句的输入型卡片显著优于纯单词卡。学习者看到整句后才点击翻面看到释义能同时吸收单词在真实句子里的接续形态而这一点恰恰是韩语学习中最容易卡住的地方。验证Anki导出是否成功有一个基本功——打开牌组浏览界面按等级排序确认“topik1”字段没有变成乱码、例句里的谚文完整显示就说明TSV编码和字段映射都对了。如果导出后卡面空白多半是字段名与模板变量名没对上检查{{单词}}是否拼写一致即可。我现在的习惯是每次构建完词库先在临时目录里跑一个冒烟测试随机取50条词检查谚文是否都被组合成完整音节、词性字段分布是否合理、CSV用表格软件打开是否正常、例句里有没有未翻译的中文残留。这一套十分钟可以完成却能在发布前挡住绝大多数低级回归。这个词库方案值得做也适合从你手头现有的词表开始逐步迁移过去它真正值钱的部分不在词条本身而在于把词库变成一套可重复构建、可持续维护的流程。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑