☰
从文本到SQLite:构建唐诗三百首结构化数据集与查询实践
2026/9/25 14:31:25 网站建设 项目流程

简介:这份数据集以「唐诗三百首」为主题,整理收录320条经典唐诗的结构化记录,覆盖诗题、作者、正文等常见字段,适合中文学习者、文学研究者、数据分析师以及后端开发者在文本挖掘、诗词应用、数据库课程设计等场景中直接使用。压缩包共4个文件,分别提供JSON、CSV、SQL、XLSX四种格式:JSON便于程序间灵活交换,CSV适合表格处理与机器学习预处理,SQL脚本可直接导入MySQL等关系型数据库,XLSX则方便非技术用户快速浏览筛选,四种格式覆盖了常见使用路径。整个压缩包仅141KB,轻量易下载。目前已有1449人学习下载,口碑实用,尤其适合需要快速获取标准中文古诗语料用于教学演示、接口测试或数据建模的初学者与进阶开发者。拿到手即可按需选用对应格式,无需重新爬取或手工整理,能有效节省数据准备时间,直接聚焦后续分析、算法验证或应用开发,是一份高性价比的古典诗歌文本数据集。

1. 为什么我现在还会把《唐诗三百首》做成一款数据库数据集

如果你只是上网搜一个现成的 txt 来背诗,那确实不需要数据库。但如果你在做数据库课程设计、练 SQL 查询、或者想搭一个能按作者、按意象、按韵脚检索的古诗词检索工具,你会很快发现:网上流传的《唐诗三百首》文本大多来源不明、编码混乱、断句随缘,拿来做数据集基本是给自己埋雷。我几年前接过一个课程设计需求,学生拿到的原始数据是一份从古诗词站点抓下来的网页转文本,三百多首诗里混着编者按、注释和广告残留,清洗花了整整一个晚上。从那之后我的习惯就变了:先把《唐诗三百首》整理成一份结构化的数据库数据集,再谈查询和应用。这个方向适合三类人——做课程设计的学生、想练手 SQL 和 Python 的开发者、以及做中文文本检索实验的研究者。它能解决的核心问题就一个:让三百首诗的检索、统计、筛选变成几分钟内可以完成的事。

2. 从 txt 到表结构:把三百首诗拆成可查询的字段

2.1 先搞清楚《唐诗三百首》的原始文本长什么样

网上流传的《唐诗三百首》文本来源复杂,常见的形态有三种:第一种是带卷次结构的纯文本,开头是「卷一」或「五言古诗」,下面逐首列出标题、作者、正文;第二种是网页爬虫抓下来的 HTML 转文本,每首诗之间可能残留「上一篇」「下一篇」这类导航文字;第三种是 ePub 或 PDF 导出的文本,排版错乱、繁体字与简体字混用。我拿到原始文件后的第一个动作,不是写正则,而是先打开文件看前面的三十行,确认它的编码、换行规则和分隔方式。这一步在操作上很短,但能省掉后面大量的返工。

确定了文本形态之后,再定清洗方案。以最常见的「标题 + 作者 + 正文」三段式为例,一首诗在文本里大致长成这个样子:标题独占一行,作者独占一行,从第三行开始到空行之前是正文。但《唐诗三百首》有个特例:五言绝句和七言绝句可能两首连排,中间只空一行,或者正文里混入了「(唐)李白」这样的括号前缀。整理这块时我一般不看网上的现成脚本,而是自己写一个针对格式的解析规则,因为不同来源的断行方式差异不小,现成脚本往往要二次修改。

2.2 用 Python 把原始文本切分成结构化数据

下面是我处理这类原始文本时常用的拆分脚本,核心思路是用「空行 + 标题行特征」定位每首诗的开始位置,然后把标题、作者、正文切出来,最后统一写出 CSV。这里不以任何现成包为前提,只需要 Python 标准库就能跑完。

import re import csv SOURCE_FILE = "tangshi_raw.txt" OUTPUT_CSV = "tangshi_clean.csv" def parse_text(raw: str): lines = [line.strip() for line in raw.splitlines() if line.strip()] poems = [] i = 0 while i < len(lines): title = lines[i] # 标题行的特征:长度较短,且不以常见标点结尾 if not 1 <= len(title) <= 12: i += 1 continue author = "" body_lines = [] i += 1 # 下一行可能是作者,也可能是直接进入正文 if i < len(lines) and 1 <= len(lines[i]) <= 6: author = lines[i] i += 1 while i < len(lines): nxt = lines[i] # 遇到短行且像是新标题时,停止收集正文 if len(nxt) <= 12 and not nxt.endswith(("。", "!", "?", ";")): break body_lines.append(nxt) i += 1 poem_text = "".join(body_lines) if title and poem_text: poems.append({"title": title, "author": author, "body": poem_text}) return poems with open(SOURCE_FILE, encoding="utf-8", errors="ignore") as f: raw = f.read() poems = parse_text(raw) with open(OUTPUT_CSV, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=["id", "title", "author", "body"]) writer.writeheader() for idx, p in enumerate(poems, 1): writer.writerow({"id": idx, "title": p["title"], "author": p["author"], "body": p["body"]}) print(f"解析完成,共 {len(poems)} 首诗")

