
简介这份PDF汇总了人教版小学语文必背古诗词75首按汉乐府、唐诗、宋诗等经典篇目编排覆盖《江南》《静夜思》《望庐山瀑布》《悯农》等常考诗篇适合小学生、家长及语文教师作为日常诵读与考前复习的便携清单。文件为1个PDF文档体积仅13KB内容包含诗词原文部分篇目附有对韵律、修辞、时代背景等知识点的简要解读方便快速预览与打印背诵。目前已有262人学习下载适合需要系统梳理小学阶段必背古诗词、培养语感与传统文化素养的读者。通过这份资料可集中掌握75首必背篇目理解诗中自然景象、情感寄托与历史人文内涵辅助课内学习与积累。1. 静态 PDF 里的 75 首古诗是教育产品最好用的冷启动语料做教育类产品的团队通常都会遇到同一个问题手头有大量内容但内容形态全都是 PDF、Word、扫描件。整理工作总是被排期产品原型却已经需要真实数据来联调。人教版小学语文必背古诗词 75 首这份 PDF 恰好是一个绕不开又容易低估的样本它只有 75 篇却覆盖汉乐府、唐诗、宋诗、清诗多个历史时期作者信息完整篇目短小非常适合作为古文语料工程的第一个试验场。把这份 PDF 从“给人看的文档”变成“给程序用的数据”中间并不是简单的复制粘贴。篇目格式不统一、标题与作者信息混杂、正文存在异体字和标点缺失这些都是文本抽取阶段要处理的真实问题。这篇文章会从 PDF 版面解析讲起经过字符清洗、数据建模、检索索引最后落到一个可以实际调用的诗词学习 API 上。每一步都有可以直接跑的代码和参数说明读完你手里会多一条从静态资源到结构化服务的完整管线。2. 用 pdfplumber 完成古诗文档的版面解析与字符级清洗2.1 先做版面分析再决定抽取粒度处理 PDF 文本选库之前要先回答一个问题我要拿到的是整页字符串还是带坐标的文本块对古诗词文档来说答案是后者。因为这份 PDF 的篇目排列并不完全规律有的是“序号 书名号标题 作者 正文”一整行有的是换行排版如果直接提取整页文本再接正则很容易把两首诗的内容拼到一起。我一般第一轮用 pdfplumber 而不是 PyPDF2原因在于它可以返回每个文本行的坐标信息bbox后续如果需要按位置排序或者根据字体大小判断标题行都留有余地。下面这段代码完成最基本的逐行抽取import pdfplumber def extract_lines_from_pdf(pdf_path: str) - list[dict]: lines [] with pdfplumber.open(pdf_path) as pdf: for page_num, page in enumerate(pdf.pages, start1): for line in page.extract_text_lines(): text line[text].strip() if not text: continue # 同时记录页码和 y 坐标供后续按阅读顺序重排 lines.append({ page: page_num, text: text, top: line[top], chars_count: len(text) }) return lines pdf_lines extract_lines_from_pdf(gushi75.pdf) for item in pdf_lines[:8]: print(item[page], item[text])这里extract_text_lines()返回的行对象里text是当前行文本top是该行顶部坐标。保留页码和坐标不是多余操作有的 PDF 在排版时会插入页眉、页脚后续清洗阶段需要按坐标把这些干扰行过滤掉。chars_count字段是为了快速找出明显过短的行——比如页码或者孤立的标题行。参数层面需要留意两个点extract_text_lines()的extra_attrs参数可以传入[size]来获取字体大小这会在“标题行 vs 正文行”的判别上非常有用另外pdfplumber打开页面后默认会做字符重排所以遇到文本被切割成两半的情况可以检查extract_words()替换extract_text_lines()。2.2 用正则和命名分组做三字段拆分清洗的下一步是把每一行拆成“序号、标题、作者”三个字段。直接按中括号切是行不通的因为原文档里有好几种写法有的序号后带点有的没有有的作者在书名号前有的在书名号后还有几首诗的标题里带括号比如《悯农》一和《悯农》二。这种情况下命名分组正则比手写字符串切片更稳。import re LINE_PATTERN re.compile( r^(?Pidx\d{1,2})\s*[、.]?\s* r[《(]?(?Ptitle[^《》()]{1,12})[》)]? r\s*(?Pauthor[\u4e00-\u9fa5]{1,6})?$ )拆分逻辑里最微妙的是作者字段{1,6}限制了作者名长度但这个正则要求作者名后必须紧跟行尾。问题在于原文档第 27 首写作“杜甫《绝句》杜甫”书名号前后都出现了作者名。如果只用上面的表达式后半个“杜甫”会匹配到author组里但“绝句”会被切出来而前面的“杜甫”会变成多余字符。我实际处理时会在正则之前先做一次特殊预处理把“作者《标题》作者”这种模式直接替换成“《标题》作者”def normalize_header_line(line: str) - str: # 处理类似 27. 杜甫《绝句》杜甫 这种作者重复出现的情况 dup re.match( r^(?Pidx\d{1,2})\s*[、.]?\s* r(?Pauthor1[\u4e00-\u9fa5]{1,6}) r[《(](?Ptitle[^《》()]{1,12})[》)] r(?Pauthor2[\u4e00-\u9fa5]{1,6})$, line ) if dup: return f{dup.group(idx)}. 《{dup.group(title)}》{dup.group(author1)} return line替换完成后再走正式解析。这个边界处理如果漏掉会导致整个清洗管线在跑到第 27 首时崩掉后续所有篇目的 ID 错位。如果你打算把这个清洗脚本跑在不同批次的 PDF 上建议把这类特殊模式收集成字典表而不是只依赖正则覆盖。2.3 正文清洗标点统一与异体字映射表标题行拆完正文还有一层字符级处理。原始文本里有几个绕不开的问题全角标点和半角标点混用、“里”和“裏”这类异体字同时出现、以及正文行尾缺少句号。字符清洗决定了后续做背诵评测时的比对准确度这一步省了后面全部返工。问题类型示例处理方案标点不统一“江南可采莲莲叶何田田” vs 半角逗号统一替换为中文标点异体字“里” 与 “裏”“峰” 与 “峯”建立映射表再 replace行末缺标点“鱼戏莲叶间”后直接换行按韵脚补句读符混入页眉页面顶部重复出现“人教版”字样按坐标过滤掉 top 值小于阈值的行对应的清洗函数如下建议把映射表单独抽成常量方便后续扩展其他语料时复用。VARIANT_MAP { 裏: 里, 峯: 峰, 飮: 饮, 烟: 烟, 邨: 村, } def clean_poem_text(text: str) - str: # 统一标点全角逗号、句号归一 text text.replace(,, ).replace(., 。).replace(;, ) # 异体字映射 for old, new in VARIANT_MAP.items(): text text.replace(old, new) # 去掉首尾空白与多余空格 return .join(text.split()).join(text.split())会去掉文本内部所有空白字符这个操作对古文安全吗只要是纯汉字与标点组成的诗句就是安全的但如果未来接入包含注释的文本就不能这么粗暴应该换成只去掉空白字符的正则。补句读符是最难自动化的部分简单做法是按“韵脚字集 句末助词”判断比如“兮”“之”“矣”后补句号但更稳妥的方案是直接硬编码这 75 首的标准分行数据清洗只是兜底。3. 建立诗词结构化模型与低开销检索层3.1 诗词 JSON Schema 的设计考量数据清洗完成后下一个决策点是为每首诗设计存储结构。我的选择是 JSON原因有三个字段可以逐步新增而不破坏旧数据与 Python 字典天然兼容输出到 Elasticsearch、SQLite 或 NoSQL 都只是做一次序列化转换。但 JSON 的灵活性也容易掩盖数据质量问题所以 schema 里的关键字段必须提前定义好约束。下面是我处理这类诗词语料时使用的结构75 首的字段总量很小不需要引入 ORM直接存.json文件或导入 SQLite 都合适{ id: 1, title: 江南, author: 汉乐府, dynasty: 汉, genre: 乐府, char_count: 35, theme_tags: [江南, 采莲, 自然], content: 江南可采莲莲叶何田田。鱼戏莲叶间。鱼戏莲叶东鱼戏莲叶西鱼戏莲叶南鱼戏莲叶北。, sentence_count: 7, first_chars: [江, 莲, 鱼, 鱼, 鱼, 鱼, 鱼] }字段设计不是越多越好要围绕“上层产品用得到什么”来定。theme_tags用于主题筛选first_chars是每句首字数组背诵评测时可以用“给首字背全句”的提示模式char_count和sentence_count用于做难度分级。genre字段建议统一枚举值不能有的存“乐府”有的存“汉乐府”否则后面按体裁过滤时会出现统计偏差。3.2 从清洗文本到 JSON 的转换管线解析逻辑分成两步先按目录正则切分 75 首的边界再逐首提取正文。切分边界是整个管线里最容易出错的地方必须用“序号连续 标题行特征”双重判断而不是只在文本里找书名号。下面这段代码把上一步清洗好的文本列表转换成结构化数据import json def build_poem_dataset(clean_lines: list[str]) - list[dict]: poems [] current None for line in clean_lines: header LINE_PATTERN.match(line) if header: if current: poems.append(current) current { id: int(header.group(idx)), title: header.group(title), author: header.group(author), } else: if current: current.setdefault(content_parts, []).append(line) if current: poems.append(current) for poem in poems: content .join(poem.pop(content_parts, [])) poem[content] content # 去掉按句切分后的空字符串 sentences [s for s in re.split(r[。], content) if s] poem[sentence_count] len(sentences) poem[first_chars] [s[0] for s in sentences if s] return poems dataset build_poem_dataset(cleaned_lines) print(json.dumps(dataset[:2], ensure_asciiFalse, indent2))运行时会发现一个常见问题五言诗和七言诗的sentence_count计算并不准确因为在“鱼戏莲叶间。鱼戏莲叶东”这两句里“间”和“东”之间没有逗号只在句尾有句号。按标点切分会把一个“句”分成多个片段。如果产品端只需要句子数量可以容忍这个误差如果要做严格的“逐句背诵”就必须手工维护一份断句数据。对 75 首的规模来说手工校正成本很低远比写一个自然语言处理的层次切分器划算。3.3 用倒排索引做篇目级检索数据结构建好之后需要解决“用户搜一个词如何快速找到相关诗篇”的问题。75 首的规模不大直接遍历每一首做子串匹配也就几毫秒但为了接口后续扩展到更多诗词我倾向直接用 Python 默认库建倒排索引不引入额外中间件。from collections import defaultdict def build_inverted_index(poems: list[dict]) - dict[str, list[int]]: index defaultdict(list) for poem in poems: # 以单个汉字为最小索引单元配合主题标签做增强 for ch in set(poem[content].replace(, ).replace(。, )): index[ch].append(poem[id]) for tag in poem.get(theme_tags, []): index[tag].append(poem[id]) return dict(index) index build_inverted_index(dataset)以单个汉字为单位建索引的优点是索引体积很小一个汉字对应一个诗词 ID 列表查询时直接把用户输入中的汉字对应列表取交集就能得到包含这些字的诗篇。比如输入“春风”可以查出《咏柳》《村居》《泊船瓜洲》等篇目。这个方案的缺点是无法感知词组语义所以我在索引里额外写入了theme_tags用人工打标解决“春风”这种抽象词在语义层面的召回问题。实际开发里主题标签的覆盖率和准确性比索引结构对搜索体验的影响更大。4. 背诵练习背后的记忆算法与评测逻辑4.1 间隔重复为什么默认参数不能照搬诗词背诵类产品的核心功能是“提醒用户复习”。业界标准方案是间隔重复SM-2 算法最常用它通过用户每次的自评等级调整下次复习间隔。但把它直接套到小学生背古诗的场景里有两个参数必须调整。第一个是初始间隔。Anki 的默认曲线是“1 分钟 → 10 分钟 → 1 天”这适合记忆卡片但一首 20 字的五言绝句初次背诵后当天就应该再做一次复习而不是等一天。我处理的方案是把第一轮间隔设为 4 小时当天完成“首次学习 第一次复习”闭环。第二个是用户自评等级的粒度成人可以区分 0 到 5 六个等级对低龄用户明显过细压缩成“记住了 / 模糊 / 忘了”三档更现实。def schedule_review(interval_days: float, ease: float, memory_level: int) - tuple[float, float]: memory_level: 0忘了, 1模糊, 2记住了 返回新的间隔天数与难度系数 if memory_level 0: # 忘掉间隔重置到 0.17 天约 4 小时难度下调 interval_days 0.17 ease max(1.3, ease - 0.2) elif memory_level 1: # 模糊间隔不变保持当前难度 interval_days max(0.17, interval_days * 0.5) ease ease else: # 记住了间隔按难度系数增长 if interval_days 1: interval_days 1 else: interval_days round(interval_days * ease, 1) ease max(1.3, ease 0.05) return interval_days, ease这段代码里interval_days用浮点数因为第一天内的复习间隔要用小时表示。ease的初始值设为 2.0比 SM-2 的默认 2.5 略低原因是古诗词篇幅短、复习成本低难度系数虚高会导致间隔拉得过长与“每周把 75 首轮一遍”的目标冲突。实盘跑下来发现ease 低于 1.3 时对应的用户基本已经放弃所以下限必须锁死在 1.3。4.2 背诵结果的自动判定方法背诵评测不能只靠用户自己点“记住了”。常见的判断手段是把用户背诵的文本和标准诗句做相似度比对但逐字相等太严格背串诗句的现象很常见。我用的是编辑距离思路用 Python 标准库difflib实现import difflib def normalize_for_compare(text: str) - str: # 去掉所有标点与空白只保留汉字 return .join(ch for ch in text if \u4e00 ch \u9fa5) def recitation_score(user_input: str, standard: str) - float: u normalize_for_compare(user_input) s normalize_for_compare(standard) if not u: return 0.0 matcher difflib.SequenceMatcher(None, u, s) return matcher.ratio()判定阈值的经验值是ratio 0.95算完全正确允许 1 个字错漏0.8 ~ 0.95算模糊要求用户观看答案后再复习一次 0.8直接判遗忘。阈值不能定死因为ratio()是按匹配块长度算的五言诗和七言诗同样错一个字得分差异明显。七言诗 28 个字错 1 个字ratio 已经降到 0.96 左右四言短诗更敏感。实际产品里应该按char_count分档设置阈值而不是用全局阈值。4.3 结合遗忘曲线的复习时间窗排复习计划时可以把一天的复习任务按“超过 80% 用户遗忘节点”分早中晚三个窗口。根据这个语料库的实际体量首诗均 2035 字一天背 5 首新诗 复习 15 首旧诗是合理的上限。如果并发复习超过 25 首间隔重复算法的间隔调整已经无法解决实际疲劳问题需要做的是降级新诗数量而不是继续硬排。复习队列的实现不复杂但要注意每次调度后把next_review_at持久化。这里给出一段参考实现数据结构使用heapq按时间排序import heapq from datetime import datetime, timedelta review_queue [] def push_review(poem_id: int, review_time: datetime): heapq.heappush(review_queue, (review_time.timestamp(), poem_id)) def pop_due_reviews(now: datetime) - list[int]: due [] while review_queue and review_queue[0][0] now.timestamp(): _, poem_id heapq.heappop(review_queue) due.append(poem_id) return duepush_review里我用时间戳作为堆排序主键避免datetime对象比较时出现时区问题。这里有一个工程细节无论复习结果如何都要在用户提交背诵结果后立刻重新调用schedule_review并生成新的review_time入堆不能在定时器回调里统一处理否则会出现大量到点但用户已经睡了的无效提醒。5. 把资源做成可在线调用的诗词学习 API5.1 FastAPI 单文件实现最小服务前面的数据和算法都是离线运行的要让前端 H5、小程序或者语音助手用起来需要把整个管线包一层 HTTP 接口。FastAPI 适合这个场景因为 75 首诗的规模不需要引入数据库内存里维护一份 JSON 列表就够。启动命令也简单uvicorn main:app --host 0.0.0.0 --port 8000。这里给出一个单文件实现同时暴露“列表查询”和“单首详情”两个接口from fastapi import FastAPI, Query, HTTPException from fastapi.responses import JSONResponse import json app FastAPI(title古诗词 API, version1.0.0) with open(poems.json, encodingutf-8) as f: POEMS json.load(f) app.get(/poems) def list_poems( author: str | None Query(None, description按作者过滤), keyword: str | None Query(None, description按正文关键词搜索), ): result POEMS if author: result [p for p in result if author in p[author]] if keyword: result [p for p in result if keyword in p[content]] return JSONResponse(content{total: len(result), items: result}) app.get(/poems/{poem_id}) def get_poem(poem_id: int): for p in POEMS: if p[id] poem_id: return JSONResponse(contentp) raise HTTPException(status_code404, detailpoem not found)接口参数里author用in做模糊匹配而不是精确相等是为了兼容“杜甫”和“杜”这类查询意图实际请求量小到不需要为这种匹配方式优化。返回结构直接复用 JSON schema 字段前端拿content字段展示正文拿first_chars做提示背诵模式这两个字段是产品端使用频率最高的。5.2 用内容哈希做接口版本控制接口上线后最容易被忽略的问题是“数据更新了但客户端还在用旧缓存”。JSON 文件可能因为标点修订、作者信息校正而更新浏览器端和服务端的内存缓存都需要一个幂等的版本标识。计算一份内容哈希并把它放进响应头是性价比最高的方案。import hashlib def build_etag(payload: list[dict]) - str: raw json.dumps(payload, ensure_asciiFalse, sort_keysTrue).encode(utf-8) return hashlib.md5(raw).hexdigest()[:16] from fastapi.responses import Response app.get(/poems/{poem_id}) def get_poem_with_etag(poem_id: int, response: Response): # 首次请求时计算并缓存 etag后续请求判断 If-None-Match content json.dumps(get_poem(poem_id).body, ensure_asciiFalse) etag build_etag([content]) response.headers[ETag] etag if response.headers.get(If-None-Match) etag: raise HTTPException(status_code304) return contentsort_keysTrue是为了保证同样内容每次计算出的哈希一致否则字典键顺序变化会导致 ETag 抖动。304 状态码配合浏览器本地缓存可以让背诵打卡页在二次打开时秒开。这个小技巧比引入 Redis 缓存更直接也更容易在 PHP、Node.js 项目里迁移。本文还有配套的精品资源点击获取