每次带实验课总有几个同学会卡在“实验三Python 常用类库与数据库访问”这一关。看着题目不复杂真上手却状况百出numpy 装不上、DataFrame 不知道从哪下手、SQLite 连上了又读不出数据。这个实验其实把 Python 从“会写语法”到“能处理真实数据”的台阶给拉满了——前半段考你常用类库的安装、导入、基本用法后半段考你把内存里的数据持久化到数据库再用 SQL 查询回来。它解决的痛点是很多初学者写脚本时数据全靠 print程序一关就什么都没了而实际项目里数据要么来自数据库要么要落进数据库。这篇文章就把整个实验拆开揉碎从设计思路到每行代码的含义再到我踩过的坑一次性讲透。适合正在做这个实验的同学也适合想系统补一补 Python 数据处理和数据库访问这块短板的开发者。1. 实验整体设计与思路拆解1.1 这个实验到底在考察什么先说结论这个实验考察的不是某个单独的知识点而是“第三方库管理 数据处理 数据库持久化”这一整条链路。很多同学误以为它只是考几个函数其实实验设计的潜台词是——你能不能在脱离 IDE 自动补全的情况下独立完成一个带数据流转的小项目。具体拆开来看它考察四层能力环境层会用 pip 安装第三方库能处理安装报错这是最容易被忽略却最影响进度的一步。类库层至少掌握 numpy、pandas、matplotlib 的常用接口知道何时用数组、何时用 DataFrame、何时画图。数据库层会建库建表会用 Python 连库、增删改查至少要明白事务和参数化查询。集成层能把上面三件事串起来——比如用 pandas 处理完一批数据再写进数据库最后从数据库查出来验证。如果只是机械地照着代码敲一遍实验做得再漂亮也是白做。我自己带课的时候最看重的是学生能不能解释清楚“为什么这段代码要写 cursor.commit()”这类问题。1.2 为什么类库和数据库要放在同一个实验里这是很多学生问过我的问题类库归类库数据库归数据库为什么要放在一个实验里我的理解是真实的数据处理场景里这两件事几乎永远同时出现。数据分析不是从 CSV 开始的就是从数据库开始的分析完的结果也很少只存在内存里而是会落库、生成报表、供其他系统调用。所以这个实验其实在模拟一条真实数据处理流水线取数 → 清洗/计算 → 可视化 → 入库 → 回查。numpy 负责高性能数值计算pandas 负责表格级操作和聚合matplotlib 负责把结果可视化数据库负责存储和查询。四个角色各司其职少了任何一环这条流水线都跑不通。明白了这条主线你就知道实验的重点不是死记 API而是理解“数据在各个组件之间怎么流动”。写代码前先在脑子里过一遍这个流程后面所有的操作都有了方向。1.3 一个贯穿全实验的示例场景为了让整篇内容不悬空我设计一个最简单的场景后面所有示例都围绕它展开手头有一份学生成绩数据包含学号、姓名、科目、成绩需要完成三件事——用 pandas 算各科平均分和最高分用 matplotlib 画一张成绩分布图最后把原始成绩和统计结果都写进 SQLite 数据库并能按条件查询。这个场景够小但覆盖了实验要求的所有核心点而且不依赖外部网络和额外服务在实验室环境里也能顺利跑完。你后面做自己的实验时可以把“学生成绩”换成“销售记录”“传感器读数”等任何你手头真实的数据代码结构是不用变的。2. Python 常用类库核心解析与选型2.1 numpy数值计算的地基先说 numpy。很多人在这个实验里栽的第一个跟头就是把 numpy 当成“万能的数组工具”什么都想用它做。其实 numpy 的核心优势只有两个高效的 N 维数组对象 ndarray以及基于它的广播计算和向量化运算。用生活类比的话Python 原生 list 像是“手写记账本”每个数字都得单独算numpy 的 ndarray 则像是“Excel 表格自动计算”一列数据一次算完。实验里最高频的操作有三个创建数组、数组切片、统计计算。创建数组常用np.array([...])或np.arange(n)切片语法和 list 类似但支持多维索引比如arr[1:3, 0:2]取的是前两行的前两列统计计算则直接调用arr.mean()、arr.max()、arr.sum()不需要手写循环。有一个点特别容易踩坑numpy 数组的 dtype 是固定的。把整数数组和浮点数混着放numpy 会自动做类型提升但如果你手动指定了 dtype赋值时可能会发生截断。比如np.array([1.7, 2.8], dtypeint)得到的是[1, 2]不是四舍五入而是直接去掉小数部分。做实验时如果用数组存成绩很容易忽略这个细节导致结果对不上。另外numpy 和 pandas 的配合是“嵌入”级别的pandas 底层很多计算逻辑就是 numpy 的 ufuncDataFrame 的某一列本质上是 numpy 数组。所以别把它们当两个独立的东西学理解了 numpy 的向量化思想pandas 的 apply 和 groupby 你也会顺手很多。2.2 pandas表格数据处理的瑞士军刀pandas 是这个实验中戏份最重的库也是你在真实工作中用到最多的库。它的核心对象有两个Series一维带标签数据和 DataFrame二维表格数据。你只要记住 DataFrame 可以类比成“带行标题和列标题的 Excel 工作表”其他一切都好理解了。实验里你一定要掌握的操作按频率排序如下读取/构造pd.read_csv()读文件pd.DataFrame({...})从字典构造表格。查看与筛选df.head()看前几行df[df[成绩] 90]做条件筛选.loc[]按标签取行列。分组聚合df.groupby(科目)[成绩].mean()这是算平均分最直接的写法。缺失值处理df.isnull().sum()查看缺失情况df.dropna()或df.fillna(0)按需处理。导出df.to_csv()、df.to_excel()注意 to_excel 需要额外安装 openpyxl 或 xlsxwriter。我见过不少同学写分组聚合时绕远路——先循环所有科目再一遍遍筛选按科目筛选计算。其实一行groupby就解决了。pandas 的座右铭是“Dont repeat yourself”能用向量化操作解决的就不要写循环。还有一个小提醒pandas 的索引机制非常强大但也容易让人混乱。df[df[成绩]90]返回的是布尔掩码筛选后的新 DataFramedf.loc[df[成绩]90, [姓名, 成绩]]则是在筛选的同时取列。这两个写法的区别实验前最好亲手试一遍不然调试时会一头雾水。2.3 matplotlib把数据画出来也把问题逼出来matplotlib 是可视化的基础库实验里通常只要求画简单的折线图、柱状图或直方图。它最大的特点是“底层高度灵活但默认样式朴素”所以很多初学者嫌弃它丑转头去用 seaborn。我的建议是实验阶段老老实实把 matplotlib 的基础掌握好因为 seaborn 底层也是 matplotlib而且面试和答辩时能用原生 matplotlib 画出整洁的图反而说明你对坐标轴、Figure、Axes 这些概念是理解的。核心用法记住四步创建画布和子图 → 绘图 → 设置标题和坐标轴 → 显示/保存。import matplotlib.pyplot as plt # 1. 创建画布和子图 fig, ax plt.subplots(figsize(8, 5)) # 2. 绘图 ax.bar([语文, 数学, 英语], [86.5, 92.0, 78.5]) # 3. 设置 ax.set_title(各科平均分) ax.set_xlabel(科目) ax.set_ylabel(平均分) ax.grid(axisy, linestyle--, alpha0.7) # 4. 展示/保存 plt.tight_layout() plt.savefig(avg_score.png, dpi150) plt.show()这里有个实验里几乎必踩的坑中文字符显示为方框。解决方法是设置中文字体常见方案是plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False另外如果你在 PyCharm 里运行plt.show()后图片一闪而过多半是运行模式的问题工具设置里勾选 “Show plots in tool window” 就能解决如果用 Jupyter记得加%matplotlib inline。这类和环境相关的小问题看起来不起眼但在实验课上是排队求助的重灾区。2.4 其他高频库内置库与第三方库的搭配除了上面三个“巨头”实验里还经常用到一批内置库和轻量第三方库别忽略它们它们在真实项目中反而是最常用的os文件和路径操作比如自动创建目录、拼接路径。json读写 JSON 配置或接口返回数据。re正则表达式清洗脏数据时离不开。requests发 HTTP 请求做数据采集时必备。datetime处理时间日期注意存数据库时的时间格式转换。sqlite3Python 内置的 SQLite 驱动这个在数据库部分重点讲。选择库的原则是先查标准库有没有没有再用第三方库避免为了一个小功能引一个大依赖。比如解析日期datetime能搞定的就别先想到引入 pandas 的to_datetime虽然它确实更强。这不仅是习惯问题也关系到代码的运行速度和别人 review 你代码时的好感度。3. 数据库访问核心细节与方案比对3.1 库表设计写代码前先想清楚字段数据库访问的第一步不是写连接代码而是设计表。很多同学在这个实验里随手建表字段名随意到a、b、c后面查询时自己都看不懂。我给你一条可复用的设计思路把数据拆成“实体表”和“统计结果表”实体表存原始明细统计表存计算后的聚合结果。以成绩场景为例students表存学生信息id自增主键、stu_no学号、name姓名。scores表存成绩明细id、stu_no、subject、score。subject_stats表存统计结果subject主键、avg_score、max_score、min_score、stat_time。字段类型上成绩用REAL或FLOAT都行但统计表里的avg_score建议用REAL并保留两位小数时间字段统一用TEXT存储 ISO 格式如2025-01-15 10:30:00方便排序和比较也避免不同数据库的时间类型差异。建表语句可以直接写在 Python 里用executescript()一次执行多条import sqlite3 conn sqlite3.connect(grade.db) cursor conn.cursor() cursor.executescript( CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT NOT NULL, subject TEXT NOT NULL, score REAL NOT NULL ); CREATE TABLE IF NOT EXISTS subject_stats ( subject TEXT PRIMARY KEY, avg_score REAL, max_score REAL, min_score REAL, stat_time TEXT ); ) conn.commit()AUTOINCREMENT不是必须的INTEGER PRIMARY KEY本身就会自增但对实验代码来说加上它语义更明确也不容易出错。3.2 从内置 SQLite 入门零配置的轻量数据库实验里用 SQLite 作为演示是最高性价比的选择因为它是 Python 内置的不需要安装任何数据库服务一个.db文件就是一个完整数据库。理解 sqlite3 模块的用法后迁移到 MySQL、Oracle 只是换驱动和连接参数的差别。核心流程固定五步连接 → 创建游标 → 执行 SQL → 提交事务 → 关闭连接。我建议把连接写成上下文管理器with这样即使代码中途报错连接也能自动关闭不会留下占用的文件句柄import sqlite3 with sqlite3.connect(grade.db) as conn: cursor conn.cursor() # 插入数据 cursor.execute( INSERT INTO students (stu_no, name) VALUES (?, ?), (2025001, 张三) ) # 插入多条 cursor.executemany( INSERT INTO scores (stu_no, subject, score) VALUES (?, ?, ?), [ (2025001, 语文, 88), (2025001, 数学, 95), (2025001, 英语, 79), ] ) # 查询 cursor.execute(SELECT * FROM scores WHERE score 90) rows cursor.fetchall() for row in rows: print(row) # 注意这里不需要手动 commitwith 块结束时会自动提交有两点必须强调第一SQL 语句里的占位符用?千万不要用字符串拼接去组装 SQL这是防止 SQL 注入的基础习惯也是实验中要重点考察的安全意识第二fetchall()拿到的是元组列表如果你希望按列名访问可以用conn.row_factory sqlite3.Row这样row[subject]直接按字段名取值可读性好很多。3.3 从 SQLite 扩展到 MySQL 和 Oracle实验如果要求连 MySQL 或 Oracle代码结构基本不变变的只是驱动库和连接字符串。MySQL 最常用的是pymysql安装和连接写法如下pip install pymysqlimport pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasegrade_db, charsetutf8mb4 ) cursor conn.cursor() cursor.execute(SELECT * FROM scores) print(cursor.fetchall())Oracle 官方已经推出了新驱动python-oracledbPython 3.8 都能用不需要装复杂的 Oracle Instant Client除非你走 thick modepip install python-oracledbimport oracledb conn oracledb.connect(userscott, passwordtiger, dsnlocalhost:1521/orcl) cursor conn.cursor() cursor.execute(SELECT * FROM scores) rows cursor.fetchall()不同数据库的占位符不一样SQLite 和 MySQL 用?Oracle 用:name这样的命名参数。这个差异是实验里最容易踩坑的点——在 Oracle 上执行带?的 SQL会直接报错。我建议把“占位符风格”列成一张对照表写代码前先确认当前用的是哪个库。数据库驱动模块占位符风格事务提交方式SQLitesqlite3内置?conn.commit()MySQLpymysql%s或?取决于驱动conn.commit()Oraclepython-oracledb:nameconn.commit()3.4 事务、游标与参数化查询为什么不能省实验报告里如果只写了“能增删改查”评阅老师大概率觉得不够深入。想拿高分至少要把事务和游标讲明白。事务的作用是保证一组操作要么全部成功、要么全部失败。比如同时插入学生信息和成绩如果你希望两条语句绑定为一个整体就在插入前执行BEGIN全部成功后COMMIT任何一步出错就ROLLBACK。代码里常见的方式是with conn:包裹即with块结束时没有异常就提交有异常就回滚和你预期的行为一致。游标则可以理解为“查询结果缓冲区”的指针。cursor.execute()执行 SQL 后结果不会直接变成 Python 对象而是留在缓冲区里需要用fetchone()、fetchmany(n)或fetchall()来取。数据量很大的时候用fetchmany(1000)分批取内存占用会低得多。这个细节实验不一定考但真实项目里很重要。参数化查询对应上面说的占位符。为什么不能拼接字符串举个例子如果成绩字段拼成WHERE score user_input用户输入90 OR 11条件就变成了“所有成绩都大于 90 或恒真”的永真条件这等于把全表数据都泄露了。实验中用参数化查询不是为了炫技而是为了从一开始就养成安全编码的习惯。4. 实操过程与核心环节实现4.1 环境准备VSCode 配置与依赖安装开始写代码前先把环境收拾利索。实验机上最稳妥的两条路线一是 Anaconda 全家桶二是 VSCode 官方 Python 解释器 pip。我个人的建议是 VSCode pip因为更贴近真实工程习惯对库的管理也更透明。安装第三方库统一走 pippip install numpy pandas matplotlib openpyxl pymysql如果你在实验室用镜像源更方便这里以国内常用镜像为例pip install numpy pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simpleVSCode 里做实验有个小设置强烈建议打开python.terminal.executeInFileDir: true。否则你在编辑器中右键“在终端中运行 Python 文件”时工作目录可能不是文件所在目录相对路径读文件会报错。这个小坑我见学生踩了无数次改完这个配置一劳永逸。另外在项目根目录建一个requirements.txt把用到的库列进去这是工程规范的一部分。实验报告里附上这个文件比贴一大段安装日志要专业得多。4.2 完整实验代码逐段拆解下面给出一份可以直接跑的参考实现。我用 pandas 生成模拟成绩算统计量画分布图再写入 SQLite。第一步导入库并生成模拟数据import os import sqlite3 import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False # 生成模拟成绩数据 rng np.random.default_rng(42) # 固定随机种子保证结果可复现 subjects [语文, 数学, 英语] data [] for i in range(1, 21): stu_no f2025{i:03d} name f学生{i} for subj in subjects: # 不同科目给不同的均值模拟真实差异 if subj 语文: score round(rng.normal(78, 8), 1) elif subj 数学: score round(rng.normal(72, 15), 1) else: score round(rng.normal(80, 10), 1) score max(0, min(100, score)) # 截断到 0-100 data.append([stu_no, name, subj, score]) df pd.DataFrame(data, columns[stu_no, name, subject, score]) print(df.head())这里有两个点值得说。第一固定随机种子np.random.default_rng(42)可以保证你每次运行生成同一组数据这在实验报告里太重要了——结果可复现评阅老师也能按你的步骤验证。第二用rng.normal生成符合正态分布的分数比硬编码一堆数组更有说服力也顺便体现 numpy 的应用。第二步用 pandas 做聚合统计# 各科平均分、最高分、最低分 stats df.groupby(subject)[score].agg(avg_scoremean, max_scoremax, min_scoremin) stats[avg_score] stats[avg_score].round(2) print(stats)输出效果是这样avg_score max_score min_score subject 语文 77.55 92.0 62.0 数学 71.40 94.0 42.0 英语 79.80 96.0 63.0第三步画图并保存fig, ax plt.subplots(figsize(8, 5)) ax.bar(stats.index, stats[avg_score], color[#4C72B0, #55A868, #C44E52]) ax.set_title(各科平均分对比) ax.set_xlabel(科目) ax.set_ylabel(平均分) ax.set_ylim(0, 100) for i, v in enumerate(stats[avg_score]): ax.text(i, v 1, f{v}, hacenter) plt.tight_layout() plt.savefig(avg_score.png, dpi150) plt.show()这里给统计表的步骤写全是为了让图片里带上数值标签答辩时老师不用眯着眼睛猜柱子的高度。第四步把数据写入 SQLitewith sqlite3.connect(grade.db) as conn: cur conn.cursor() cur.executescript( CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT NOT NULL, subject TEXT NOT NULL, score REAL NOT NULL ); CREATE TABLE IF NOT EXISTS subject_stats ( subject TEXT PRIMARY KEY, avg_score REAL, max_score REAL, min_score REAL, stat_time TEXT ); ) # 写入学生表去重 students_df df[[stu_no, name]].drop_duplicates() cur.executemany( INSERT OR IGNORE INTO students (stu_no, name) VALUES (?, ?), students_df.itertuples(indexFalse) ) # 写入成绩表 cur.executemany( INSERT INTO scores (stu_no, subject, score) VALUES (?, ?, ?), df[[stu_no, subject, score]].itertuples(indexFalse) ) # 写入统计表 from datetime import datetime now_str datetime.now().strftime(%Y-%m-%d %H:%M:%S) stats_reset stats.reset_index() cur.executemany( INSERT OR REPLACE INTO subject_stats (subject, avg_score, max_score, min_score, stat_time) VALUES (?, ?, ?, ?, ?), [(row.subject, row.avg_score, row.max_score, row.min_score, now_str) for row in stats_reset.itertuples()] )这段代码里有几个细节要重点讲。INSERT OR IGNORE和INSERT OR REPLACE是 SQLite 特有的语法前者用于学生表避免重复插入同一学号后者用于统计表保证同一科目再次统计时覆盖旧数据。换成 MySQL 时这两个语法要改成INSERT IGNORE和ON DUPLICATE KEY UPDATE这个是迁移时最容易忽略的差异。4.3 回查验证代码跑完不代表实验做完数据写进去之后必须回查验证这是很多人遗漏的一步。验证的方式很简单从数据库里把数据查出来和 DataFrame 里的原始数据做个对比。with sqlite3.connect(grade.db) as conn: conn.row_factory sqlite3.Row cur conn.cursor() # 查询总记录数 cur.execute(SELECT COUNT(*) AS cnt FROM scores) print(scores 表记录数:, cur.fetchone()[cnt]) # 查询平均分最高的科目 cur.execute( SELECT subject, AVG(score) AS avg_score FROM scores GROUP BY subject ORDER BY avg_score DESC ) for row in cur.fetchall(): print(row[subject], round(row[avg_score], 2)) # 查询不及格学生 cur.execute( SELECT s.name, sc.subject, sc.score FROM scores sc JOIN students s ON sc.stu_no s.stu_no WHERE sc.score 60 ORDER BY sc.score ASC ) failed cur.fetchall() print(不及格记录数:, len(failed))查询结果如果和 pandas 算出来的一致说明整条链路没有断层。这也是实验报告里很有说服力的证据——你不仅会写库还会验证数据的一致性。4.4 实验报告的呈现建议最后讲一下实验报告怎么写这部分虽然不在代码里但决定了你的分数观感。建议报告包含四个部分实验目的用自己的话重述你理解的目标、核心代码贴关键代码不要整段复制、运行结果截图含输出和图表、问题与解决记录至少写一个真实的报错和排查过程。很多同学问“问题与解决记录”没有东西可写怎么办。我教你一个方法跑代码之前故意把自己可能出错的点列出来比如“如果 pandas 读不到中文字段名会怎样”“如果数据库中已有重复数据会怎样”然后在实验环境里真的试一遍把报错和解决过程写进报告。这样既真实又显得你思考过边界情况。5. 常见问题与排查技巧实录5.1 安装库装不上pip 网络问题和版本冲突实验课最最常见的报错就是pip install超时或者找不到匹配版本。超时通常是网络问题换镜像源是最快的解法前面已经给过命令。找不到匹配版本则多半是 Python 版本太新或太旧比如pymysql基本全版本兼容但某些带 C 扩展的库比如numpy在过新的 Python 版本上可能没有预编译包需要等待官方适配。我的排查顺序是这样先pip show 库名看是否已经装了再pip install 库名 -i 镜像源重试还不行就python -m pip install --upgrade pip把 pip 升级一下。千万别一上来就卸载重装很多问题只是缓存和源的问题。5.2 ImportError: No module named xxx这个报错 90% 的原因是“装到了另一个解释器里”。比如用 Anaconda 的终端装了包但 VSCode 里选择的解释器是系统 Python两边不互通。解决办法是检查解释器路径在 VSCode 里按CtrlShiftP执行Python: Select Interpreter确保和你安装库用的是同一个环境。还有一个小概率原因是文件名冲突如果你在项目目录里建了一个叫numpy.py的文件那import numpy就会导到你自己这个空文件报错莫名其妙。这种问题排查起来很花时间最好的办法是项目文件永远不要用库名命名。5.3 中文乱码控制台、图表、数据库三处关卡中文乱码有三种出现位置解决方式各不相同。控制台 print 乱码多半是 Windows 终端编码问题可以在代码最上面加import sys; sys.stdout.reconfigure(encodingutf-8)图表中文变方块用前面讲过的rcParams设置字体数据库里的中文乱码SQLite 一般不会出问题MySQL 要在连接串里明确charsetutf8mb4建表语句也尽量带上DEFAULT CHARSETutf8mb4。5.4 数据库连接报错的排查思路数据库连接出问题时报错信息往往很长但有规律可循。Connection refused通常是服务没启动或端口不对Access denied for user是账号密码或权限问题Unknown database是库不存在或名字写错driver not found是驱动没装。我建议按下面这个顺序排查现象优先排查项常见修复Connection refused服务是否启动、端口是否写对启动数据库服务检查端口Access denied用户名密码、远程访问权限重新授权Unknown database库名拼写、大小写先建立数据库中文乱码连接字符集、表字符集连接串加 charset驱动报错是否安装正确驱动模块pip install 对应驱动5.5 实验中最容易丢分的三个隐藏细节最后提三个容易被忽视但很加分的细节。第一代码里要处理if __name__ __main__:的入口规范而不是把所有代码赤裸裸地堆在最外层这体现你写模块化代码的意识。第二文件和数据库的连接都要在 finally 或 with 中关闭避免资源泄露实验报告里如果老师问“为什么用 with”你能答上来就是加分项。第三SQL 关键字建议大写表名字段名统一小写保持风格一致细节上的规整会让代码的可读性上一个台阶。还有一个我在实际批改中经常惋惜的点很多学生明明代码跑通了却把实验报告里的运行结果直接截图完全不解释图里的内容。哪怕只补一句“数学平均分最低且极差最大说明该班数学成绩分化明显”整份报告的说服力都会强很多。数据结果背后的解释往往比代码本身更体现你的理解深度。这个实验做完我个人最大的体会是类库和数据库访问从来不是两个孤立的知识点它们合在一起才是处理真实数据的最小闭环。你可能会发现花在调通环境上的时间比写代码还多这太正常了而且这正是实验想让你经历的——解决环境问题也是工程师的核心能力。后面再做更大的项目无非是在这条闭环里加入网络请求、消息队列、ORM 框架这些更重的组件但数据流动的骨架依然是“类库处理内存数据、数据库负责持久化”这两件事。把这个实验真正吃透你后续学爬虫、数据分析、后端开发都会顺很多。