这段逻辑的核心在while循环的判断条件里:我把「短于等于 12 个字且不以句末标点结尾」的行视为下一首诗的标题。之所以用这个规则,是因为《唐诗三百首》的标题普遍是《感遇》《送别》这类不超过四个字的短题,而正文末行通常以句号、问号或分号收尾,两者特征差异明显。作者行则根据长度判断,超过六个字的行基本不可能是作者。这个方案的参数依赖原始文本的排版规范,如果换一个来源的文本,建议先跑一遍并打印所有被切出来的标题,人工核对。编码上,我统一用utf-8-sig写 CSV,这样用 Excel 打开时不会出现中文乱码,后面导入数据库时也少一层编码问题。

2.3 建表方案:单表够用,三表更规范

数据拆完,下一步是决定怎么存进数据库。常见做法有两种:一种是把全部字段塞进一张poems表,字段包括id、title、author、dynasty、body、volume;另一种是拆成poets表和poems表,诗人信息单独存放。对《唐诗三百首》这种体量的数据集,单表完全够用,三百多条记录在 SQLite 里查询是毫秒级,根本不需要为了范式而故意拆表。但如果你想把数据集做得更规范,或者后续打算扩充到《全唐诗》,那按作者拆表是更稳的方案。

建表时我一般会在 SQLite 里这样写:

CREATE TABLE poems ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, dynasty TEXT DEFAULT '唐', body TEXT NOT NULL, volume TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_poems_author ON poems(author); CREATE INDEX idx_poems_title ON poems(title);

字段类型上,title和author用 TEXT,id用 INTEGER 主键。由于古诗场景没有复杂的数值运算,不必用 VARCHAR(n) 来限制长度,TEXT 在 SQLite 里更省事。索引方面,author和title是查询高频字段,各建一个普通索引就够了。body字段不要建索引,因为对全文做模糊匹配时索引基本失效,这个在后面讲查询时会说。注意逗号分隔的 SQL 语句在数据库工具里执行没问题,但如果写在 Python 的sqlite3模块里,建议用executescript而不是execute,因为一条execute只能执行单条语句。

3. 用增删改查玩转这批数据集:导入、检索与常用 SQL 姿势

3.1 从 CSV 导入 SQLite 的完整姿势

上一步生成的tangshi_clean.csv还停在文件层面,接下来的标准动作是把它装进 SQLite 数据库。用 Python 的sqlite3模块是直接的方式,我一般不用命令行里的.import,因为 CSV 字段里可能带引号和换行,命令行导入的转义规则在这种数据上容易出问题。下面是常用的导入脚本:

