LCMC中文语料库实战:词频统计、文体对比与SQLite检索

发布时间:2026/10/13 22:38:10
LCMC中文语料库实战:词频统计、文体对比与SQLite检索 简介兰卡斯特汉语语料库LCMC是一份约100万词次的现代汉语书面语通用型平衡语料库资源面向语言学研究者、语料库方法学习者以及自然语言处理从业者可用于英汉时体对比研究、汉语词类标注观察、语体分布分析与语料检索实践对需要免费公开语料库进行课题设计或论文写作的读者尤其适用。压缩包共71个文件主体为31个xml语料文本和39个htm说明/索引页面另含1份doc格式的LCMC数据手册便于查看语料结构、标注规范与使用说明资源包仅6.34MB轻量易用。现有3466人下载学习适合以此为起点开展汉语平衡语料库相关训练。文件按语料类别组织xml中保留原始文本与标记信息htm页面可快速浏览语料分布doc手册则为使用者提供了字段与格式指引能明显降低上手成本。1. 百万字级现代汉语平衡语料库LCMC 能做什么做中文词频统计或文体对比时最尴尬的不是算法写不出来而是拿不出让人信服的语料。临时从网上抓几百篇文章来源杂、篇幅不均、文体混在一起结论很难站得住脚。LCMC 正是一份能直接拿来用的现代汉语平衡语料库总量约一百万汉字覆盖新闻、社科、公文、小说等十五类文体按固定配额取样多数发行版还自带分词与词性标注。对语言研究者、对外汉语教师、NLP 初学者来说拿到它等于把最耗时的建库环节直接跳过。它解决的核心问题是给结论配数据、给课程配例句、给模型配干净的训练文本。2. 库的底细从解压到摸清文件构成2.1 解压之后先认目录文件命名里藏着文体分类我拿到这类语料库时有个习惯先不急着写检索脚本而是把压缩包解压后完整看一遍目录。LCMC 的文件结构接近经典语料库的惯例文体用大写字母表达A 到 P 按顺序排中间跳过 O每类下放几十个纯文本文件文件名像 A01.txt、B05.txt 这样第一个字母就是文体代码。很多二次发布的压缩包还会套一层子目录不先认目录后面统计文体分布时容易漏掉一半文件。第一步不是打开文本而是用命令行盘一下整体unzip LCMC_release.zip -d lcmc find lcmc -type f -name *.txt | wc -l find lcmc -maxdepth 2 -type d -print第一行把下载的压缩包解压到 lcmc 目录建议用纯英文路径省得后续 Python 脚本在 Windows 上遇到中文路径编码问题。第二行统计解压后所有 txt 文件数量如果结果跟发布说明里的篇目数对不上多半是压缩包没解完整或者里面还藏着二次压缩的子包。第三行只列两层目录确认每个文体目录存在目录名是字母还是中文不重要文件名规则才是关键。为什么文件名这么重要因为后续按文体做对比统计时程序都靠文件名首字母判断类型而不是读目录名。我一般还会顺手查一下文件大小的分布把异常小的文件挑出来find lcmc -name *.txt -exec ls -l {} \; | awk {print $5, $NF} | sort -n | head -20这一行把每个文件按字节数从小到大排序只显示最小的 20 个。正常一篇 LCMC 样例文本大约两千汉字左右对应文件体积应该在几 KB 以上。如果看到 0 字节或几十字节的文件那基本都是空文件或下载损坏要尽早从分析清单里剔除不然统计词频时会多出一堆 NaN 或空行问题。2.2 十五类文体配比先看清样本是否均衡LCMC 的设计参考了经典语料库的取样框架十五类文体各有配额。整理一份对应关系很有必要后面做文体对比时直接按这个映射给代码加注释。文体代码类型文体代码类型A新闻报道J报告与公文B新闻评论K一般小说C新闻特写L武侠小说D宗教文献M侦探小说E商业与贸易N科幻小说F社科学术文章P爱情小说G自然科学文章H通俗读物I传记与杂文为什么我要先列这张表因为很多初学者想当然地认为语料库只是“一堆中文文本”直接对着全库统计词频完全不区分文体。可新闻和小说的高频词完全不同混在一起统计会掩盖掉最有价值的文体差异。而 LCMC 这类平衡语料库的价值恰恰在于它的配比经过设计可以直接支撑文体间的横向比较。2.3 Python 全库体检文件数、总字数与分布统计目录确认无误后我习惯跑一个 Python 脚本把整库的数据量摸一遍。这个脚本输出的结果就是后续所有分析的底表。# -*- coding: utf-8 -*- import os import glob corpus_dir lcmc category_map { A: 新闻报道, B: 新闻评论, C: 新闻特写, D: 宗教文献, E: 商业贸易, F: 社科论文, G: 自然科学, H: 通俗读物, I: 传记杂文, J: 报告公文, K: 一般小说, L: 武侠小说, M: 侦探小说, N: 科幻小说, P: 爱情小说 } summary {} total_chars 0 for path in glob.glob(os.path.join(corpus_dir, **, *.txt), recursiveTrue): fname os.path.basename(path).upper() cat fname[0] with open(path, r, encodingutf-8, errorsignore) as f: content f.read() char_count len(content) summary.setdefault(cat, {files: 0, chars: 0}) summary[cat][files] 1 summary[cat][chars] char_count total_chars char_count for cat, stat in sorted(summary.items()): print(cat, category_map.get(cat, 未知), stat[files], stat[chars]) print(TOTAL, total_chars)glob.glob(..., recursiveTrue)会递归捕获所有子目录下的 txt 文件避免二层目录丢失os.path.basename取出文件名后取第一个字符作文体代码setdefault则为每个文体初始化计数器省去手动判断字典键是否存在的麻烦。errorsignore是先忽略个别解码失败的字符保证统计口径统一编码问题后面专门处理。跑完这个脚本如果发现某个文体只有二十几篇而其他文体四十篇说明压缩包不完整或下载过程丢文件就要回源头重新获取。我见过有人拿着缺失版的语料库做了半个月词频分析最后发现少了一整个文体所有对比结论都作废那感觉确实不好受。提示统计结果别只看终端输出建议重定向保存一份例如在命令尾部加 corpus_report.tsv方便后面核对或写进论文附录。3. 编码与标注格式动手分析前先清洗3.1 编码识别为什么文件打开全是乱码这类学术语料库发布时对编码的态度比较随意。有的压缩包按 UTF-8 存储有的在 Windows 里被重新保存过变成 GB18030还有少数文件头部残留 UTF-8 BOM。在编辑器里打开一切正常换到 Python 的open()就抛UnicodeDecodeError这就是编码不统一最直观的信号。我拿到文本后第一步永远是全库编码检测而不是直接按 UTF-8 硬读。# -*- coding: utf-8 -*- import glob def detect_encoding(path): with open(path, rb) as f: raw f.read(4096) if raw.startswith(b\xef\xbb\xbf): return utf-8-sig if raw.startswith(b\xff\xfe): return utf-16 try: raw.decode(utf-8) return utf-8 except UnicodeDecodeError: try: raw.decode(gb18030) return gb18030 except UnicodeDecodeError: return unknown files glob.glob(lcmc/**/*.txt, recursiveTrue) enc_counts {} for path in files: enc detect_encoding(path) enc_counts[enc] enc_counts.get(enc, 0) 1 print(enc_counts)函数先读文件前 4096 字节按 BOM 头快速判断 UTF-8-SIG 或 UTF-16再依次尝试 UTF-8 和 GB18030。GB18030 是中文 Windows 上最常见的本地编码兼容 GBK连生僻字都能覆盖作为兜底方案非常可靠。返回unknown的文本基本可以认定损坏直接加入黑名单。为什么只读前 4096 字节因为编码检测不需要读完整文件这一段足以区分绝大多数情况。有一种例外文件开头是大段英文说明中间才出现中文此时前半段 ASCII 能用 UTF-8 解、后半段却需要 GB18030这种情况不常见真遇到了不要自己修重新找完整版本拷贝更省事。3.2 标注格式识别分词标注版和纯文本版用法完全不同LCMC 的流通版本大致有两种形态。一种是纯汉语文本适合检索和阅读另一种是带词性标注的版本适合语法研究。带标注的行看起来像“我们/r 的/u 语料库/n”也就是词、分隔符、标注码连在一起。但不同整理者的分隔符不一样有人用斜杠有人用 Tab 键还有人用双斜杠。写解析脚本前先抽几个文件看格式比直接套正则靠谱import glob samples glob.glob(lcmc/**/*.txt, recursiveTrue)[:5] for p in samples: with open(p, r, encodingutf-8, errorsignore) as f: lines [l for l in f.read().splitlines() if l.strip()][:3] print(---, p) for ln in lines: print(ln[:120])这段代码只读取前五个文件每个文件显示前三行非空内容每行前 120 个字符。输出里如果频繁出现“/v”“/n”这类后缀说明文件是词性标注版如果句子干净通顺则是纯文本版。还有一种较老的格式用 Tab 分隔词和标注看起来是“今天\t时间词”这种后续解析要单独兼容。为什么先区分版本因为词频统计的技术路线完全不同。标注版可以直接按词和标注切开统计得到的是基于原分词结果的词频纯文本版则需要引入切词工具重新切分两条路线清洗逻辑不一样混在一起处理等统计词性时才发现格式不统一返工成本非常高。提示压缩包里通常有 readme 或说明文件里面会写明标注体系先读 readme 永远比靠自己猜格式更快。3.3 清洗规则去掉页眉、页码和标点噪音语料库数字化时通常保留原始出版物的页眉页脚、页码和版权行。这些内容出现在检索结果里非常碍眼比如查“的”字句时会带出“第 3 页”“资料来源某某报”这类不完整片段。清洗阶段要把它们过滤掉之后再进入检索或统计流程。# -*- coding: utf-8 -*- import re def clean_corpus_text(text): # 去除页眉页脚常见模式 lines text.splitlines() lines [ln.strip() for ln in lines if ln.strip()] lines [ln for ln in lines if not re.search(r第\s*\d\s*页, ln)] lines [ln for ln in lines if not re.match(r^[A-Za-z\s]{20,}$, ln)] # 压缩空白与统一换行 body \n.join(lines) body re.sub(r[ \t], , body) return body.strip()re.search(r第\s*\d\s*页, ln)用于匹配“第 3 页”“第十二页”这类页码行re.match(r^[A-Za-z\s]{20,}$, ln)过滤掉整行超过 20 个英文字母的纯英文行因为 LCMC 的样例文本头尾常混入来源英文说明。最后把行内所有空格和 Tab 压掉统一成紧凑文本。这一步的参数需要按实际语料微调。比如“第X页”的匹配规则对“第 12 页/共 40 页”这种格式有效遇到“- 12 -”这种页码格式就得改用r[-—\s]*\d\s*$去匹配行尾数字。所以我一般会把清洗函数写成可以传入正则的版本而不是写死规则方便不同语料版本共用。4. 检索与词频统计把语料库变成分析工具4.1 关键词检索与例句导出按文体提取语料证据语料库最常用的功能是提取例句。比如想研究“把”字句在新闻和小说中的分布差异需要把所有包含“把”的句子按文体归类。直接翻文本文件很低效我一般写一个检索函数把结果输出成 CSV方便在表格软件里筛选。# -*- coding: utf-8 -*- import os, glob, re, csv corpus_dir lcmc target 把 def clean_sentence(text): text re.sub(r\s, , text) return text def search_corpus(corpus_dir, target, limit_per_cat50): out [] for path in glob.glob(os.path.join(corpus_dir, **, *.txt), recursiveTrue): fname os.path.basename(path).upper() cat fname[0] with open(path, r, encodingutf-8, errorsignore) as f: content clean_sentence(f.read()) for sent in re.split(r[。!?], content): if target in sent: out.append([cat, fname, sent]) return out rows search_corpus(corpus_dir, target) with open(search_ba.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([文体, 文件名, 例句]) writer.writerows(rows) print(total:, len(rows))clean_sentence先把所有空白字符压掉避免句子因为换行被拦腰截断。re.split(r[。!?], content)按中文句末标点切句英文问号感叹号也一并处理兼容混排文本。输出用utf-8-sig编码是为了让 CSV 在 Windows 的表格软件里直接打开不乱码这个小细节很多人容易忽略。limit_per_cat参数控制每类最多收集多少条防止输出文件过大。检索词“把”只是示例实际使用时可以换成任意目标词或短语。如果想做更精确的结构检索比如“把NV”句式就需要在切句后加一层正则匹配那已经属于另一套语法匹配方案。4.2 高频词统计纯文本版的分词与停用词设置词频统计是语料库的看家本领。拿到标注版时可以直接利用现成的分词结果把词和标注切开后统计拿到纯文本版时则需要用jieba这类工具补做切分。下面的示例按纯文本版处理这样代码换到其他中文语料也能复用一个流程。# -*- coding: utf-8 -*- import glob, os, collections import jieba # 最小停用词集实际使用时建议用独立停用词表文件 stopwords set(的了是在有和就不人都一这上中很也我你他她它们说为与及被中.strip()) def tokenize_files(corpus_dir): counter collections.Counter() for path in glob.glob(os.path.join(corpus_dir, **, *.txt), recursiveTrue): with open(path, r, encodingutf-8, errorsignore) as f: content f.read() words jieba.cut(content) for w in words: w w.strip() if len(w) 1: continue if w in stopwords: continue counter[w] 1 return counter counter tokenize_files(lcmc) for w, c in counter.most_common(30): print(w, c)代码的关键点是stopwords。中文高频词一大半是“的、了、是、在”这类功能词不提前过滤“的”会毫无悬念地排在第一位分析价值几乎为零。len(w) 1筛掉单字词理由很多单字在汉语中要么是虚词素、要么语义不独立先拿掉能减少低频噪声。实际项目中停用词量至少几百个建议单独存成文本文件不要每次在代码里维护。jieba.cut(content)默认使用精确模式适合词频统计场景。如果语料库本身已经分词就不要再用jieba二次切分否则分词结果可能与原始标注不一致引入额外误差。4.3 文体对比用词频差异识别新闻和小说的用语特征词频统计跑完下一步通常是比较文体差异。最直接的做法是分别统计两个文体的高频词看哪些词的频次差异最大。# -*- coding: utf-8 -*- import os, glob, re, collections def freq_for_category(root, cat): counter collections.Counter() for path in glob.glob(os.path.join(root, **, *.txt), recursiveTrue): fname os.path.basename(path).upper() if fname[0] ! cat: continue with open(path, r, encodingutf-8, errorsignore) as f: text f.read() words re.split(r[\s/], text) for w in words: if len(w) 1 or w in stopwords: continue counter[w] 1 return counter A freq_for_category(lcmc, A) K freq_for_category(lcmc, K) common set([w for w, _ in A.most_common(100)]) set([w for w, _ in K.most_common(100)]) for w in list(common)[:20]: print(w, A[w], K[w])这个示例假设文本已是分词状态所以用re.split(r[\s/], text)按空白或斜杠切词。A对应新闻报道K对应一般小说两边各取前 100 高频词后求交集再逐个打印在两个类别里的原始频次。如果某个词在 A 类里出现 300 次、在 K 类里只有 20 次就是明显的文体标记词。更严格的对比应该换算成标准频次比如每十万词出现多少次以消除两边语料量差异的影响。原始频次只能看相对关系要写进论文或报告建议加一步归一化处理。这一步也可以换成计算对数似然比或互信息但原理都是从相对频率差异里找出特征词。5. 避坑LCMC 使用中绕不开的五个典型坑下面五条都是实际使用这类语料库时容易翻车的地方按现象、原因、解决三步记录。5.1 文件打开全是乱码现象用 Python 读某个文本打印出来全是“锟斤拷”或者一大堆替换字符但用系统记事本打开却显示正常。原因编码不统一。老版本在大陆 Windows 环境下保存成了 GB18030而脚本默认按 UTF-8 解码自然出错也有少数文件头部被加了 UTF-8 BOM干扰解码判断。解决按 3.1 节的检测流程先跑一遍全库按检测结果分类。对已经乱码的文件写一个自动转换脚本统一转成 UTF-8 另存到新目录原文件保留不动等文件数量核对一致后再清理旧包。不要手工逐个文件修复既慢又容易改坏。5.2 不同文件的标注格式不一致现象同一个压缩包里A 类文本的标注符号是“词/标注”K 类文本却是“词\t标注”少数文件甚至完全没有标注。原因语料库经过多轮整理和网络转载不同时期不同来源的版本格式发生漂移。发布者往往只保证内容完整不保证格式统一。解决先按 3.2 节的抽样式检查做格式普查统计出全库到底有几种格式再写一个归一化函数。常见做法是把斜杠分隔和 Tab 分隔统一成同一种分隔符我再把没有标注的文件单独拎出来要么重新补标注要么在统计时排除并写明数量。千万别默认全库格式一致。5.3 检索结果混入页眉页脚和来源说明现象检索“把”字句时导出的例句里频繁出现“第 1 页”“资料来源某某编辑部”这类不完整片段。原因语料数字化时保留了原始出版物的页眉页脚、页码和版权行切句逻辑无法区分正文和附属信息把它们当成普通句子切开了。解决在检索前先用 3.3 节的清洗函数剥掉文件头尾与页码行。我习惯把清洗函数接到search_corpus的读取环节里保证后续所有查询拿到的都是干净文本。如果清洗后仍有个别噪声就再加一条正则把“资料来源”“责任编辑”这类特征词过滤掉。5.4 繁简混杂影响词频统计现象统计“计算机”时词频表里同时出现“计算机”和“計算機”两个词都排在中等频次合并前看不出是同一个词。原因语料来源横跨不同年代的出版物早期文本可能是繁体转简时不彻底个别字符保留繁体发行方一般不做全局繁简归一。解决统计前做一次繁简转换。我一般用 opencc 这类库的t2s配置把繁体转简体不要用简单字符串替换因为“發”“髮”“发”这类字有上下文歧义工具库会按语境处理。转换后重新跑词频高频词表中的重复条目会明显减少。提示LCMC 主要使用简体中文繁简混杂通常只出现在个别文本里按文件处理即可不用全局重转。5.5 Windows 下二次保存导致文件损坏现象在 Windows 系统里用记事本打开某个语料文本顺手按了快捷键保存。之后脚本读取该文件时固定在某一行中断或者输出一堆替换字符。原因记事本默认保存编码可能与原文件不一致加了 BOM 或直接改了编码。更隐蔽的是原文件是 LF 换行符保存后变成 CRLF切句正则里的换行假设失效。解决处理语料期间把语料目录设为只读所有中间结果输出到另一个目录。必须编辑原文件时用能显示编码的编辑器打开保存时显式选择 UTF-8 无 BOM并保持原有换行符风格不变。这条坑很隐蔽一旦中招往往要重下原始文件。6. 进阶把常用检索固化成一个 SQLite 小工具如果只是临时查一两个词每次写脚本检索没问题。但真正做研究或备课检索是高频动作今天查“把”明天查“被”后天统计“了”和“着”的分布频繁重读几百个文本文件效率太低。我习惯把 LCMC 预处理成一份 SQLite 数据库以后查例句、统计词频都不再碰原始文件随取随用。# -*- coding: utf-8 -*- import sqlite3, glob, os db sqlite3.connect(lcmc.db) db.execute(DROP TABLE IF EXISTS docs) db.execute(CREATE TABLE docs (id TEXT PRIMARY KEY, cat TEXT, content TEXT)) for path in glob.glob(lcmc/**/*.txt, recursiveTrue): fname os.path.basename(path).upper() cat fname[0] with open(path, r, encodingutf-8, errorsignore) as f: content f.read() db.execute(INSERT OR REPLACE INTO docs VALUES (?,?,?), (fname, cat, content)) db.commit() print(done)docs表只有三个字段文件名、文体代码、全文。INSERT OR REPLACE的作用是导入遇到重名文件时自动覆盖适合语料版本更新后重新导入。数据库文件生成后只有几十 MB后续操作不再反复读原始目录。导入完成后按文体查关键词一条 SQL 就能解决sqlite3 lcmc.db SELECT cat, id, substr(content, instr(content, 把), 30) FROM docs WHERE content LIKE %把% LIMIT 20;instr(content, 把)找到关键词在文本中的位置substr截取前后共 30 个字符返回一段可读的上下文而不是整篇文本。LIKE %把%在百万字规模上扫描速度对个人研究足够快。想查“被”字句直接把两个“把”换成“被”就能复用。从那以后每次接到语料处理需求我都强制走一遍固定流程先看文件数与编码再抽查标注格式清洗后抽三个文件做人工复核最后导入 SQLite 库。这套流程最多花二十分钟但帮我挡掉了后面至少一周的返工。整理语料库这件事耐心比技术更值钱希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询