Python+Flask构建四六级单词助手:微信小程序与艾宾浩斯记忆曲线的实践
发布时间:2026/10/1 4:14:27来源:尧图网络
如果你也准备四六级估计你用过不少背单词App——广告弹窗、会员墙、算法推荐一堆花里胡哨的功能反而让人静不下心背单词。我自己备考时被这些干扰整烦了干脆用 Python 做了一个四六级英语单词助手小程序。说是小程序其实核心是 Python 搭的服务端负责词库管理和记忆曲线计算前端用微信小程序承载微信里随手打开就能背不需要装 App、不用看广告。这篇文章我把整个设计和实现过程完整拆给你从需求梳理、技术选型、数据库设计、核心算法、后端接口到小程序关键页面的联动和上线踩坑全程可复现。不管是刚学 Flask 的小白还是想给小程序加个后端的老手这篇都值得你花几分钟看完。1. 项目背景与需求梳理这本单词助手到底要解决什么1.1 现实中背单词的三个痛点先说清楚我为什么要做这个而不是直接用现成产品。第一市面上的背单词工具功能堆叠严重签到、排名、社区、动画效果真正核心的科学复习反而被稀释。第二普遍采用订阅制付费对在校学生不算友好。第三我想把词库和复习策略完全掌握在自己手里——今天想背四级高频词明天想刷六级真题词后天想让某个词多复习几遍都得能灵活调整。自己做就不一样。我的目标很纯粹打开小程序看今天该学哪些新词、复习哪些旧词点一遍卡片记录记忆情况完事。复杂交互全部砍掉把精力放在后端算法和数据结构上这恰恰是 Python 最擅长的地方。1.2 功能优先级排序做项目最忌讳一上来追求大而全。我按必须做、应该做、先不做把功能排了三档优先级功能说明P0 必做单词查询与释义展示包含音标、词性、例句、发音入口P0 必做每日学习计划每天分配新词和到期复习词P0 必做记忆状态反馈用户对单词标记认识/模糊/忘记P1 应该做测验模式用选择题检验掌握程度P1 应该做打卡与学习统计记录每日学习量、正确率P2 暂不做社区打卡、排行榜和核心目标无关不做这个思路很重要。刚开始我先做 P0跑通全流程后再加测验和统计。如果你第一次做类似项目强烈建议也这样规划别让功能清单拖垮进度。2. 技术选型与整体架构为什么是 Python 服务端加微信小程序2.1 前端方案横向对比标题叫python小程序但前端小程序这个词在 2025 年的语境下最自然的载体就是微信小程序。单纯用 Python 写桌面端当然也可以但我的使用场景很明确上下课路上、图书馆排队、宿舍睡前掏出手机就想刷一组词不可能背着电脑开 PyQt 程序。对比下来大概是这么个情况方案优点缺点微信小程序 Python 后端免安装、即开即用、分享方便前端要写 JS/WXML不能全栈 PythonPyQt 桌面端全 Python离线运行开发周期短需要安装环境手机端没法用Flask 渲染网页全 Python部署简单手机上要存书签体验偏浏览器Flutter / RN App跨平台、体验接近原生开发成本高需要应用商店上架最终我选了微信小程序 Flask 后端。注意一个认知误区小程序的小程序只是前端外壳所有核心逻辑包括词库管理、复习计划计算、数据统计全都在 Python 服务端。对我这种习惯 Python 的人来说等于把复杂业务都放在最顺手的语言里处理前端只是做展示和数据采集。2.2 后端框架选型后端框架我直接选了 Flask而不是 Django。理由很现实项目规模没到必须用 Django 的重量级程度Flask 一个文件就能启动路由写法直观第三方扩展按需加载。四六级单词助手这种轻中量 API 服务Flask 完全不虚。到后期如果数据量上来了再加 SQLAlchemy 做 ORM、加 Redis 做缓存过渡也平滑。数据库用了 SQLite 起步。个人项目、小程序初期用户量小SQLite 单文件部署省心不需要单独装数据库服务。等日活真做到几千再迁移 PostgreSQL 也不迟表结构和 SQL 大体兼容迁移成本不高。2.3 整体架构图整个项目按下面这套逻辑跑用户打开微信小程序通过 wx.login 拿到临时 code小程序把这个 code 发给 Python 后端后端调微信接口换 openid返回自定义 token小程序每次请求带上 token访问单词、计划、记录、统计四类 API后端从 SQLite 读取词库和学习记录按艾宾浩斯记忆曲线算出今日复习列表返回给前端前端渲染单词卡片用户点认识/模糊/忘记后把结果提交回后端后端更新复习时间。这套架构的好处就是把算法和业务集中在 Python 侧小程序端保持无脑展示。我只要修一份后端代码所有用户立即生效不需要发版审核。3. 单词数据与数据库设计词库清洗和表结构一次讲清3.1 词库来源与清洗四六级单词助手没有词库就是空壳。词库我优先从开源途径找GitHub 上有不少整理好的 CET4/CET6 词库 JSON 文件十有八九是爬下来的或人工整理的有音标、词性、中文释义更完整的还有例句。找到之后不能直接入库必须做清洗去重同一个单词可能出现在多个词单里按字母序去重去除噪音词比如纯数字、缩写、人名地名占位符补全字段部分词条缺音标或缺词性能补的补不能补的先标记前端展示时做降级处理分级四级词表和六级词表拆成两个数据集用户可以在设置里切换。清洗完成后我会输出成两个 JSON 文件cet4.json 和 cet6.json每条数据长这样{ word: abandon, phonetic: /əˈbændən/, part_of_speech: v., meaning: 放弃抛弃, example: He abandoned his car in the snow., level: cet4, category: 高频 }词库文件直接用 Python 脚本批量导入数据库一行命令的事。这里提醒你一个细节千万不要把词库写死在程序代码里一定要拆出来你要随时能更新和重新导入。3.2 核心数据表设计数据库我设计了五张表下面这张表是核心中的核心表名关键字段作用usersid, openid, nickname, created_at用户信息openid 唯一标识wordsid, word, phonetic, meaning, example, level, category单词本体静态数据learning_recordsid, user_id, word_id, review_count, next_review_at, last_seen_at, mastery_level用户与单词的学习交集动态数据daily_plansid, user_id, plan_date, new_word_ids, review_word_ids每天计划提前生成保存daily_statsid, user_id, stat_date, new_words, reviewed_words, correct_rate每日学习统计注意 users 表里我没存手机号、密码这类隐私信息。小程序端只需要 openid这是微信用户在当前小程序内的唯一身份后端不接触任何敏感认证数据安全压力小很多。learning_records 是成长记录表每个用户对每个单词只有一条记录字段不断更新复习次数累加、下次复习时间重置、熟练度打分。而 daily_plans 是把算好的计划存下来避免每天重复计算。3.3 索引与并发考虑learning_records 这张表是查询最频繁的表尤其查询条件是 user_id next_review_at 的组合这两个字段必须建联合索引CREATE INDEX idx_user_review ON learning_records (user_id, next_review_at);如果不建索引用户学了一个月、记录上万条之后每次拉取今日复习列表都会全表扫描体感明显变慢。SQLite 默认支持索引直接在初始化脚本里加就行。并发方面SQLite 写入是串行的所以我开了 WAL 模式提升读写并发能力PRAGMA journal_modeWAL;这个在项目的初始化连接里设置一次即可。4. 核心算法艾宾浩斯复习点是怎么算出来的4.1 遗忘曲线和复习间隔背单词 App 的核心竞争力不是 UI而是什么时候让你复习这个词。心理学上的艾宾浩斯遗忘曲线告诉我们记忆不复习就会指数级衰减但每次成功回忆后衰减速度会变慢。落到工程实现上就是给每个单词设定一组递增的复习间隔。我采用的复习间隔序列是当天、第 2 天、第 4 天、第 7 天、第 15 天、第 30 天。也就是说一个新单词从第一次学习开始如果每次都标记为认识它会在这些时间点反复出现。只要用户完整跟完这六轮长期记忆基本就稳了。4.2 计算下一个复习时间用 Python 写这个逻辑非常直白from datetime import datetime, timedelta INTERVALS [0, 2, 4, 7, 15, 30] # 0 表示当天 def get_next_review_at(review_count: int, base_time: datetime) - datetime: if review_count 0: review_count 0 index min(review_count, len(INTERVALS) - 1) days INTERVALS[index] return base_time timedelta(daysdays)这里的 review_count 是用户对该单词的成功复习次数。第一次学完review_count 从 0 变成 1下次复习间隔取 INTERVALS[1] 即 2 天。第二次复习成功review_count 变成 2下次间隔取 7 天的位置等一下这里我写过多个版本最初是直接取下标后来发现一个问题INTERVALS[0] 0 是当天第 1 次复习完成后 review_count 1应该取 INTERVALS[1] 2 天等第 2 次复习完成review_count 2取 INTERVALS[2] 4 天。这个逻辑确实没问题但为了让代码更好读我把下标偏移封装成了一个函数避免在业务代码里到处写魔法数字。4.3 标记忘记时怎么调整如果用户标记忘记说明这个词还没进长期记忆。我的策略不是简单重置 review_count而是给他一个降级 插队复习方案先把 master_level 下调一级然后把 next_review_at 改为 30 分钟后也就是今天晚些时候再出现一次。如果再错就说明这个词需要进入重点词名单以后每天计划额外插入一个重点词槽位。这个设计比单纯重置更有温度。你可以类比成健身某个动作做得不标准教练不会让你退回第一节课重新学而是让你今天就多做两次纠正动作第二天继续观察。这样既尊重已有记忆痕迹又强化薄弱环节。4.4 每日计划生成每日计划我用了一个后台函数在每天早上生成def build_daily_plan(user_id, plan_date): new_words pick_new_words(user_id, plan_date, count20) review_words pick_review_words(user_id, plan_date, count60) save_plan(user_id, plan_date, new_words, review_words)新词挑选逻辑是优先选 category 为高频的词再按 word_id 顺序补充避免总是从字母 A 开头背到 C。复习词挑选逻辑是所有 next_review_at 小于今天的记录按到期顺序全部纳入最多 60 个如果超过 60 个超过部分顺延到明天。这套逻辑保证了计划不是拍脑袋随机而是有数据依据的。5. 后端 API 实现Python 接口逐段拆解5.1 项目结构先把后端项目结构摆出来建议你直接照抄这个目录word_helper/ ├── app.py # Flask 应用入口 ├── config.py # 配置常量 ├── models.py # 数据表模型 ├── auth.py # 登录与 token 校验 ├── api/ │ ├── __init__.py │ ├── words.py # 单词相关接口 │ ├── plan.py # 每日计划接口 │ ├── records.py # 学习记录接口 │ └── stats.py # 统计接口 ├── data/ │ ├── cet4.json │ └── cet6.json └── requirements.txt # Flask, requests, gunicorn入口文件 app.py 做了三件事初始化数据库、注册蓝图、启用跨域支持。跨域问题很容易被忽略但小程序请求域名和本地调试域名不同后端不加 CORS 头浏览器预览小程序时会直接失败。5.2 单词查询接口单词查询是最简单的接口入参是单词 ID 或拼写from flask import Blueprint, request, jsonify words_bp Blueprint(words, __name__) words_bp.route(/api/words/int:word_id) def get_word(word_id): word query_word_by_id(word_id) if not word: return jsonify({code: 404, message: word not found}), 404 return jsonify({code: 0, data: word})返回的 data 里包含音标、释义、例句、词性。前端拿到后直接渲染卡片。这里不返回所有字段像 category 字段在前端不需要透出能减小包体。5.3 学习记录提交接口这是整个项目最重要的接口。用户每学完一个单词前端会提交一个动作动作包含三要素word_id、action认识/模糊/忘记、当前时间戳。records_bp.route(/api/records, methods[POST]) def submit_record(): data request.get_json() user_id get_current_user_id(request) word_id data.get(word_id) action data.get(action) if action not in (know, blur, forget): return jsonify({code: 400, message: invalid action}), 400 record get_or_create_record(user_id, word_id) record.review_count record.review_count 1 next_time calculate_next_review(record, action) record.next_review_at next_time record.last_seen_at datetime.now() save_record(record) return jsonify({code: 0, data: {next_review_at: next_time.isoformat()}})这里的 get_or_create_record 逻辑要处理新用户第一次见到旧词的情况。判断规则是如果该用户对这个词没有记录就创建一条并把 review_count 设为 0。动作know加一次成功复习次数blur保持次数不变但缩短下次间隔forget降级处理。务必用数据库事务包住读取和写入防止并发提交时出现重复记录。5.4 登录与鉴权小程序的身份验证跟普通网页不同。用户在小程序端 wx.login 拿到 code后端拿 code 去微信接口换 openiddef code_to_openid(code): url https://api.weixin.qq.com/sns/jscode2session params { appid: APP_ID, secret: APP_SECRET, js_code: code, grant_type: authorization_code } resp requests.get(url, paramsparams).json() return resp.get(openid)换到 openid 后后端自己签发一个 token 返回给前端之后每次请求都带着这个 token。token 我用的方案是最简单的 UUID 存 Redis 或 SQLite 表里过期时间设为一周。虽然不如 JWT 标准但胜在实现简单、可控性强个人项目完全够用。这边提个醒APP_SECRET 绝对不要写在小程序代码里只放在服务端环境变量中。5.5 统计接口统计接口按日期聚合 daily_stats 表stats_bp.route(/api/stats/date) def daily_stats(date): user_id get_current_user_id(request) stats get_daily_stats(user_id, date) return jsonify({code: 0, data: stats})前端首页要展示的今日已学 15 词、复习 10 词、正确率 80%就靠它。统计数据的更新不需要每次都全量重算我是在学习记录提交接口里顺便更新 daily_stats一条 UPDATE 语句的事。6. 小程序前端关键页面与联动6.1 首页计划卡片和打卡状态小程序首页是用户进来的第一眼我把它做成一张今日任务卡。卡片上展示三个数字今日新词数、今日复习数、已完成数下面放一个大的开始学习按钮。数据来自 /api/plan 接口页面在 onShow 里拉取因为用户每次回到首页时计划可能已经变化了。这页代码里最需要注意的其实是状态同步。用户背完一组词回来首页的数字必须立刻更新。做法是在开始学习跳转前记录状态学习页返回时重新请求一次计划接口。千万不要把完成状态存在前端本地——我今天背过的词明天换个手机登录也得能看见所以一切以服务端为准。6.2 背单词卡片页核心交互闭环卡片页是最核心的页面。我一次展示一个单词上半部分是单词、音标和发音按钮下半部分是释义用户看完释义后点击认识/模糊/忘记三个按钮的其中一个然后自动进入下一个词。真正的精髓在先显示释义再出按钮这个交互设计。我一开始做的是三个按钮和单词同时出现结果用户很容易不看释义直接凭感觉点认识。后来改成释义先展示 2 秒、按钮才出现正确率统计立刻真实了很多。关于发音我用了微信的 wx.createInnerAudioContext音频地址来自一个公开的单词发音接口后端只需要在词库里存一个发音 URL 字段。6.3 测验页验证掌握程度测验我没做成大题库而是直接基于用户学习记录出题从近期学过的词里随机抽 10 个每个词出一道选择题正确答案和三个干扰项都从词库生成。干扰项的选取逻辑是找相同词性或相近字母开头的词避免一眼看出答案。测验结束之后要把结果写回后端更新该词的正确率。这一步也是检验复习算法效果的依据。如果某个词的测验正确率长期低于 50%它就应该被重新塞进复习队列。前端代码里有一个小技巧测验过程中把用户选择先暂存在本地交卷时一次性提交减少接口请求次数避免弱网环境下反复失败。7. 上线前踩过的坑与经验复盘7.1 微信小程序合法域名和 HTTPS上线第一步就被卡了个跟头小程序正式版要求所有请求域名必须配置在公众号后台的开发管理-服务器域名里并且强制 HTTPS域名还得备案。本地调试时开发者工具默认是关闭合法域名校验的但一旦预览到手机上就会失败。如果项目还没买域名和 HTTPS 证书预览阶段可以临时勾选不校验合法域名但提审前一定要换上备案过的 HTTPS 域名并且接口服务要配好证书。7.2 SQLite 并发写入的坑小程序用户量不大时 SQLite 没问题但多个人同时提交学习记录偶尔会出现database is locked。解决方案就是前面提到的 WAL 模式外加连接超时设置conn sqlite3.connect(app.db, timeout10) conn.execute(PRAGMA journal_modeWAL)如果以后用户量再涨直接切 PostgreSQLmodels.py 里的表结构基本不用动把连接配置换掉就行。这也是我当初没把表结构设计成 SQLite 专属命名的原因——字段都用通用类型迁移省事。7.3 单词发音接口的选择发音是最容易忽略又最影响体验的一块。有现成的公共 TTS 接口可以用但公共接口的稳定性和发音准确度全网参差不齐。我的做法是给词库预生成离线音频文件存到对象存储词表里直接存 CDN URL。这样线上播放不依赖第三方接口波动。如果你只想快速验证先用公共 TTS上线前再批量替换。7.4 请求防重与幂等设计学习记录提交接口如果前端网络抖动用户可能点了一次认识后端却收到了两次请求导致复习次数虚增。解决办法是在提交接口上做幂等前端每次提交时带一个 request_id 随机字符串后端如果发现该 request_id 已存在直接返回上一次的成功结果。这种设计虽然多几行代码但避免了很多脏数据用户数据长时间积累后尤其重要。做完整套项目回头看我最满意的不是代码量而是把学习算法握在自己手里的感觉。市面上的背单词工具大多把算法和词库藏在黑盒后面用户只能被动接受安排。我做的这个 Python 服务端可以随时看数据、调参数、加功能今天想让六级词每天多出现一次明天想把某个词标记为重点改一行代码重启服务就生效这种掌控感是第三方工具给不了的。这个项目本身的扩展空间也很大接上每日真题词频、加入听力跟读训练、按用户易错词生成专属测验卷都是在现有算法框架上增量开发的事。如果你也打算做类似工具建议先把记忆曲线和数据结构想清楚再碰界面你会少走很多弯路。
网站建设高端定制企业官网