import sqlite3 import csv DB_PATH = "tangshi.db" CSV_PATH = "tangshi_clean.csv" conn = sqlite3.connect(DB_PATH) cur = conn.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS poems ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, body TEXT NOT NULL, volume TEXT ) """) with open(CSV_PATH, encoding="utf-8-sig") as f: reader = csv.DictReader(f) rows = [(row["title"], row["author"], row["body"]) for row in reader] cur.executemany( "INSERT INTO poems (title, author, body) VALUES (?, ?, ?)", rows ) conn.commit() conn.close() print(f"已导入 {len(rows)} 条记录")

executemany是批量导入的关键,比逐条execute快一个数量级,而且能自动处理参数转义,避免 SQL 注入和引号冲突。注意csv.DictReader会把表头映射成字典键,如果第 2 章导出的 CSV 表头是id,title,author,body,这里就要对应取row["title"]、row["author"]、row["body"]。另外,导入前一定要先清空表或使用CREATE TABLE IF NOT EXISTS,否则重复运行脚本会插入重复数据。见过的翻车案例里,十有八九是反复执行导入脚本导致数据翻倍,这类问题很好排查,但对新手来说确实容易忽视。

3.2 五个高频查询:从「数据躺在库里」到「用起来」

数据导入后,最直接的回报就是可以用 SQL 优雅地做统计和检索。以课程设计或日常练手为例,我列几个高频查询场景。第一个是统计每位作者的收诗数量,这条 SQL 能直接看出《唐诗三百首》里谁的作品最多:

SELECT author, COUNT(*) AS cnt FROM poems GROUP BY author ORDER BY cnt DESC LIMIT 10;

第二个是全库模糊搜索。比如查「含有‘明月’的诗句」,用LIKE配合通配符即可。但这里有一个值得注意的细节:LIKE '%明月%'无法利用 B 树索引,全表扫描三百条记录也很快,可一旦数据量涨到三十万条,性能就完全不同。这正是为什么前面说body字段不要建索引,因为建了也白建:

SELECT title, author, body FROM poems WHERE body LIKE '%明月%';

第三个是「按作者 + 主题」的组合筛选,例如查李白作品中带有「酒」字的诗句。SQL 层面的组合条件本身不复杂,但它的意义在于展示了「结构化 + 模糊查询」的叠加效果,这在纯文本里做是很别扭的:

SELECT title, body FROM poems WHERE author = '李白' AND body LIKE '%酒%';

第四个是统计类查询,比如每卷收诗数量、每种诗体的平均字数。这类查询适合拿来做数据集质量校验。如果发现某个卷次只有两首诗,那大概率是文本分段出了问题:

SELECT volume, COUNT(*) AS cnt FROM poems GROUP BY volume ORDER BY cnt DESC;

第五个是随机抽诗。在课程设计里做一个「每日一诗」模块,核心就是一条随机排序查询。SQLite 的写法是ORDER BY RANDOM() LIMIT 1,MySQL 则用ORDER BY RAND() LIMIT 1。这种查询在数据量小的时候没有性能问题,但注意它同样不能用索引优化,不适合搬到线上大表:

SELECT title, author, body FROM poems ORDER BY RANDOM() LIMIT 1;

3.3 说好的增删改查,查已到位,剩三个也不能漏

一篇数据库课程设计报告只写 SELECT 会显得单薄,增删改查四个动作一般要齐活。INSERT 在前面导入脚本里已经出现过,这里补一条思路:如果你要手工录入一首新诗,字段要写完整,尤其是dynasty字段不要漏,否则统计时会出现「默认唐」和「空值」两本账。UPDATE 的场景不多,但常见于修正错别字或作者误标。比如发现「李白」被写成了「李百」,一条语句解决问题:

UPDATE poems SET author = '李白' WHERE author = '李百';

DELETE 要慎用。我见过有人写DELETE FROM poems;忘了加 WHERE 把整表清空的,事后只能靠备份恢复。建议在日常操作里严格遵守两条纪律:第一,执行 DELETE 前先写一条相同 WHERE 条件的 SELECT 确认范围;第二,导入阶段不要直接在生产表上操作,先建一张临时表验证数据,再改名。MySQL 和 SQLite 在ALTER TABLE ... RENAME TO ...上的语法略有差异,但基本思路一致:留后悔药,不留事故。

4. 常见问题与避坑:编码、断句和重复数据的三类血泪记录

4.1 CSV 导入后中文乱码

现象是数据库里能查到数据,但中文全部变成了「锟斤拷」或者一堆问号。原因通常有两个:原始 CSV 文件是 UTF-8 编码,而你在导入时没有显式声明编码;或者你没有给字段设置TEXT类型,而是用了BLOB。更隐蔽的情况是,在 Windows 上用 Excel 打开保存过 CSV,Excel 会把编码改成 ANSI(GBK),再拿这个文件导入 UTF-8 数据库就必乱。解决办法是统一在 Python 侧用encoding="utf-8-sig"读取 CSV,并且导入前就确认好数据库连接字符集。SQLite 不太挑字符集,但 MySQL 需要显式设置SET NAMES utf8mb4;才保险。

4.2 切分脚本把两首诗并成了一首

现象是解析后统计出来只有两百九十多条记录,而《唐诗三百首》全文应该是三百一十首左右(不同版本有差异)。打开数据看,发现某些诗被拦腰截断又接到下一首。原因在于我的标题判断规则(短行 + 不以句末标点结尾)在遇到某些五言绝句时失效了,因为上一首的末句恰好不是句号结尾,而是问号或感叹号,而我的断行条件只排除了句号、感叹号、问号和分号,没有排除省略号。解决的办法是扩大排除集,把省略号、破折号也加进去,并且在解析后立即做一次「标题长度全部小于 8 且正文长度全部大于 20 字」的校验,把异常记录挑出来手工核对。

4.3 同一首诗出现了多条记录

现象是《送元二使安西》在数据库里出现了两次,一次作者是「王维」,一次是「王維」。原因是原始文本来源不同,一份是简体版,一份是繁体版或半繁体版,切分后混在同一张表里。这类重复对统计结果影响很大,做「按作者统计」时王维的诗歌数量会虚高。解决思路分两层:先做繁体转简体,最简单的办法是在 Python 里用开源转换库统一转,或者做一层「标题 + 作者 + 首句」联合唯一键来去重。SQLite 支持在创建表时定义UNIQUE(title, author, body),导入时配合INSERT OR IGNORE可以自动剔除完全一样的重复行,但对繁简差异造成的「形近而实异」仍需先转换再导入。

4.4 LIKE 查询与预期不符

现象是查LIKE '%月%'时,「月」字被查出来了但有一批含「明」的诗没被命中,或者反过来查出一堆完全无关的记录。原因多半是原始文本里的标点符号是半角逗号和全角句号混用,导致正文被切分成了很多片段,某些「月」字恰好位于断行位置,两个字段拼接顺序出错。我的做法是清洗阶段统一做一次全角/半角标点归一化:把英文逗号、句号、问号全部替换成中文标点。这个操作放在解析之后、导入之前。另外,LIKE默认是不区分大小写的,但对中文没有意义,这一条不算坑,只是提醒你别在中文数据上对 LIKE 的大小写行为产生预期。

4.5 数据库文件打不开,或路径里带不出记录

现象是 SQLite 文件在 Python 脚本里能查,但换成数据库工具打开时看不到表,或者报「file is not a database」。原因往往是路径写错了,打开了同目录下的另一个同名文件;或者是用sqlite3.connect创建数据库时忘记写后缀.db,生成了一个无扩展名文件。这种情况在 Windows 上最隐蔽,因为资源管理器默认隐藏扩展名,两个文件看起来一样。解决方法是建立固定习惯:在脚本开头打印DB_PATH的绝对路径,导入后用PRAGMA integrity_check;做一次完整性检查。这条命令能快速判断文件是否被破坏,也可以用来验证备份是否可用。

5. 让数据集成作品:全文检索、指标校验与课程设计场景

5.1 用 SQLite FTS5 给「查诗句」加上全文检索

如果只是用 LIKE 做模糊匹配,数据量小没问题,但你想做「输入一个词,输出所有包含该词的诗句及其出处」这类检索时,LIKE 的体验就一般了。SQLite 自带全文检索扩展 FTS5,建一张虚拟表来承载body字段的索引,查询速度和 LIKE 完全不在一个量级。做法是给诗集数据建一张 FTS 表,导入时同步填充,查询时用MATCH语法:

CREATE VIRTUAL TABLE poems_fts USING fts5(title, author, body); INSERT INTO poems_fts (title, author, body) SELECT title, author, body FROM poems; SELECT title, author, snippet(poems_fts, 2, '【', '】', '…', 12) FROM poems_fts WHERE poems_fts MATCH '明月';

snippet函数会从正文中抽出命中位置附近的一小段文字,前后加上你指定的标记。这对做检索结果预览很有用。需要注意的点有两个:FTS5 表与普通表是独立的,源表数据更新后 FTS 表要同步更新;另外MATCH默认是按分词和短语匹配,中文分词效果有限,短词搜索时不如 LIKE 直接,但在长句和短语搜索场景下优势明显。

5.2 用统计指标验证数据集质量

查验数据集是否可用,不能只靠肉眼,下面这几个指标可以作为标准检查项。第一个是原始记录总数,与权威版本比对;第二个是body字段的平均长度,如果某条记录正文少于 20 个字,大概率是切分残缺;第三个是author字段为空的比例,正规数据里这个比例应该为 0;第四个是「标题重复」的组数,用来定位疑似重复数据;第五个是「作者数量」,唐诗三百首收录的诗人有七十余位,偏差过大就说明作者字段有问题。把这些指标写成一段 Python 脚本,每次清洗完数据自动跑一遍,能省下不少人工核对时间。

5.3 这套数据集在课程设计里怎么用

如果你是在做数据库课程设计,这套数据集几乎是现成的素材。前端可以做一个「诗人作品检索 + 每日一诗 + 主题词云」的简单页面,后端接 SQLite,查询就是前面写的那些语句,足够覆盖「设计报告」里要求的增删改查和索引设计说明。想增加区分度,可以在报告里讲清楚你为了清洗数据做了什么,不如从文本清洗、繁简转换、断句修正、去重和导入校验这几个维度各写一小节。

最后一件事是备份意识:无论库有多小,做完清洗就导出一份tangshi_clean.csv备份放在项目目录外,这是你的后悔药。数据库文件删了可以重建,原始清洗脚本丢了就只能从头再来。这是我做文本型数据集养成的习惯,写在这篇笔记里,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询