尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python解析PDF:人教版小学古诗词结构化入库实战

Python解析PDF:人教版小学古诗词结构化入库实战 简介这份PDF汇总了最新人教版小学语文1至6年级全部古诗词与日积月累内容面向小学语文教师、家长及需要系统梳理小学阶段古诗文的学习者可解决逐册翻找、版本不一、复习无纲的问题。资源包共1个PDF文件约72KB按年级分册编排涵盖一年级《静夜思》《咏鹅》至六年级《石灰吟》《竹石》等经典篇目并附二年级日积月累、节气歌、成语与名言警句等积累内容。目前已有230人学习下载。读者可借此快速建立小学六年古诗文知识框架按册检索原文用于备课、晨读、期末复习与亲子共读也能对照各年级篇目查漏补缺把握从唐诗宋词到元曲、明清诗歌的编排脉络是一份轻量而实用的古诗文学习索引。1. 从一份 PDF 汇总说起古诗词数据化到底难在哪很多做教育类工具、题库系统或家长端小程序的开发者都遇到过同一个需求把小学阶段散落在各册教材里的古诗词和日积月累整理成结构化数据。人教版小学语文一到六年级古诗词分布在课文、语文园地、日积月累等多个位置格式不统一有的带注释有的只有正文有的还夹着作者朝代。手工整理一份 PDF 汇总看似简单真正落到代码里就会发现断句、标点、作者归属、年级册次映射每一项都能让解析脚本翻车。这份「最新人教版小学语文古诗词和日积月累全汇总(1-6年级).pdf」的价值不在于 PDF 本身而在于它是一份相对完整的语料底本。把它变成可查询、可检索、可导入数据库的结构化数据才是工程上真正要解决的问题。适合谁看做 K12 教育产品的后端和前端、需要批量导入诗词数据的运营工具开发者以及想给孩子做背诵打卡小程序的独立开发者。2. 解析 PDF 前的数据建模与文本清洗2.1 先定 schema再动手解析拿到 PDF 不要急着写解析代码。先想清楚最终要存成什么结构。古诗词和日积月累两类内容字段不同建议分开建模。字段名类型说明是否必填idint主键是gradeint年级 1-6是volumevarchar上册/下册是categoryvarchar古诗/词/日积月累是titlevarchar篇名是authorvarchar作者日积月累可为空否dynastyvarchar朝代否contenttext正文换行用 \n是sourcevarchar出处如语文园地一否这个 schema 的关键决策是把「日积月累」和「古诗词」用 category 区分而不是建两张表。原因是查询场景里经常要「按年级拉全部背诵内容」一张表一次查询就够避免 union。content 字段保留换行符前端渲染时再决定是分行还是连排。2.2 用 pdfplumber 抽取原始文本PDF 解析工具里pdfplumber 对中文排版的支持比较稳能拿到每个字符的坐标方便后续按位置切分。先装依赖再抽文本。# 安装pip install pdfplumber import pdfplumber def extract_raw_text(pdf_path): pages_text [] with pdfplumber.open(pdf_path) as pdf: for i, page in enumerate(pdf.pages): # layoutTrue 保留原始空格布局便于后续按列切分 text page.extract_text(layoutTrue) if text: pages_text.append(text) return \n.join(pages_text) raw extract_raw_text(古诗词汇总.pdf) print(raw[:800]) # 先看前 800 字确认编码和排版逻辑说明extract_text(layoutTrue)会尽量还原 PDF 里的空格和缩进这对后面判断「标题行」和「正文行」很关键。参数上如果 PDF 是扫描件而非文字版pdfplumber 抽出来是空的这种情况得先走 OCR不在本文讨论范围。抽完先打印前几百字确认没有乱码、没有把两列内容串行。2.3 清洗规则标点、空格、页码原始文本里通常混着页码、页眉、多余空格。清洗要按优先级来先删干扰行再统一标点。import re def clean_text(text): lines text.split(\n) cleaned [] for line in lines: s line.strip() # 跳过纯页码行和页眉 if re.fullmatch(r\d, s): continue if 古诗词 in s and 汇总 in s and len(s) 20: continue # 全角空格转半角多个空格压成一个 s s.replace(\u3000, ) s re.sub(r {2,}, , s) cleaned.append(s) return \n.join(cleaned)逻辑说明页码行用re.fullmatch(r\d)精确匹配避免误删正文里的数字。页眉判断用关键词加长度双重条件防止把含「古诗词」的正文标题误删。标点统一这步先不做因为中文标点本身要保留只处理空格。提示清洗规则一定要在完整文本上跑一遍再抽查不要只看前几页。很多 PDF 前几页排版规整后面册次格式突变。3. 按年级册次切分并结构化入库3.1 用正则锚点定位年级和篇目清洗后的文本需要按「年级—册次—篇目」三级切分。常见做法是用正则找锚点比如「一年级上册」「二年级下册」这类字样作为分册标记篇目则靠「标题 作者 正文」的模式识别。import re GRADE_PATTERN re.compile(r([一二三四五六])年级(上|下)册) # 匹配「《静夜思》 唐 李白」这类标题行 TITLE_PATTERN re.compile(r《(.?)》\s*([唐宋元明清]?)\s*([\u4e00-\u9fa5]{2,4})?) def split_by_grade(text): result {} current_grade None for line in text.split(\n): m GRADE_PATTERN.search(line) if m: current_grade f{m.group(1)}年级{m.group(2)}册 result.setdefault(current_grade, []) continue if current_grade and line: result[current_grade].append(line) return result逻辑说明GRADE_PATTERN用中文数字匹配年级因为教材原文一般写「一年级」而不是「1年级」。TITLE_PATTERN里作者部分设为可选因为日积月累条目往往没有作者。切分完得到的是「册次 - 行列表」的字典下一步再逐行判断是标题还是正文。3.2 标题与正文的判定逻辑标题行和正文行的区分是解析成败的关键。经验规则含书名号的行优先当标题不含书名号但符合「短行 后面跟长行」的可能是日积月累的题目。def parse_entries(lines): entries [] i 0 while i len(lines): line lines[i] m TITLE_PATTERN.search(line) if m: title m.group(1) dynasty m.group(2) or author m.group(3) or # 向下收集正文直到遇到下一个标题或空行块 body [] j i 1 while j len(lines) and not TITLE_PATTERN.search(lines[j]): if lines[j].strip(): body.append(lines[j].strip()) j 1 entries.append({ title: title, dynasty: dynasty, author: author, content: \n.join(body) }) i j else: i 1 return entries逻辑说明内层 while 负责收集正文遇到下一个书名号标题就停。这里有个坑有些篇目的正文里也会出现书名号比如注释里引用其他诗。稳妥做法是给正文收集加一个行数上限比如最多 20 行超过就强制断开并打日志人工复核。3.3 写入 SQLite 并建索引结构化数据先落 SQLite方便本地验证后续再迁 MySQL 或 PostgreSQL。import sqlite3 def save_to_db(entries, grade, volume, db_pathpoems.db): conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS poems ( id INTEGER PRIMARY KEY AUTOINCREMENT, grade INTEGER, volume TEXT, category TEXT, title TEXT, author TEXT, dynasty TEXT, content TEXT, source TEXT ) ) for e in entries: cur.execute( INSERT INTO poems (grade, volume, category, title, author, dynasty, content) VALUES (?, ?, ?, ?, ?, ?, ?), (grade, volume, 古诗, e[title], e[author], e[dynasty], e[content]) ) conn.commit() conn.close()逻辑说明grade 存整数便于范围查询volume 存「上册/下册」字符串。category 这里先写死「古诗」日积月累的条目在解析时用另一套规则标记。建表用IF NOT EXISTS保证脚本可重复跑。数据量不大单条 insert 足够几千条以内不用考虑批量优化。注意入库前一定要去重。同一首诗可能在不同册次重复出现比如作为复习内容用 title grade 做唯一性校验避免查询时出重复行。4. 查询、校验与常见解析坑4.1 按年级和关键词查询的 SQL 写法数据入库后最常见的查询是「某年级全部内容」和「按诗句关键词搜」。前者简单后者需要模糊匹配。-- 查三年级全部背诵内容按册次和篇名排序 SELECT grade, volume, title, author, content FROM poems WHERE grade 3 ORDER BY volume, id; -- 按诗句关键词搜索比如找含「明月」的诗 SELECT title, author, content FROM poems WHERE content LIKE %明月% OR title LIKE %明月%;逻辑说明第一条查询走 grade 索引如果数据量大可以给 grade 建索引。第二条 LIKE 前后都带通配符无法走索引数据量上万后会慢。优化方向是用全文检索SQLite 可以开 FTS5把 title 和 content 建虚拟表。-- SQLite FTS5 全文检索 CREATE VIRTUAL TABLE poems_fts USING fts5(title, content, contentpoems, content_rowidid); INSERT INTO poems_fts(rowid, title, content) SELECT id, title, content FROM poems; -- 查询 SELECT * FROM poems_fts WHERE poems_fts MATCH 明月;逻辑说明FTS5 对中文分词默认按字切搜「明月」能命中但搜「明月光」这种跨词组合需要配置分词器。小数据量下 LIKE 够用别过早优化。4.2 校验解析结果的三个检查点解析完不能直接上线至少做三项校验。第一统计每个年级的篇目数和教材实际数量对比偏差超过 10% 就说明切分有问题。第二抽查 content 字段有没有混入页码或页眉。第三检查 author 字段有没有把正文首句误当作者。def validate(db_pathpoems.db): conn sqlite3.connect(db_path) cur conn.cursor() # 检查点一各年级篇目数 cur.execute(SELECT grade, COUNT(*) FROM poems GROUP BY grade) for row in cur.fetchall(): print(f年级 {row[0]}: {row[1]} 篇) # 检查点二正文过短的条目 cur.execute(SELECT title, LENGTH(content) FROM poems WHERE LENGTH(content) 10) for row in cur.fetchall(): print(f疑似异常: {row[0]} 正文长度 {row[1]}) conn.close()逻辑说明正文长度小于 10 的条目大概率是解析断了需要人工看。作者字段的校验可以加一条规则如果 author 长度超过 4 个汉字基本可以判定是误抓因为古人名字很少超过四字。4.3 几个高频翻车点第一个坑是繁简混排。部分 PDF 底本用了繁体字直接入库会导致搜索「明月」搜不到「明月」的繁体写法。清洗阶段统一转简体用 opencc 或简单的映射表。第二个坑是标点全半角混用。正文里「」和「,」混着出现前端展示会很难看。统一转全角中文标点。第三个坑是日积月累里的谚语、成语被当成古诗解析。这类内容没有书名号也没有作者用 TITLE_PATTERN 匹配不到会被漏掉。解决办法是给日积月累单独写一套规则以「日积月累」字样为锚点向下收集到下一个年级标记为止。def parse_riji(text): # 以「日积月累」为起点收集后续非空行 blocks re.split(r日积月累, text) items [] for block in blocks[1:]: lines [l.strip() for l in block.split(\n) if l.strip()] if lines: items.append(\n.join(lines[:10])) # 最多取 10 行 return items逻辑说明split 后第一段是「日积月累」之前的内容丢弃。每段最多取 10 行防止把下一节内容吞进来。这类启发式规则一定要配合人工抽查不能全自动跑完就信。5. 把汇总数据接进小程序或题库的进阶技巧数据整理完真正的价值在于用起来。如果目标是做背诵打卡小程序最实用的技巧是给每首诗生成一个「首字提示」字段用户背不出来时点一下显示每句首字。这个字段可以在入库时用脚本批量生成不用前端实时算。def make_hint(content): # 按标点断句取每句首字 sentences re.split(r[。], content) return .join(s[0] for s in sentences if s) # 批量更新 conn sqlite3.connect(poems.db) cur conn.cursor() cur.execute(ALTER TABLE poems ADD COLUMN hint TEXT) cur.execute(SELECT id, content FROM poems) for pid, content in cur.fetchall(): cur.execute(UPDATE poems SET hint ? WHERE id ?, (make_hint(content), pid)) conn.commit()逻辑说明re.split用中文标点断句取每句第一个字拼成提示串。注意 content 里如果有换行split 后可能产生空串用if s过滤。这个 hint 字段让前端逻辑极简直接展示即可。另一个进阶用法是导出成 JSON 供前端静态加载避免每次查询都打数据库。导出时按年级分文件小程序按需加载。import json def export_by_grade(db_pathpoems.db, out_dir./data): conn sqlite3.connect(db_path) cur conn.cursor() for grade in range(1, 7): cur.execute( SELECT title, author, dynasty, content, hint FROM poems WHERE grade ?, (grade,) ) rows [dict(zip([title, author, dynasty, content, hint], r)) for r in cur.fetchall()] with open(f{out_dir}/grade_{grade}.json, w, encodingutf-8) as f: json.dump(rows, f, ensure_asciiFalse, indent2) conn.close()逻辑说明按年级拆文件单个文件体积小小程序分包加载友好。ensure_asciiFalse保证中文正常显示。导出后建议用json.load回读一次验证格式避免写入时编码出错导致前端解析失败。最后一个技巧关于数据更新教材偶尔会有微调汇总数据要能增量更新而不是全量重跑。给每条记录加一个version字段新版本解析时对比 title grade只更新 content 变化的条目。这样每次教材调整只需要跑一次 diff人工复核量从几千条降到几条。本文还有配套的精品资源点击获取
返回列表