
简介《中华字经》是一部以四字韵文形式编排的国学识字篇目涵盖自然地理、历史文化、社会生活、现代科技等多元主题适合幼小衔接识字、语文拓展及传统文化爱好者研读。这份PDF将全文与注释汇编于一体逐段标注字词释义与语义背景便于读者在诵读中理解字义、积累词汇也可供教师直接用于课堂讲析或学生课后自学。资源包为1个PDF文件大小1.05MB轻巧便携支持在手机、平板及电脑端随时翻阅按三大板块系统呈现。已有370人学习下载可帮助学习者通过韵文节奏快速识记常用汉字同时感受汉语音韵之美与传统文化内涵。1. 为什么值得为一本汉字启蒙书的 PDF 写一套解析脚本上个月接到一个“给小学语文课件做字频统计”的小需求翻遍了开源词库都不顺手——要么是通用词频表要么是新闻语料和小学教材的用字场景差了一截。而《中华字经》全文及注释版[汇编].pdf 正好是一份难得的“结构化汉字语料”全文按天文地理、人伦器物、鸟兽虫鱼分类四字一句、押韵易背覆盖常用字面广注释版还把“字 → 释义 → 归类”打包在一起了。如果你只拿它当普通 PDF 读复制粘贴出来的文本会夹杂页码、断行错位、注释混排几乎没法直接用。把“提取 → 清洗 → 入库 → 校验”这套脚本跑通后就能得到一份可复用的 JSON/SQLite 语料库后续做汉字教学、OCR 测试集和前端检索都能接着用。这篇文章不搬运 PDF 内容而是按一线开发的做法把这套解析链路里的工具选型、参数设置和容易踩的坑讲清楚。2. 用 PyMuPDF 把《中华字经》PDF 抽成干净的纯文本2.1 先比较三套工具pdftotext、pdfplumber、PyMuPDF《中华字经》注释版 PDF 属于典型的“双栏排版 页眉页脚 正文与注释混排”转文本时最怕两栏文字被揉在一起、注释行抢在正文前面。常见的三种工具各有侧重我在一个双栏样例页上实测过它们的差异工具中文准确率分栏处理脚本友好度维护状态pdftotext中上依赖字体映射用 -layout 看运气命令行为主适合快速看Poppler 持续维护pdfplumber中上拿 words 坐标自己拼代码多速度慢更新频率一般PyMuPDF高对 CJK 优化好clip sort 可控API 简洁快活跃维护之所以选 PyMuPDF是因为它的get_text()支持按坐标区域裁剪也支持sortTrue按阅读顺序排序且内部对中文字体映射做了大量兼容。pdftotext 的-layout在单栏页面很好用但遇到左右双栏时经常把左栏尾部的一两个字捡到右栏开头pdfplumber 虽然能拿到每个 word 的坐标但要自己拼行、拼列处理注释段落时工作量陡增。所以我一般会先装 PyMuPDFpip install pymupdf然后用一个最小脚本把全文倒出来先看结构再定清洗规则。2.2 最小提取脚本按坐标排序提取正文import fitz # PyMuPDF doc fitz.open(zhonghuazijing_all.pdf) out_lines [] for pno in range(doc.page_count): page doc[pno] # 裁掉上下页眉页脚避免目录页码和注释标题混入正文 top 80 bottom page.rect.height - 60 rect fitz.Rect(0, top, page.rect.width, bottom) txt page.get_text(text, cliprect, sortTrue, flagsfitz.TEXT_PRESERVE_WHITESPACE) out_lines.append(f!-- page {pno1} --) out_lines.append(txt) open(raw_zijing.txt, w, encodingutf-8).write(\n.join(out_lines))fitz.open()打开 PDFpage.rect返回页面尺寸单位是点与分辨率无关。cliprect限制提取范围把页眉页脚挡在区域外。sortTrue让提取结果按阅读顺序输出对单栏排版足够。flagsfitz.TEXT_PRESERVE_WHITESPACE保留原始空白这样换行结构不会因为空行被吃掉而粘连。每页插入一个!-- page N --注释行后面清洗时能定位某一行的来源。参数调整上top80和bottompage.rect.height - 60是经验值。如果页眉里还有“第 X 篇”这种标题就把 top 再加大如果正文底部被裁掉就把 60 改成 40再跑一遍比对。先输出head -n 50 raw_zijing.txt看有没有缺字再批量处理。2.3 左右双栏的注释版怎么切《中华字经》注释版如果排成左右两栏sortTrue并不总能理解“先左后右”它可能按文本块的内部坐标先输出右栏上半部分。这时按中缝把页面切两半分别提取再拼接page doc[pno] W page.rect.width H page.rect.height margin_x 40 # 左右留白 gap 20 # 中缝宽度 top, bottom 80, H - 60 left_rect fitz.Rect(margin_x, top, W / 2 - gap / 2, bottom) right_rect fitz.Rect(W / 2 gap / 2, top, W - margin_x, bottom) left_txt page.get_text(text, clipleft_rect, sortTrue, flagsfitz.TEXT_PRESERVE_WHITESPACE) right_txt page.get_text(text, clipright_rect, sortTrue, flagsfitz.TEXT_PRESERVE_WHITESPACE) if left_txt.strip() and right_txt.strip(): out_lines.append(f!-- page {pno1} two-col --) out_lines.append(left_txt) out_lines.append(right_txt) else: out_lines.append(f!-- page {pno1} full --) out_lines.append(left_txt right_txt)这里的gap和margin_x是关键。gap太小中缝附近的一两个字会串到另一栏gap太大又会把栏内最后一个字吞掉。我的做法是先导出一页 PDF 图片量出正文左右边界和中缝坐标再回头改这两个常量。页码那一行通常落在bottom之外切栏时已经被挡掉了。提示清洗阶段能解决的是文本位置错位和页眉页脚混入。如果 PDF 字体把字形映射到 Unicode 私有区提取出来是方块或乱码后面洗不掉那一页要单独走 OCR 或换字体解析具体兜底方案在第 5 章。2.4 抽取后先看结构再决定清洗规则raw_zijing.txt生成后先不要急着写清洗脚本。用head -n 50或者编辑器打开观察这几类行!-- page 1 -- 第X篇 天文篇 日月星辰 循轨有常 ——日月星辰是指太阳、月亮和星星循轨有常是说…… 雨露霜雪 四时成章 097 第 12 页篇名通常带“篇”或“X篇”正文是四个汉字加空格再四个汉字注释以“——”开头或跟在句后页脚页码很好认。把这几类特征的样例记下来清洗规则才有依据。如果看到类似\ue000的字符说明是私有区乱码在 3.3 和 5.2 会用字频白名单处理。3. 清洗与校验把韵文句子变成一行一条的规范语料3.1 先确定目标结构篇名、正文句、注释行《中华字经》正文是四字一句注释版会在每句后面或页脚给出释义。清洗目标不是把 PDF 里所有文字都保留而是只把“四字正文句”按顺序摘出来同时记录它属于哪一篇。这样后续做字频统计、按篇检索才有意义。抽取后的文本往往是这种混合结构第X篇 天文篇 日月星辰 循轨有常 ——日月星辰是指太阳、月亮和星星循轨有常是说…… 雨露霜雪 四时成章正文句之间的换行是可靠的因为排版就是一行一句。注释行以“——”开头页脚行含“第 X 页”。所以清洗的核心思路是先丢掉整行噪音再校验剩下的行是否符合“四字正文”的形态。常见做法是先把所有行拆开逐行分类而不是上来就用大段正则替换全文。3.2 用两组正则做去噪与断行合并import re def clean_raw(raw: str) - list[str]: # 去掉上一阶段插入的页面标记 raw re.sub(r!-- page \d --, , raw) # 去掉页码行和页脚版权行 raw re.sub(r^.*第\s*\d\s*页.*$, , raw, flagsre.M) # 去掉以 —— 或 — 开头的注释行 raw re.sub(r^[——\-—].*$, , raw, flagsre.M) lines [ln.strip() for ln in raw.splitlines() if ln.strip()] result [] for ln in lines: # 正文是纯四个汉字用 fullmatch 精确过滤 if re.fullmatch(r[\u4e00-\u9fff]{4}, ln): result.append(ln) return resultre.sub(r^.*第\s*\d\s*页.*$, , raw, flagsre.M)用re.M让^和$按行匹配把页码整行删掉。re.sub(r^[——\-—].*$, , raw, flagsre.M)处理注释行时方括号里的——是三个字符外部加^和.*$能把整行注释吞掉。最后re.fullmatch(r[\u4e00-\u9fff]{4}, ln)只保留四位汉字整行不是四字的内容直接丢。这里有个边界要注意如果 PDF 里正文句之间用全角空格分隔比如“日月星辰 循轨有常”那整行不是 4 个汉字fullmatch 会失败。遇到这种情况先把行内空白去掉再做匹配ln_clean re.sub(r[\s\u3000], , ln) if re.fullmatch(r[\u4e00-\u9fff]{4}, ln_clean): result.append(ln_clean)\u3000是全角空格\s覆盖半角空格和换行两个都去掉再匹配正文行就不会漏掉。篇名“第X篇 天文篇”会被这条规则丢弃如果后面需要按篇分组就在 3.4 之前先用^第.*篇把篇名单独抓出来不要和正文混在一起处理。3.3 异体字与繁体字统一注释版 PDF 可能出现“朙”“脩”“臺”这类古字形或繁体写法。如果目标语料是简体常用字这些不统一会影响字频统计和检索命中。我会维护一个很小的映射表在清洗后做一遍替换variant_map { 朙: 明, 脩: 修, 臺: 台, 佈: 布, } def unify_variants(s: str) - str: for k, v in variant_map.items(): s s.replace(k, v) return s映射表怎么确定先跑一遍collections.Counter统计所有出现的字符把不在常用字表里的字挑出来逐个看。几十个字符的规模人工映射比写自动繁简转换更可控。需要注意别把“台”一概替代成“臺”这个映射只针对原文里出现的异体字范围越小越安全。如果搜出来的异常字符落入 Unicode 私有区replace 之前还要先解决编码问题见 5.2。3.4 校验按句长与字频双检查清洗脚本跑完不能只看输出漂不漂亮要用两个硬指标验证句长和字频。from collections import Counter def validate(lines: list[str]) - None: bad_len [(i, ln) for i, ln in enumerate(lines) if len(ln) ! 4] counter Counter(.join(lines)) print(总句数:, len(lines)) print(总字数:, sum(counter.values())) print(不重复字数:, len(counter)) print(非四字句示例:, bad_len[:10]) validate(clean_lines)句长校验抓的是漏网的注释行或错切的中缝。字频校验用来判断覆盖度《中华字经》通说全文四千余字处理完若“不重复字数”只有一千多多半是清洗时把大量句子误删了若出现大量生僻字则可能是字体映射问题。除此之外还可以用一个高频字集合做交集比对把“的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞” 做差集缺失多的就回头查清洗规则。检验结果里如果出现非四字句看两种典型情况异常类型可能原因处理方式行为三个字篇名被误判进正文提前用^第.*篇抓篇名并移除行为五个字以上注释行没删净检查注释开头是否为全角破折号补正则字符是方块或乱码字体映射到私有区走 5.2 的私有区兜底总字数远低于预期中缝裁掉了字符把 2.3 的 gap 调小重新抽取4. 结构化入库与检索给《中华字经》加索引并秒查某个字4.1 JSON 还是 SQLite数据量决定实现方式《中华字经》正文约四千字四字一句约一千句。这个量级用 JSON 文件几 KB 就能放下前端直接 fetch 也很快。但注释版带释义、篇目、拼音后字段变多检索需求也会变多比如“查一个字出现在哪些句子里”“按篇目导出字频”这时 SQLite 更顺手。我的选择标准很简单只要有两类以上的检索维度就用 SQLite只有纯展示JSON 够了。先设计最常用的字段字段类型说明idINTEGER PRIMARY KEY自增主键chapterTEXT篇名seqINTEGER在篇内序号sentenceTEXT四字正文full_textTEXT该句四字便于 LIKE 检索full_text和sentence在数据上是重复的但建索引时直接对full_text建索引比sentence更明确后边查询能少写一层转换。4.2 把清洗后的语料转成 JSON 并落库清洗结果是一行一句的clean_lines写库前先把篇目信息补上。如果之前没有抓篇名可以用一个简单的规则遇到“第X篇”标记时切换当前章节后续句子都归属这个章节。import sqlite3 def init_db(db_path: str) - sqlite3.Connection: conn sqlite3.connect(db_path) conn.execute(DROP TABLE IF EXISTS zijing) conn.execute( CREATE TABLE zijing ( id INTEGER PRIMARY KEY, chapter TEXT, seq INTEGER, sentence TEXT ) ) conn.commit() return conn def insert_items(conn: sqlite3.Connection, items: list[tuple[str, int, str]]): conn.executemany( INSERT INTO zijing (chapter, seq, sentence) VALUES (?, ?, ?), items, ) conn.commit()executemany批量插入第二个参数是一个元组列表每个元组对应(chapter, seq, sentence)。seq用该句在当前篇内的序号从 1 开始。chapter为空字符串时说明篇名规则没有匹配到此时需要回头在 3.4 之前抓取篇名而不是让数据带空章节入库。落完库后顺手导出一份 JSON方便前端直接用import json with open(zijing.json, w, encodingutf-8) as f: rows conn.execute(SELECT chapter, seq, sentence FROM zijing ORDER BY id).fetchall() data [{chapter: r[0], seq: r[1], sentence: r[2]} for r in rows] json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse让 JSON 保持中文明文检查 diff 时一眼能看出内容问题。4.3 检索示例查一个字返回它所在的句子、篇目和序号数据量只有一千句时LIKE 检索足够快不需要上 FTS5。下面这个命令行脚本可以查一个字import sqlite3 import sys def search(word: str): conn sqlite3.connect(zijing.db) rows conn.execute( SELECT chapter, seq, sentence FROM zijing WHERE sentence LIKE ? ORDER BY chapter, seq, (f%{word}%,), ).fetchall() for chapter, seq, sentence in rows: print(f[{chapter or 未分篇}·{seq:3}] {sentence}) if __name__ __main__: search(sys.argv[1])查询语句里使用参数占位符?传入值为f%{word}%避免拼接 SQL。这里可以加一个CREATE INDEX idx_zijing_sentence ON zijing(sentence);虽然千行数据不建索引也能跑但后续如果要扩展到五万多字的古籍语料索引的优势就出来了。输出格式里seq:3是把序号右对齐日志或终端输出时更整齐。4.4 生成“字 → 出处”映射教学场景里常见需求是“点这个字看它在全书哪些句子里”。这句 SQL 查出来的结果可以直接转成映射def build_char_mapping(conn: sqlite3.Connection) - dict[str, list[tuple[str, int, int]]]: cur conn.execute(SELECT chapter, seq, sentence FROM zijing) mapping: dict[str, list[tuple[str, int, int]]] {} for chapter, seq, sentence in cur: for pos, ch in enumerate(sentence): mapping.setdefault(ch, []).append((chapter, seq, pos)) return mappingmapping的键是单个汉字值是(篇目, 句序号, 字在句内位置)的列表。导出成char_index.json后前端输入一个“明”字直接拿 JSON 查就能返回它在哪些句子、哪些位置出现。这个结构也可以用来做字频表len(mapping[ch])就是某个字的总出现次数。5. 排错清单与两个进阶玩法5.1 扫描件先过 OCR参数决定准确率如果 PDF 是扫描版get_text提取出来的是空白或乱码。先把页面转成 PNG再用 Tesseract 中文语言包识别tesseract scanned_page.png stdout -l chi_sim --psm 6--psm 6表示把整页当作统一文本块适合排版规整的正文带注释的页面改用--psm 4按列识别再用第 2 章的中缝裁剪逻辑切图分别 OCR。PaddleOCR 对中文小字注释的识别率更高但模型下载和依赖安装重一些批量跑之前先拿三页试效果。5.2 Unicode 私有区乱码的兜底字体映射错误时提取结果里会出现\ue000到\uf8ff区间的字符。用这个脚本检测def check_private(line: str) - list[str]: return [hex(ord(c)) for c in line if 0xE000 ord(c) 0xF8FF]检测结果如果只有少量字符人工映射到正确汉字后替换如果大量出现说明字体 cmap 表被破坏清洗救不回来只能对受影响页面走 OCR。5.3 进阶玩法生成 Anki 卡组与篇目字频曲线把库里的句子导出成 CSV可以直接导入 Anki 做字词卡片sqlite3 -header -csv zijing.db \ SELECT sentence, chapter FROM zijing ORDER BY chapter, seq anki_cards.csv每行是一条卡片正面是四字句背面是篇目信息。配合 TTS 朗读插件背诵复习的闭环就成型了。按篇目统计不重复字数的走势能看出学习量随篇目推进的坡度import sqlite3 from collections import Counter conn sqlite3.connect(zijing.db) seen set() for chapter, sentence in conn.execute( SELECT chapter, sentence FROM zijing ORDER BY seq ): new_chars set(sentence) - seen seen.update(sentence) print(chapter, len(new_chars), len(seen))输出每一篇的新增字数和累计字数画出来就是字频曲线。课程设计时可以据此调整每篇的教学时长。5.4 覆盖率自测用高频字集合核对清洗结果清洗是否漏字用高频字交集检验最直观from collections import Counter actual Counter(.join(clean_lines)) high_freq set(open(high_freq_200.txt, encodingutf-8).read().strip()) missing high_freq - set(actual) print(高频字缺失:, len(missing), 个) print(缺失样例:, sorted(missing)[:20])把这段存成check.py每次清洗完语料跑一遍缺失数量明显下降再继续下一步。如果某次调整清洗规则后高频率字缺失数反而上升说明正则把正文误删了要回到 3.2 检查匹配范围。本文还有配套的精品资源点击获取