ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python后端+微信小程序:计算机考研刷题平台开发全复盘

2026/9/15 6:18:30 拓冰建站 浏览量
Python后端+微信小程序:计算机考研刷题平台开发全复盘 去年教研室的学弟问我考研计算机专业课那么多题网上刷题 App 要么收费要么题目老能不能自己搞一个 python 后端 微信小程序的刷题平台我当时正在整理 408 的错题心想这确实是个刚需——数据结构、操作系统、计算机网络、计算机组成原理四门课每门课的题目分散在各种公众号、网盘和纸质资料里。于是花了大概一个月业余时间把这个“计算机考研刷题平台”从零搭了起来后端用 python 写 API前端是微信小程序后来还开放给了班里的同学一起用。这篇博文不是课程式教程更像一次项目复盘。我会把题库从哪来、接口怎么设计、小程序端交互怎么做、上线前后踩了哪些坑全部交代清楚。适合三类人看想用 python 练全栈项目的人、准备拿“微信小程序考研刷题”做毕业设计的人、以及单纯想给自己做个刷题工具的学生。1. 题库从哪来又怎么存被忽略的“原料”工程1.1 题目数据的来源选择与版权边界很多人做刷题平台一上来就写登录注册、答题页面最后发现所有功能都做好了却没有题可刷。题库才是这个项目的原点。我做的时候明确划了几条线自己手工录入从学校图书馆借的王道、天勤等教材里的经典例题这类题目本身用于学习交流录入后仅限内部分享通过公开渠道整理考研回忆版真题这部分数据要标注“回忆版”不能直接声称是官方真题小组同学把自己整理的错题共享出来形成一个小型共享题库。为什么特别强调版权因为考研教辅是有版权的题目解析也是作者智力成果。如果将来这个平台要对外发布、甚至商业化题目来源必须合规。很多“免费python源码大全”里的爬虫项目直接抓取其他 App 的付费题库这种代码我建议不要拿来做自己的毕设或产品风险太大。1.2 采集脚本的协程改造从串行到并发从公开网页收集题目时最直接的方式是用 requests BeautifulSoup 写一个串行爬虫把每个页面的题目解析出来。串行脚本写起来简单但页面一多就很慢一个晚上只能抓几百页。后来我把题库采集改成了 asyncio aiohttp 的协程版本速度提升非常明显。这里有个基础概念要区分爬虫是 IO 密集型任务大多数时间花在等网络响应上协程可以在等待时主动让出控制权去处理另一个请求而多进程更适合 CPU 密集型任务比如对题目文本做批量正则匹配、聚类分析。很多初学者把线程、进程、协程混为一谈选型时就容易踩坑。import asyncio import aiohttp from bs4 import BeautifulSoup async def fetch_one(session, url): async with session.get(url) as resp: text await resp.text() return parse_question(text) async def main(): async with aiohttp.ClientSession() as session: tasks [fetch_one(session, url) for url in url_list] results await asyncio.gather(*tasks) save_to_db(results)注意控制并发数并设置重试与延迟避免给目标站点带来压力。这是我跑采集脚本时最深刻的一条体会并发确实快但过快会把对方服务器打挂换个 IP 可能都救不回来。1.3 题库表设计题目、选项、知识点三张核心表题库数据结构直接决定后端代码怎么写。我的核心表分三张subject科目、question题目、question_tag知识点标签。question 表的关键字段包括subject_id、question_type单选/多选/判断/简答、content题干、optionsJSON 数组、answer、analysis、difficulty、source、created_at。这里有个经验options 用 JSON 数组存储而不是拆成 option_a、option_b 四列。因为考研题除了四选一还有多选、判断题拆列的话多选和判断的存储逻辑会很别扭。JSON 存起来后端读出后直接传给小程序端渲染前端代码也省事。如果希望做选项级统计可以在读取时 parse 成 Python list再按需处理。另一个细节是 answer 字段。单选存 ‘A’判断存 ‘T’/‘F’多选存 ‘ABD’ 这种字符串后端统一约定为字符串格式前端渲染时再转成数组。数据清洗阶段我遇到过答案里混着空格、小写字母、中文括号的脏数据必须做 python 类型转换和字符串规整统一转大写、去空格、把中文括号替换成英文括号否则最终对答案时会莫名出错。1.4 数据清洗时那些容易被忽略的脏数据人类录入的题目脏数据远比想象中多。我举几个实际遇到的例子题干里有 HTML 标签p、strong之类直接渲染到小程序里会出现标签串和样式错乱内容里有 LaTeX 公式比如$\sum_{i1}^{n} i$小程序 rich-text 不支持需要预处理成图片或使用特定插件答案里有“正确答案是A”这种描述性文本换行符被存成了\r\n在不同端展示时会出现多余空行。清洗时我用正则 HTML 解析库处理第一类用统一的公式规则处理第二类对第三类做关键词归一化。做完清洗后我加了一个很笨但很有效的验证脚本把所有题目的答案枚举出来统计每种答案分布发现某张卷子 80% 都选 C立刻意识到这组数据八成是乱录入的于是重新补验。这一步看似和“刷题平台”没关系但它决定了用户刷题体验的下限。题目都是错的平台功能再花哨也没人用。2. 后端接口不是简单CRUD刷题状态机的设计思路2.1 技术选型对比FastAPI 是刷题场景的最优解吗后端我选的是 python 的 FastAPI而不是 Django 或 Flask。对比很直接框架适合场景对刷题平台的适配点Django大而全的管理系统自带 Admin、ORM、鉴权但偏重写 API 略笨重Flask轻量接口、自由度高的项目灵活但要自己拼装鉴权、文档FastAPI高频读写 API、异步密集型服务异步原生、Pydantic 校验、自动生成 Swagger 文档刷题平台的核心场景是“短请求 高频刷题 答案校验”请求体以 JSON 为主FastAPI 的请求模型校验能把很多前端传参错误挡在业务逻辑外面。另外它的自动文档让小程序端联调时省了很多口舌前端同学直接打开/docs就能看到每个接口的字段定义。有一点我要说明如果项目是给课程毕设用的Flask 也完全够不必为了“流行”硬上 FastAPI。关键是接口设计思路对——框架只是工具状态机设计才是灵魂。2.2 核心接口清单与鉴权设计我把接口控制在一个很小的范围内前端页面再多接口也就十几条。核心的几条如下接口方法作用关键参数/api/v1/subjectsGET获取科目列表无/api/v1/questions/randomGET获取一组随机题目subject_id, count/api/v1/questions/{id}GET获取题目详情id/api/v1/answersPOST提交答案并返回判定结果question_id, selected_option/api/v1/wrong-bookGET获取错题本status, page/api/v1/stats/dailyGET获取学习统计date/api/v1/exam/startPOST开启一次模拟考试subject_id, question_count鉴权方面微信小程序登录走wx.login拿到 code后端拿 code 调用微信接口换 openid 和 session_key再签发自己的 token。这里有个新人常犯的错不要直接把 openid 暴露给前端当用户 ID容易泄露用户信息也不利于后续扩展。正确做法是后端生成自增 user_id 或 UUIDopenid 只在内部存数据库。提交答案接口是核心请求体用 Pydantic 定义前端传错类型直接返回 422能节省大量联调时间。2.3 为什么“答题状态”要用有限状态机来建模刚开始我处理做题记录时写的逻辑很直接提交答案对了就插一条记录错了再插一条错题记录。结果过几天发现同一个用户对同一道题做了五次错题本里出现了五条重复记录统计正确率时也不知道以哪条为准。后来我把“刷题”理解成一个有限状态机每道题对当前用户只有几个状态——未做、已做对、已做错、已收藏。每次提交答案不是新增记录而是把状态转移未做 → 做对answer_records 新增一条正确记录未做 → 做错新增一条错误记录同时错题本状态置为待复习做错 → 做对更新 answer_records 的最新结果错题本状态转为已掌握做对 → 做错保留历史正确统计但错题本重新激活。为了实现这种转移我用 SQLAlchemy 维护answer_records表并加了唯一约束(user_id, question_id, date)保证一天内同一道题只保留一条最新记录权重统计靠correct_count、wrong_count两个字段累加。这样无论前端怎么重复提交后端都能给出稳定结果。2.4 统计报表的聚合查询与缓存策略个人中心的“今日刷题数、正确率、连续打卡天数”是刷题平台最让人有成就感的功能但也是最容易写坏的功能。正确率不能在前端算——前端拿到的只是分页题目算出来的正确率是局部的会忽高忽低。正确做法是后端聚合查询SELECT COUNT(*) AS total, SUM(CASE WHEN is_correct 1 THEN 1 ELSE 0 END) AS correct_count FROM answer_records WHERE user_id :uid AND created_at :start_of_day;连续打卡天数则不能靠“今天有没有记录”硬推因为有人会跨时区、有人凌晨还在刷题。我在user_study_days表里记录“学习日”每天只生成一行再通过连续日期比较来计算 streak。计算时注意时区统一接口返回的日期时间都按北京时间处理避免小程序端与服务器 UTC 时间差导致的天数错位。数据量上来之后统计接口每次实时 count 会比较慢我加了 Redis 缓存key 设计为stats:{user_id}:{date}5 分钟过期用户刷完一题后主动删除对应 key下一次请求再重建缓存。这样既保证数据新鲜又不会频繁打数据库。如果后端要生成学习趋势图用 matplotlib 时 x 轴日期标签别直接全量显示日期太密就会糊成一团设置每 7 天一个刻度这种抽稀策略是常见做法。3. 小程序端刷题交互从选项点击到状态更新的完整链路3.1 页面结构规划五个Tab背后的心智模型微信小程序端我规划了五个底部 Tab首页、刷题、错题本、统计、我的。这个结构完全按照刷题用户的心智模型来首页展示科目入口、每日一题、学习提醒刷题核心练习区按科目或知识点选题组卷错题本按科目筛选错题支持复习和重做统计正确率、刷题曲线、连续打卡我的登录信息、设置、导出错题等。页面结构别贪多五个 Tab 已经能覆盖 90% 的刷题诉求。每多一个页面都要多维护一份路由和交互状态。新手最容易犯的错是一上来就设计“好友排行”“组队学习”这类社交功能结果核心刷题流程反而做得稀烂。错题本里的排序我用了长按拖拽滚动长按某个错题项进入可拖拽状态手指上下滑动时通过 touchmove 事件动态更新列表顺序松开后调用后端接口保存新排序。这个交互对“把最需要优先复习的题目排前面”很实用但也容易在 iOS 上出现列表抖动需要在 touchmove 里做节流。3.2 答题页的选项交互单选框的反馈时机与状态管理答题页是整个小程序交互密度最高的页面。我用原生的 radio-group radio 实现选项但注意刷题场景里的“单选框”和普通表单里的单选框体验完全不同。普通表单是选完再提交而刷题是“选中即判定”所以不能只依赖 radio 的 change 事件还要在 data 里维护做题状态。关键状态我放在一个对象里管理data: { currentIndex: 0, question: null, selected: , // 当前选中项 submitted: false, // 是否已提交 result: null // correct 或 wrong }用户点击某个选项时触发handleTapOption先把选中项写入 selected然后立刻显示对错反馈再展示解析。这里有个体验细节点击选项后要禁用其他选项防止用户连续点击导致状态错乱。等用户看完解析点击“下一题”再清空 submitted 重置状态。3.3 setData动态路径与页面数据同步很多人在写刷题记录时会碰到一个尴尬场景题目列表在 data.questions 里用户做完了第 3 题要更新questions[2].userAnswer。新手会写this.setData({ questions: newQuestions })把整个数组重新赋值这在大列表下性能很差。正确做法是用 setData 的动态路径this.setData({ [questions[ index ].userAnswer]: selectedValue, [questions[ index ].status]: correct });你甚至可以动态拼接多层路径比如stats[2025-06-01].count。这套写法在答题状态更新、收藏标记、统计信息同步时特别省心而且微信小程序的 setData 只更新指定路径性能远比整段数据覆盖好。网上常见的问题this.setData({ userinfo.nickname: that.data.nickname })也是同一个原理——通过字符串路径精确更新某个字段避免影响其他数据。提示setData 的更新路径是字符串注意不要拼错引号否则页面数据不会更新而且很难从报错里看出来。3.4 启动体验优化用分包和懒加载摊平首屏时间小程序第一版的启动页是我随便写的封面图后来发现很多用户打开后要等 2 秒才看到首页查看 Network 才发现主包太大——题库、图标、部分页面全塞进了主包。后来我做了两步优化一是分包。把“错题本”“统计”“模拟考试”这些低频页面放进subPackages主包只保留首页和刷题页。用户在 Tab 间切换时子包按需加载启动速度明显提升。二是减少启动时请求。原来首页在 onLoad 里同时请求科目列表、每日一题、用户信息三个接口后来改成先渲染科目列表数据量小每日一题用占位图延迟加载。用户感知到的首屏速度是“看到内容”的时间而不是“所有内容都加载完”的时间。微信小程序的 lazyCodeLoading 配置项也建议打开可以让 JS 按需注入避免一次性解析所有页面代码。另外如果做自定义导航栏顶部导航栏高度不能写死要用wx.getWindowInfo动态获取状态栏高度和胶囊按钮位置不同机型差异很大。3.5 题目富文本渲染公式、图片与代码块的处理考研计算机题目最大的特点是题干里全是数据结构图和代码片段。表结构示意、二叉树遍历、时间复杂度公式每一类都有不同渲染需求。小程序里有两个方案rich-text原生组件或者引用mp-html之类的富文本解析库。rich-text对基础 HTML 支持还行但遇到table或复杂嵌套标签时表现不佳mp-html则支持更多标签并提供扩展接口可以在渲染图片时做点击预览。我的经验是数据库里的题干不要存渲染后的 HTML而是存结构化的 Markdown 或纯文本 特殊标记。后端在返回题目时根据不同的内容类型做转换代码块用pre包裹公式图片用标准 URL 替换。这样小程序端渲染逻辑简单后端以后要生成 PDF 试卷或导出 Excel 也能复用同一套数据。个别场景下比如要内嵌一个在线代码编辑器小程序可以用 webview 组件承载 h5 页面与 h5 之间的通信一般通过 postMessage 等机制传递但 webview 加载和通信成本较高刷题页面能用原生组件就别轻易用。4. 上线前调试与踩坑实录从抓包到渲染异常的排查链路4.1 本地调试charles抓包定位接口异常小程序开发最常见的问题是页面表现和接口数据对不上。你看前端代码觉得逻辑没问题后端日志又显示请求正常这时候就需要用抓包工具看真实请求和响应。我在本地用 charles 做 HTTPS 抓包手机和小程序开发者工具都走同一个代理能清晰地看到每个请求的 URL、请求头、参数和返回体。举个例子有次用户反馈“登录后头像一直加载不出来”。前端排查半天没发现问题抓包一看原来是后端返回的 JSON 里用户头像字段名是avatar_url而小程序端读的是avatarUrl字段名不一致导致静默失败。这种问题在联调文档不完善时非常常见抓包能直接暴露出来。另外提醒一下抓包工具是开发调试的辅助手段可以分析自己开发的小程序或学习公开技术文档不要去抓取其他商业应用的敏感流量用于非法用途。4.2 iOS渲染机制特殊scroll-view里的选择器为什么错位这个坑让我印象特别深。上线后第一个 iOS 用户反馈模拟考试页里点击日期时间选择器弹层跑到了页面顶部甚至有些按钮点不到。我一开始以为是样式问题后来排查发现是渲染机制差异。iOS 端微信小程序的页面基于 WKWebView 渲染部分原生组件或复杂组件的层级、定位在scroll-view这种滚动容器里会出现异常。比如 uni-datetime-picker 这类日期时间选择器组件被放在scroll-view内Android 上正常iOS 上就可能出现错位、遮挡或点击失效。排查链路是这样的先在 Android 真机上复现发现正常锁定为 iOS 专属问题在 iOS 开发者工具里模拟仍然无法稳定复现改用真机调试用wx.createSelectorQuery()打印组件的 boundingClientRect发现其在页面上的实际位置和预期位置偏差很大孤立测试把选择器从 scroll-view 里挪出来放到普通 view 下问题消失最终方案把日期时间选择器改成页面级弹层并重新设计滚动容器避免在 scroll-view 内嵌复杂交互组件。后来我才知道这类问题在模拟考试、长列表筛选里都会出现。所以我现在写小程序有个习惯复杂交互组件尽量不进滚动容器要么用页面滚动要么用弹层承载。4.3 错题导出excel后端生成文件与小程序端保存错题本是刷题平台的沉淀功能但很多用户想把错题打印出来复习。于是我做了一个导出 Excel 的功能。后端用openpyxl生成 xlsx 文件每个 Sheet 对应一个科目列有题目内容、选项、正确答案、解析、错误次数。生成之后把文件写到临时目录返回一个下载 URL。小程序端不能直接下载保存文件到任意目录一般是先用wx.downloadFile下载到临时路径再调用wx.openDocument预览用户需要保存时可以通过右上角菜单另存或者在有文件管理能力的平台上调用保存接口。这个功能看起来简单实际有两个坑中文文件名在 part 头里要转成 URL 编码否则部分客户端下载时文件名乱码导出耗时如果超过几秒用户容易无感。我加了生成任务队列点击“导出”先返回“生成中”生成完成后通过订阅消息或轮询通知用户。小数据量直接同步导出问题不大题库大了就要做异步任务。4.4 包体积限制下的题库下发策略微信小程序主包大小限制很严格早期是 2MB总包 20MB 左右现在限制以官方文档为准。题库数据如果全塞进小程序几道数据结构大题带图片分分钟爆包。我的策略是本地只缓存最近一次刷题记录、每日一题等少量数据完整题库全部通过接口获取。后端接口返回题目时按subject_id和难度分页每次只返回 10 到 20 题。用户在刷题时前端做一个简单的预取队列当还剩 3 题时自动请求下一组题目这样既不会一次性拉大量数据又能保证滑动不中断。图片资源单独走 CDN尺寸在录入时就压缩到合适宽度。不要在高清大图上做文章考研题干里的图大多很小压缩到 750px 宽以内完全够用还能大幅降低加载耗时。4.5 合规意识的红线不逆向、不搬运他人代码写小程序项目时很多同学会看到网上有“一键反编译微信小程序”之类的工具或者去下载免费源码大全然后改个 Logo 就上线。这里我要泼一盆冷水反编译别人的小程序属于逆向工程可能违反平台规则和相关法规直接搬别人题库和界面设计也有版权风险。刷题平台这类教育项目尤其要谨慎题库和内容是核心资产来源不清会给自己留隐患。合规的做法是优先使用自己录入的数据、公开授权的开放数据集、或原创教学内容的自主采集。技术上可以学习别人的设计思路但实现要自己写。这个项目能顺利上线很大程度上是因为一开始就把内容合规放在了前面。5. 上线后回头看刷题平台的架构余量与后续规划5.1 性能与容量题库接口的缓存设计上线两周后题库 5000 道题日活几十人数据库毫无压力。但我还是把缓存做了因为一届考研学弟学妹同时刷题时热点题目和统计接口很容易成为瓶颈。项目部署在 linux 服务器上时不要图省事把 python 依赖直接装进系统环境建议用虚拟环境或 docker否则依赖版本冲突会让整个应用起不来。当前线上的缓存策略科目列表缓存 1 小时随机组题按科目 题目 ID 列表缓存组题时直接从缓存里随机抽取避免每次都ORDER BY RAND()这种全表扫描的写法用户统计缓存 5 分钟写操作时失效每日一题按日期缓存。另外我把题目权重做了简单分层难题和易错题的出题概率更高。这样用户刷题时不会全是简单题也不会觉得太难。这套设计支撑几百人同时在线没什么问题。如果未来用户量级再涨可以上消息队列做答题记录异步落库但现阶段没必要过度设计。答题数据积累到一定量级后还可以用 sklearn 的层次聚类对错题知识点做聚合分析找出“一错错一片”的知识模块这是我最想做的下一版功能。5.2 多端复用从微信小程序到H5与App很多人在做完微信小程序后会想把它扩展到 H5 或其他 App 端。我在开发时特意把后端 API 设计成纯 JSON RESTful不依赖微信生态的客户端能力这意味着换一个前端壳子就能复用。如果想多端复用有两条路用原生微信小程序继续维护另起 H5 项目共用后端 API从一开始就用 uni-app 开发代码编译到微信小程序、H5、App 等多端。uni-app 的生态比较成熟开发时可以通过 HBuilderX 创建和管理项目编译到微信小程序时uniAPI 会映射成微信的wxAPI一些跨端组件如日期时间选择器在不同端的行为可能不一致需要多端回归测试。如果项目只面向微信内用户原生小程序足够如果想覆盖更多渠道uni-app 是性价比更高的选择。我目前把后端 API 文档维护得很干净前端以后想换成 uni-app 写或直接做毕业设计展示对接成本都很低。5.3 题目管理后台的必要性刷题平台做了三个月后我发现最累的不是写代码而是维护题库每周录入新题、修正错题解析、调整知识点标签。如果只靠直接操作数据库迟早会出错。所以我后来加了一个简单的管理端用 FastAPI Jinja2 模板实现题目录入表单支持富文本粘贴批量导入功能支持 Excel / CSV 模板题目检索与编辑用户反馈入口看到“这道题答案存疑”的留言能快速修改。这个管理端不需要很漂亮但一定要能减少人工操作。对于考研刷题平台这种内容驱动的产品“内容运营效率”往往比“功能数量”更重要。如果有同学拿这个做毕设强烈建议加一个管理端答辩时也比较容易讲清楚完整闭环。5.4 给准备入坑全栈开发的人几句实在话最后说几句题外话。我见过太多人一上来就学各种框架、找免费源码大全却连 python 基础语法还没过一遍。做全栈项目先把 Python 的变量、类型、函数、类基础打牢再谈框架。环境装不好的网上 python 安装教程很多照着做就行。然后是心态。这个刷题平台真正做到可用的版本大概花了三周。第一周在搭数据模型和采集脚本第二周在写后端接口第三周在磨小程序交互。中间无数次想放弃最后发现坚持下来后前后端串起来的成就感远超预期。如果你是准备毕业设计选这个方向我的建议是把题库质量、答题交互、学习统计三个闭环做好比堆一堆没用的功能更打动人。这些内容打通了自然就能讲出一套完整的技术故事。根据我个人的经验这类工具型项目最怕的是“什么功能都想加”。每次冒出新的想法先问自己一句如果只能保留三个功能我会留哪三个对这个刷题平台来说答案永远是题库、答题、错题沉淀。把这三件事做到极致比任何华丽的界面都更能留住用户。