
简介这是一份适合小学生及家长共同使用的文言文启蒙学习资料聚焦小故事中蕴含的传统智慧与做人道理帮助孩子在轻松阅读中积累文言词汇、培养语感。文档精选《陈元方候袁公》《画蛇添足》等经典篇目每篇均配有生僻字注音、重点词注释、白话译文及精要点评例如借陈元方巧妙回应袁公提问展现少年思辨力用画蛇添足告诫做事须适可而止、实事求是非常便于自学或亲子共读。资源共1个docx文件压缩包总大小约57KB正文排版清晰可打印或转存至平板使用。目前已吸引63人浏览学习。除上述两篇外内容还延伸收录《父善游》《人有亡斧者》等短章从“技能无法遗传”“疑邻盗斧”等经典素材切入引导孩子思考具体问题具体分析、避免主观臆断等道理。整体篇幅精简、重点突出适合小学中高年级作为课外文言文阅读拓展也适合家长在家庭教育中渗透品格教育。1. 小学生文言文小故事数字化第一步不是做 App一份「2021-2022年收藏」的 .docx 文件里躺着几十篇文言文小故事内容是好内容——短小、有情节、适合孩子读。但它躺在 Word 里就意味着只能一页页翻没法按篇目检索、没法标注重难点、没法生成背诵计划、更没法做自适应练习。问题不是「内容不够好」而是「形式没加工」。这篇文章要解决的正是这个把一份 .docx 格式的文言文故事集做成一套可检索、可标注、可交互的学习数据资产。读者如果是做教育工具、内容管理系统或者单纯想给孩子整理学习资料的工程师这篇文章的路子可以直接套用先解析文档再做文本清洗和断句然后设计一套 JSON 数据模型最后落成一个能在浏览器里跑的背诵卡片页。整体不涉及框架选型全是能用 Python 和静态页面解决的问题。2. 用 python-docx 把 .docx 里的文言小故事抽成干净文本2.1 为什么选 python-docx 而不是直接改后缀解压很多人拿到 .docx 第一反应是用 pandoc 转 txt或者把后缀改成 .zip 后手撕 document.xml。但这两个方案对「带收藏、备注、手工排版」的文档都不够稳。pandoc 会把段落结构按 Markdown 规则重新折叠丢失 Word 里原有的空行、缩进和手动换行手撕 XML 则要自己处理命名空间和样式节点工作量大且容易漏字。我一般直接上 python-docx核心原因有两个它保留 paragraph 级别粒度每段文字、每段样式都能单独拿到适合后续按「标题」「正文」「注释」分流。它在处理中文字符时不会做编码转换拿到的那段文字在 Python 里是什么样写到 JSON 里就是什么样不会出现繁体转简体或全角半角被悄悄改掉的意外。安装一行命令pip install python-docxpython-docx 依赖 lxml 和 typing_extensions装完可用python -c import docx; print(docx.__version__)确认加载成功。2.2 最小可行的解析脚本写一个最朴素的版本先把所有段落按顺序打出来from docx import Document doc Document(优秀资料2021-2022年收藏小学生文言文小故事.docx) for i, para in enumerate(doc.paragraphs): text para.text.strip() if text: print(f[{i}] {text})这段代码能跑但输出大概率是这样的[12] 守株待兔 [13] 宋人有耕田者田中有株。兔走触株折颈而死。 [14] 因释其耒而守株冀复得兔。 [15] 兔不可复得而身为宋国笑。 [16] 注释株——树桩。走——跑。看起来结构简单但那是因为这篇文档排版规整。真实情况里故事标题、正文、注释、生字注音很可能在不同层级的样式中直接用doc.paragraphs会错过表格里的内容和文本框里的内容。这时候要用iter_inner_content()做块级遍历from docx import Document from docx.table import Table from docx.text.paragraph import Paragraph doc Document(优秀资料2021-2022年收藏小学生文言文小故事.docx) def iter_block_items(parent): from docx.oxml.ns import qn parent_elm parent.element.body for child in parent_elm.iterchildren(): if child.tag qn(w:p): yield Paragraph(child, parent) elif child.tag qn(w:tbl): yield Table(child, parent) for block in iter_block_items(doc): if isinstance(block, Paragraph): text block.text.strip() if text: print(P:, text) elif isinstance(block, Table): for row in block.rows: cells [cell.text.strip() for cell in row.cells] print(T:, | .join(cells))逻辑说明iterchildren()按文档流的真实顺序遍历 body 下的子节点不会像doc.paragraphs那样把所有段落先捞出来、把表格单独放一边。用qn(w:p)和qn(w:tbl)判断节点类型分别构造 Paragraph 和 Table 对象这样表格里的文言文故事也能被抽出来。如果文档里是「一篇故事一个表格」这个脚本会按行打印单元格内容方便下一阶段按行号定位到具体篇目。参数说明iter_block_items函数里parent传入doc即可如果文档存在分节符建议改成for section in doc.sections: section._element否则会把所有节的内容混在一起处理。2.3 结构感知提取按样式名分流标题与正文拿到纯文本后下一步就是区分「哪段是篇目名哪段是原文哪段是注释」。常见做法是看段落的样式名from docx import Document doc Document(优秀资料2021-2022年收藏小学生文言文小故事.docx) for i, para in enumerate(doc.paragraphs): if not para.text.strip(): continue style_name para.style.name if para.style else None print(f[{i}] style{style_name} | {para.text.strip()[:30]})如果文档的作者用过「标题 1」「标题 2」或自定义的「故事名」「译文」样式这里会直接显示出来。要是样式全是「正文」那还有一个土办法用字体大小和是否加粗来判断。for para in doc.paragraphs: if not para.text.strip(): continue for run in para.runs: if run.bold or run.font.size and run.font.size.pt 14: print(标题候选:, para.text.strip()) break这个方法的判断依据是正文一般 12 磅或 10.5 磅标题至少 14 磅加粗。收藏类文档里作者通常会用加粗或大字号标注故事名这一招在大多数手工整理稿里都有效。2.4 处理从网页粘贴留下的脏格式教育类文档有个高频来源从网页直接复制粘贴到 Word。这会导致段落里混入不间断空格\xa0、弯引号、全角括号混用还有些段落明明该是正文却被套了「超链接」样式。写一个正则清洗函数在进入下一步之前统一处理import re def clean_text(text: str) - str: text text.replace(\xa0, ) text text.replace( , ) text re.sub(r[ \t], , text) text text.replace(“, 「).replace(”, 」) text text.replace(‘, 「).replace(’, 」) text text.replace(, ().replace(, )) # 去掉 Word 自动生成的批注引用标记 text re.sub(r\[A-Za-z]{2}\d\], , text) return text.strip()逻辑说明第一步把不间断空格换成普通空格防止后续正则匹配\s时出现死角第二步把弯引号和弯括号统一成半角或直角形式便于后续按句子边界拆分最后一步去掉类似[AB12]这样的批注残留。参数说明clean_text只处理字符串不感知段落上下文。如果确认文档里某些标点如「·」是中音线需要保留把这个函数里的对应替换删掉即可不要全局套用。3. 文言文小故事的分句与断句正则之外还需要规则3.1 按句末标点做首轮拆分文言文分句比现代文简单因为句末标点只有「。」「」「」三种加上引号闭合。先做一个按标点切分的函数import re def split_sentences(text: str): parts re.split(r(?[。]), text) return [s.strip() for s in parts if s.strip()] text 宋人有耕田者田中有株。兔走触株折颈而死。因释其耒而守株冀复得兔。 for sent in split_sentences(text): print(sent)输出宋人有耕田者田中有株。 兔走触株折颈而死。 因释其耒而守株冀复得兔。拆分逻辑用的是零宽断言(?[。])含义是在句末标点之后切分但保留标点本身。这样每句话带上标点结束后续拼回原文时不需要重新补标点。参数说明这个正则会漏掉一种常见情况——后引号。如果原文写成「兔不可复得而身为宋国笑。」直接按上述正则会切在「。」后面把后引号留在下一句开头。处理办法是拆分前把「。」替换成「。」加一个临时占位符或者改用(?[。」’])把右引号纳入切分边界def split_sentences(text: str): parts re.split(r(?[。」’]), text) return [s.strip() for s in parts if s.strip()]3.2 注释行与原文行分离文档里「注释」开头的内容必须和原文分开放否则会污染后续的字频统计和难度评估。识别逻辑按前缀匹配def is_comment_line(line: str) - bool: comment_markers [注释, 注, 译, 译文, 释义, 〔] return any(line.startswith(mark) for mark in comment_markers)这里的startswith匹配方式比in更严格避免把正文里出现的「注释」两个字误判为注释行。每条注释用「——」或「」切出词语和解释def parse_comment(line: str): line line.replace(注释, ).replace(注, ) items re.split(r[;], line) gloss [] for item in items: if —— in item: word, meaning item.split(——, 1) gloss.append({word: word.strip(), meaning: meaning.strip()}) elif in item: word, meaning item.split(, 1) gloss.append({word: word.strip(), meaning: meaning.strip()}) return gloss print(parse_comment(注释株——树桩。走——跑。))输出[{word: 株, meaning: 树桩。}, {word: 走, meaning: 跑。}]注释解析这块最怕分隔符不统一同一篇文档里可能有些条目用「——」有些用「」还有些直接写「株树桩」。建议先打印全部注释行人工扫一遍分隔符类型再对应补充split逻辑。3.3 收集生字与通假字生成难词表文言文小故事的教与学核心不是句子结构分析而是生字和「古今异义」词。从清洗后的文本里提取高频疑难字做法是先排除常用字表再看剩余字在全文中的出现频次from collections import Counter COMMON_CHARS set(的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞 def build_char_freq(text): counter Counter(c for c in text if \u4e00 c \u9fff) rare {} for ch, cnt in counter.most_common(): if ch not in COMMON_CHARS: rare[ch] cnt return rare for ch, cnt in build_char_freq(text).items(): print(f{ch}: {cnt})逻辑说明COMMON_CHARS是用现代汉语高频字表过滤掉常用字剩余的字多半是文言文特有字或生僻字。对小学生文言文学习来说生字不是「出现频次越高越重要」而是「超出认识范围的字」最重要所以输出结果里还应该人工过一遍把「兔、株、耒、冀」这类虽然常见但文言含义不同的字单独挑出来。注意COMMON_CHARS这张字表不是标准集不同版本高频字表略有差异。要更精确的生字表可以参考小学语文教材生字表把它做成一个 Python 集合文件后续每次解析直接加载。4. 设计一个 JSON 数据模型让每篇小故事可以独立检索4.1 故事、句子、注释三层结构清洗完成之后最忌讳的是把所有故事继续堆在一个文本文件里。检索、标注、出题都需要把「故事」拆成「篇目 → 句子 → 词语注释」的粒度。给出一个 JSON 结构{ meta: { source_file: 优秀资料2021-2022年收藏小学生文言文小故事.docx, parsed_at: 2024-05-18, total_stories: 12 }, stories: [ { id: shou-zhu-dai-tu, title: 守株待兔, source: 韩非子·五蠹, paragraphs: [ 宋人有耕田者田中有株。, 兔走触株折颈而死。, 因释其耒而守株冀复得兔。, 兔不可复得而身为宋国笑。 ], sentences: [ {text: 兔走触株折颈而死。, idx: 2}, {text: 因释其耒而守株冀复得兔。, idx: 3} ], glossary: [ {word: 走, meaning: 跑, type: 古今异义}, {word: 株, meaning: 树桩, type: 生字}, {word: 耒, meaning: 古代农具, type: 生字} ], tags: [寓言, 农业, 战国] } ] }这个结构设计的关键点在于paragraphs保留原文段落顺序供整篇展示。sentences是拆出来的单个句子供逐句背诵和测验。glossary里的type字段标记「生字」「古今异义」「通假字」做练习题时可以直接按类型过滤。tags留给后续做分类浏览比如「寓言」「历史人物」「成语故事」。4.2 从清洗后的文本自动生成这个 JSON写一个生成脚本把前面几个步骤串起来import json import re from docx import Document # 前面定义的 clean_text、split_sentences、parse_comment 省略 def docx_to_stories(docx_path: str) - dict: doc Document(docx_path) stories [] current_story None current_glossary [] for para in doc.paragraphs: text clean_text(para.text) if not text: continue # 判断是否是标题加粗且长度小于 20 的短文本 is_title any(run.bold for run in para.runs) and len(text) 20 if is_title: if current_story: current_story[glossary] current_glossary stories.append(current_story) current_story { id: re.sub(r\s, -, text), title: text, paragraphs: [], sentences: [], glossary: [] } current_glossary [] elif current_story: if is_comment_line(text): current_glossary.extend(parse_comment(text)) else: current_story[paragraphs].append(text) for sent in split_sentences(text): current_story[sentences].append({ text: sent, idx: len(current_story[sentences]) 1 }) if current_story: current_story[glossary] current_glossary stories.append(current_story) return { meta: { source_file: docx_path, parsed_at: 2024-05-18, total_stories: len(stories) }, stories: stories } if __name__ __main__: data docx_to_stories(优秀资料2021-2022年收藏小学生文言文小故事.docx) with open(stories.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)逻辑说明脚本把整篇文档当作连续段落流处理。遇到加粗短文本就认定为新篇目开始注释行单独走解析函数其余文本既进paragraphs又进sentences。最终输出文件里meta.total_stories自动统计篇数。参数说明is_title的判断条件是「存在加粗 run 且文本长度 ≤20」这个阈值来自常见文档排版习惯。如果某篇文档标题很长且不加粗这个判断会失败需要先跑一遍 2.3 节的样式扫描确认。4.3 善用 tags从故事内容里自动提取篇目标签tags字段手工填也可以但文档量大之后效率太低。可以写一个简单规则按关键词映射打标签TAG_RULES { 寓言: [寓言, 启示, 道理], 成语: [成语, 后来, 比喻], 历史: [帝, 王, 国, 战], 神话: [神, 龙, 天, 仙], } def auto_tag(story: dict) - list: full_text .join(story[paragraphs]) tags [] for tag, keywords in TAG_RULES.items(): if any(kw in full_text for kw in keywords): tags.append(tag) story[tags] tags return tags这个简单方法的局限是关键词覆盖不全比如「守株待兔」原文里没有「寓言」两个字就匹配不上。改进思路是把「出自哪本书」作为标签来源解析source字段像「韩非子」直接映射到「法家」「先秦」。这份文档如果自带出处信息用出处映射比内容匹配更可靠。5. 做一张可检索的背诵卡片页并用 3 个参数控制复习节奏5.1 生成一个零依赖的 HTML 检索页数据有了 JSON下一步是生成一个能在浏览器直接打开的学习页面。不需要框架用 Python 读 JSON 拼 HTML 字符串即可import json import html with open(stories.json, r, encodingutf-8) as f: data json.load(f) cards [] for story in data[stories]: title html.escape(story[title]) paras .join(fp{html.escape(p)}/p for p in story[paragraphs]) gloss .join( flib{html.escape(g[word])}/b——{html.escape(g[meaning])}/li for g in story[glossary] ) tags .join(fspan classtag{html.escape(t)}/span for t in story.get(tags, [])) cards.append(f div classcard>def next_review(completed_reviews: int, last_score: float, config: dict) - float: if completed_reviews 0: interval_days config[initial_interval] else: interval_days config[initial_interval] * (config[interval_multiplier] ** completed_reviews) if last_score 0.7: interval_days * config[ease_penalty] return round(interval_days, 1) print(next_review(0, 0.5, {initial_interval: 1, interval_multiplier: 2.0, ease_penalty: 0.2})) print(next_review(2, 0.8, {initial_interval: 1, interval_multiplier: 2.0, ease_penalty: 0.2}))输出1.0 3.2逻辑说明公式里interval_multiplier ** completed_reviews实现了间隔递增复习两次后间隔 4 天但如果本次答错last_score 0.7间隔乘上ease_penalty缩减到 0.8 倍约 3.2 天相当于把排程往回拉。这个模型比 SM-2 简单但胜在参数直白适合小故事量级的场景。参数说明last_score取 01 之间的值由答题正确率归一化得到。比如 5 题答对 4 题score 0.8。阈值 0.7 是经验值调高会导致答对也被惩罚调低会让答错也跳过惩罚。5.3 验证解析完整性一个冷启动自查命令整个流程跑完后建议做一次完整性校验防止有篇目因为标题样式不合规则被漏掉python -c import json data json.load(open(stories.json, encodingutf-8)) print(stories:, data[meta][total_stories]) for s in data[stories]: # 检查每篇是否都有正文和注释 if not s[paragraphs]: print(missing paragraphs:, s[title]) if not s[glossary]: print(missing glossary:, s[title]) # 检查句子是否都能拼回段落 joined .join(x[text] for x in s[sentences]) orig .join(s[paragraphs]) if joined.replace(, ).replace(。, ) ! orig.replace(, ).replace(。, ): print(sentence mismatch:, s[title]) print(check done) 这个校验脚本查三类问题缺正文、缺注释、句子拆错。前两类说明脚本标题判断或注释判断有问题第三类说明正则拆句时把标点吞了或漏了。实际运行时如果看到sentence mismatch优先检查 3.1 节里(?[。」’])这个正则是否覆盖了所有句末标点组合。本文还有配套的精品资源点击获取