ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python生成千字文拼音版PDF:多音字注音、拼音对齐与可检索化

Python生成千字文拼音版PDF:多音字注音、拼音对齐与可检索化 简介这份《千字文》拼音版全文附解释PDF面向国学启蒙、汉字学习及古代文化爱好者也适合家长与语文教师用作诵读和讲解材料。它把南朝梁周兴嗣所编的千字韵文逐字标注拼音并配有白话译文方便读者在认读生僻字的同时理解“天地玄黄宇宙洪荒”“推位让国有虞陶唐”等句背后的天文、历史、文学与道德内涵。资源包仅含1个PDF文件大小约100KB轻量易存可直接在手机、平板或电脑上翻阅正文以四字一句、两句一韵的方式编排译文紧随原句便于对照诵读和摘抄。目前已有131人学习下载。借助拼音与解释的对应排版读者既能纠正读音、积累文言词汇也能顺着“三、百、千”的启蒙脉络系统了解古代宇宙观、禅让典故、儒家仁政与修身格言为后续阅读蒙学经典、练习书法或开展课堂教学提供便利。1. 千字文拼音版全文附解释文件.pdf一份 PDF 背后的三个硬问题搜索「千字文拼音版全文附解释文件.pdf」的人多半不是想随便下载一个文件而是在做儿童识字 App 的离线阅读包、语文教辅的电子化排版或者把纸面教材转成可检索资源的批量管线。千字文正好是个麻烦样本一千个汉字不同版本存在个别重复字以实际版本为准、四言成句、几乎句句带典故字音里还夹着一串多音字。真正动手做这份 PDF卡点不在排版本身而在三件事逐字注音怎么保证正确拼音和汉字怎么严格对齐生成出来的文件能不能被复制、被检索、被回读校验。任何一环松掉结果就是一份看着漂亮、搜起来全是乱码的 PDF——教辅发出去家长一搜「天地」搜不到问题就大了。下面按数据、拼音、排版、质检四段推进代码用 PythonPDF 用 ReportLab 出命令能直接复现。2. 千字文全文结构化250 句四言切成可查询的字表数据一份「千字文拼音版全文附解释文件.pdf」能不能稳定复现取决于源头数据是不是结构化的。把整段文字直接丢给排版引擎注音、解释、页码全靠肉眼对改一次崩一次。先拆成三张表字表、句表、联表后面每一步都从这三张表取数改版式不动数据。2.1 原文清洗与四言切分的最小脚本先是清洗。千字文的原始文本往往夹杂全角标点、换行、注释方括号甚至有从网页复制带的零宽字符这些必须在切分前干掉。切分本身很简单四言是固定节律按 4 字步长切就够。import re from collections import Counter RAW 天地玄黄宇宙洪荒。日月盈昃辰宿列张。 寒来暑往秋收冬藏。闰余成岁律吕调阳。 云腾致雨露结为霜。金生丽水玉出昆冈。 # 只保留汉字同时兼容扩展 A 区的生僻字与异体字 HANZI re.compile(r[\u4e00-\u9fff\u3400-\u4dbf]) chars HANZI.findall(RAW) # 四字一句千字文绝大多数是四言按固定步长切 sentences [.join(chars[i:i 4]) for i in range(0, len(chars), 4)] # 两句一联页面上按联排一行读起来更接近传统版式 couplets [.join(sentences[i:i 2]) for i in range(0, len(sentences), 2)] print(len(chars), sentences[0], couplets[0])逻辑说明HANZI用了两段 Unicode 区间比只写[\u4e00-\u9fff]稳遇到「昃」这类字不会漏遇到版本差异里的异体字也不会被当成标点丢掉。切句用固定步长是刻意的——千字文没有长句一旦切出来最后一段不足 4 字说明源文件里混进了标题、脚注或页码这时应该回去改源文件而不是在代码里加容错逻辑把问题掩盖掉。参数说明步长 4 是文本特征不是可调项range(0, len(chars), 4)对不足 4 字的尾部会保留成短句配一条断言就能抓出来。assert len(chars) 1000, f原文共 {len(chars)} 字检查是否混入标题或注释2.2 用 Counter 校验「千字不重复」到底是几句真话千字文常被称作一千个汉字不重复但传世版本在异体字上有出入实做中真跑出重复字是常态。与其信说法不如让 Counter 给一份清单。c Counter(chars) dup {ch: n for ch, n in c.items() if n 1} print(总字数, len(chars)) print(去重后, len(set(chars))) print(重复字, dup)说明dup不是报错是待确认清单。如果重复的是「洁」「絜」这类版本差异字按你选定的底本统一即可如果重复的是「之」「其」这种常用字基本可以断定原文粘错或漏删了某段注释。把这一步固化进脚本每次换底本重跑一遍比人工核对一千个字靠谱。数据结构上字表按字存字段设计如下字段名类型示例用途seqint1全文序号1 到 1000charstr天单字pinyinstrtiān带调主读音pinyin_altstrtiān多音字候选逗号分隔sentence_idint1所属句序号sentencestr天地玄黄所在四言句coupletstr天地玄黄宇宙洪荒所在联meaningstr天与地苍茫玄黄整句解释2.3 字表落成 SQLite解释文件才有地方挂解释文件通常是按句给的一句一段释义偶尔带典故出处。字表必须能承接这份解释所以表的主键是字序号释义字段冗余挂在每个字上查询时按sentence_id去重即可。CREATE TABLE IF NOT EXISTS qzw_char ( seq INTEGER PRIMARY KEY, -- 全文序号从 1 开始 char TEXT NOT NULL, pinyin TEXT NOT NULL, -- 带调主读音 pinyin_alt TEXT DEFAULT , -- 备选读音逗号分隔 sentence_id INTEGER NOT NULL, -- 所属句号 sentence TEXT NOT NULL, couplet TEXT NOT NULL, meaning TEXT DEFAULT -- 整句解释按句冗余挂载 ); CREATE INDEX IF NOT EXISTS idx_qzw_char ON qzw_char(char); CREATE INDEX IF NOT EXISTS idx_qzw_sentence ON qzw_char(sentence_id);说明更规范的做法是拆成字表和句表两张导出 PDF 时多一次 join。单人维护的项目一般没必要冗余一份释义反而让导出脚本更短但如果你要同时出「按字检索」和「按句检索」两个入口老实拆表别在 SQL 里写字符串拼接。解释文案的来源要留意能自己写的尽量自己写引用现成教辅内容做商业发布是有风险的很多团队栽在这一点上。2.4 入库前后的一致性检查结构化最怕「看着对」所以给数据加断言。这份检查函数后面还会复用两次拼音生成完跑一遍PDF 生成完再跑一遍。def check(rows): assert len(rows) 1000, f字数不对: {len(rows)} assert [r[seq] for r in rows] list(range(1, 1001)), 序号不连续 for r in rows: assert len(r[char]) 1, f非单字: {r[char]} assert r[pinyin].strip(), f缺拼音: {r[char]} assert r[sentence_id] 1 print(检查通过)序号连续这一条比字数检查更值钱字数对得上但序号有断点说明中间有重复写入或删行PDF 里会出现同一个字排两次。3. pypinyin 标注千字文拼音多音字、声调与逐字对齐到了注音环节问题从「数据长什么样」变成「读音对不对」。千字文的字不算生僻但多音字密度不低而且古人用字和现代汉语的默认读音经常打架靠一个拼音库直接跑一定会有错。3.1 pypinyin 的基础调用与四个必调参数pip install pypinyinfrom pypinyin import pinyin, Style print(pinyin(律吕调阳, styleStyle.TONE)) # [[lǜ], [lǚ], [diào], [yáng]] 注意最后这个「调」单字默认读音在这里就翻车了。参数怎么设直接决定后面要不要人工返工参数常用值作用千字文场景建议styleStyle.TONE带调符号tiān排版正文用它styleStyle.TONE3音节后加数字tian1做检索键、排序键用它heteronymFalse只返回一个读音生成正文用它heteronymTrue返回全部候选读音生成复核清单用它errorsignore非汉字直接丢弃与汉字数组对齐时用它errorsdefault保留原字符需要保留标点时用它strictTrue关闭上下文变调逐字对齐时建议先关说明strict拼音库默认会对「一」「不」这类字做隐式变调教学注音一般标本调而逐字对齐时更希望读音来源可控所以先把变调关掉。真要标变调在后处理阶段自己按规则改别让它悄悄改掉你的数据。3.2 千字文里最容易标错的几类多音字下表是实做中反复踩到的几处处理思路是一致的判断这个字在本句里做什么成分再定读音理由必须写进覆盖表半年后回来看还能改。上下文字单字默认读音应读判断依据律吕调阳调diàotiáo调和阴阳动词乐殊贵贱乐lèyuè与「礼」对举指乐律盖此身发发fāfà与「肤」连用指头发率宾归王率lǜshuài使归附动词女慕贞洁女nǚnǚ无需改留作对照盖此身发盖gàigài发语词声调不变注意最后一列表里既要写「错的」也要留几个「对的」当对照否则过几个月没人敢确定这份覆盖表是不是整体改偏了。3.3 用 load_phrases_dict 覆盖整句读音拼音库提供了两级干预入口词组级优先级最高命中整句就逐字照搬不会再有上下文猜测。from pypinyin import load_phrases_dict, load_single_dict, pinyin, Style # 单字兜底千字文里「发」只作「头发」讲默认读音改成 fà load_single_dict({ord(发): fà,fā}) # 整句覆盖优先级高于单字和内置词典 load_phrases_dict({ 律吕调阳: [[lǜ], [lǚ], [tiáo], [yáng]], 盖此身发: [[gài], [cǐ], [shēn], [fà]], 率宾归王: [[shuài], [bīn], [guī], [wáng]], 乐殊贵贱: [[yuè], [shū], [guì], [jiàn]], }) print(pinyin(律吕调阳, styleStyle.TONE)) # [[lǜ], [lǚ], [tiáo], [yáng]]参数说明load_phrases_dict的值是嵌套结构——每个字一层列表列表里放该字允许的读音哪怕只有一个读音也要写成[tiáo]这种形式直接给字符串会报错。它是进程级全局设置跑批时在入口处加载一次就够多个脚本共用就抽成pinyin_dict.py单独维护别在每个函数里重复调用否则覆盖表会散落到各处。优先级顺序记牢整句覆盖 单字覆盖 内置词典 内置单字表。3.4 逐字对齐把一句四言拆成三元组排版要的是「第几个字配第几个拼音」所以注音函数的输出必须是定长数组不能是一个拼好的字符串。import re from pypinyin import pinyin, Style HANZI re.compile(r[\u4e00-\u9fff\u3400-\u4dbf]) def annotate(sentence: str): 返回 [{char: 天, pinyin: tiān, pinyin_alt: }, ...] chars HANZI.findall(sentence) one pinyin(sentence, styleStyle.TONE, heteronymFalse, errorsignore) all_ pinyin(sentence, styleStyle.TONE, heteronymTrue, errorsignore) # 三个数组长度一致才说明汉字切分和拼音切分是对齐的 assert len(chars) len(one) len(all_), f长度不一致: {sentence} result [] for ch, o, a in zip(chars, one, all_): result.append({ char: ch, pinyin: o[0], # 消歧后的主读音 pinyin_alt: ,.join(a) if len(a) 1 else , # 候选留给人工复核 }) return result print(annotate(天地玄黄))逻辑说明heteronymTrue拿候选集heteronymFalse拿消歧结果两者长度相等说明汉字和拼音一路对齐这条断言比任何肉眼检查都值钱。errorsignore让标点和空格不参与返回正好和HANZI.findall的数组一一对应如果这里改成errorsdefault两个数组长度会错开就必须用同一套正则分别去切很多对齐 bug 都是这么来的。3.5 人工复核清单怎么生成rows [r for s in sentences for r in annotate(s)] suspect [r for r in rows if r[pinyin_alt]] print(len(suspect)) # 多音字数量千字文里通常是三位数候选非空的不一定标错但一定值得看一眼。把这批数据导成 CSV让懂古文的人按「上下文 / 应读 / 理由」三列过一遍改完回写覆盖表比在代码里反复猜读音省事得多。复核完之后annotate的输出就是最终数据直接进排版环节。4. ReportLab 排出千字文拼音版 PDF字体、注音表格与分页数据齐了剩下的是把 1000 个字排到 A4 上。这一段的坑集中在字体和行高上版面本身反而是最简单的部分。4.1 中文字体注册与 .ttc、.otf 的坑import os from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.fonts import addMapping FONT /usr/share/fonts/truetype/noto/NotoSerifCJKsc-Regular.ttf pdfmetrics.registerFont(TTFont(HZSerif, FONT)) addMapping(HZSerif, 0, 0, HZSerif) # 常规字重映射说明ReportLab 对 TrueType.ttf最友好注册后会自动做子集嵌入一份含一千多字的 PDF 通常只有几百 KB。麻烦的是另外两种格式——思源字体很多默认发的是 .otfCFF 轮廓直接注册容易报错或者退化成整字体嵌入体积从几百 KB 涨到十几 MB系统里常见的中文字体是 .ttc 集合包一个文件塞了好几个字重注册时要么先拆成单个 .ttf要么用带子字体索引的方式指定。稳妥做法是先确认手上的是 .ttf 单字重再动手排版。字体授权也要看一眼商业发布前确认可嵌入可分发这一步没法用代码解决。4.2 拼音在上、汉字在下的注音表格from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm from reportlab.lib import colors from reportlab.lib.styles import ParagraphStyle from reportlab.platypus import Table, TableStyle, Paragraph py_style ParagraphStyle( py, fontNameHZSerif, fontSize8, leading10, # 行高必须大于字号 textColorcolors.HexColor(#666666), alignment1, ) exp_style ParagraphStyle( exp, fontNameHZSerif, fontSize9, leading13, alignment0, ) def couplet_table(annotated, meaning): annotated 是一联 8 个字的注音数据 py_row [Paragraph(item[pinyin], py_style) for item in annotated] hz_row [item[char] for item in annotated] data [py_row, hz_row, [Paragraph(meaning, exp_style)] [] * 7] t Table(data, colWidths[20 * mm] * 8, rowHeights[8 * mm, 14 * mm, 10 * mm]) t.setStyle(TableStyle([ (FONTNAME, (0, 1), (-1, 1), HZSerif), (FONTSIZE, (0, 1), (-1, 1), 18), (ALIGN, (0, 0), (-1, -1), CENTER), (VALIGN, (0, 0), (-1, -1), MIDDLE), (SPAN, (0, 2), (-1, 2)), # 解释跨满 8 列 (LINEBELOW, (0, 1), (-1, 1), 0.3, colors.HexColor(#DDDDDD)), ])) return t逻辑说明用 Table 而不是在一个段落里拼上下标是因为表格天然按列对齐拼音长短不一tiān 和 huáng也不会让下面的汉字错位。rowHeights必须显式给拼音行字号 8pt 时行高压到 8mm 以下某些字体会把元音上的声调符号裁掉汉字行固定高度后每页能排几联也就确定了分页才好算。参数说明colWidths20mm × 8 160mmA4 宽 210mm左右各留 18mm 边距还有富余想更紧凑可以调到 16mm。leading一定大于fontSize中文注音最常见的坑就是这里——字号 8、行高 8声调符号直接被切平。4.3 分页、页码与 PDF 书签from reportlab.platypus import SimpleDocTemplate, Spacer, PageBreak def on_page(canvas, doc): canvas.saveState() canvas.setFont(HZSerif, 9) canvas.drawCentredString(A4[0] / 2, 15 * mm, f- {doc.page} -) canvas.restoreState() story [] for i, meaning in enumerate(meanings): seg all_annotated[i * 8:(i 1) * 8] # 一联 8 个字 story.append(couplet_table(seg, meaning)) story.append(Spacer(1, 6 * mm)) if (i 1) % 12 0: # 每页 12 联 story.append(PageBreak()) doc SimpleDocTemplate( 千字文拼音版全文附解释文件.pdf, pagesizeA4, leftMargin18 * mm, rightMargin18 * mm, topMargin20 * mm, bottomMargin20 * mm, title千字文拼音版全文附解释, author, ) doc.build(story, onFirstPageon_page, onLaterPageson_page)说明125 联、每页 12 联正文大约 11 页加上封面和目录控制在 15 页以内打印成本可控。用固定联数配合PageBreak而不是让排版引擎自动分页是为了让每页版式一致——教辅类 PDF 一页 11 联、下一页 13 联翻起来很别扭。书签在on_page回调里配合canvas.bookmarkPage()和canvas.addOutlineEntry()加按页码区间分「天地玄黄」「寒来暑往」若干段阅读器侧边栏就能跳转。文件名里的中文在命令行下要加引号跨平台传输建议同时留一份 ASCII 命名版本脚本里写路径时用变量拼别硬编码。4.4 三种注音排版方案的取舍方案实现方式对齐效果适用场景表格双行platypus.Table 两行严格按列对齐四言、整齐文本本例选它段落上下标Paragraph 内嵌标签依赖字体行高长句易跑偏散文体、长短不一的注释canvas 手绘drawString 逐字定位完全可控识字卡片、挂图、固定版式canvas 手绘方案值得单独说一句drawString的 y 坐标要自己加上升部高度换行也要自己算好处是字形位置和设计稿一模一样。做识字卡片、桌面挂图时这个方案反而最省事因为卡片尺寸是死的不需要流式排版。5. 千字文拼音版 PDF 的质检与可检索化技巧文件生成出来不算完还得证明它是对的并且能用。回读校验和可搜索性是两份不同的工作但可以在一个脚本里做完。5.1 用 pdfplumber 回读验证字和拼音都落在文本层import pdfplumber, re HANZI re.compile(r[\u4e00-\u9fff\u3400-\u4dbf]) with pdfplumber.open(千字文拼音版全文附解释文件.pdf) as pdf: text \n.join(p.extract_text() or for p in pdf.pages) chars HANZI.findall(text) print(抽出汉字, len(chars), 去重后, len(set(chars)))期望值就是第 2 章源数据里的那两个数。数字对不上多半是某几页被当成图片渲染了或者字体没写 ToUnicode 映射抽出来是乱码。ReportLab 对注册过的 TTF 默认会生成 CMap用位图字体或自制 Type3 字体才会出问题。这一步顺带验证了复制粘贴可用性——家长把一段话发给老师出来不能是问号。5.2 字体子集化把体积压回几百 KBpip install fonttools pyftsubset NotoSerifCJKsc-Regular.ttf \ --text-filechars.txt \ --output-fileNotoSerifSC-subset.ttf \ --layout-features --no-hintingchars.txt里放全文汉字加解释文案里出现过的所有字一行拼起来即可。中文字体动辄十几 MB按一千多字做子集后通常只剩几百 KB放进 App 离线包或网页嵌入都很轻。注意子集字体只覆盖出现过的字后续改文案必须重新生成别把它当通用字体存进资源目录否则某天解释里多一个「曩」字就渲染成方框。5.3 一个具体技巧让不带调的拼音也能被搜到可见的注音是带调的 tiān用户搜 tian 搜不到这是拼音版 PDF 最常见的体验缺口。做法是在汉字上方再叠一层不可见文本写入不带调的拼音视觉上什么都没有搜索却能命中。canvas.saveState() canvas.setFont(HZSerif, 8) # 文本渲染模式 3 不可见搜得到、看不见 canvas.setTextRenderMode(3) canvas.drawCentredString(x_center, y_top 8 * mm, tian) canvas.restoreState()如果手上的 ReportLab 版本没有setTextRenderMode退而求其次是用白色填充在同样坐标画一遍缺点是复制粘贴时会把拼音一起带出来深色背景上也会露形。两种做法都要放在汉字绘制之前避免把可见文字盖住。收尾前把 5.1 的抽取脚本再跑一次确认叠层没有把汉字挤出文本层确认抽取出的汉字数仍然是 1000。本文还有配套的精品资源点击获取
返回列表