首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >WorkBuddy 辅助开发随机试卷生成系统:800 题题库秒出卷,openpyxl 踩坑全记录

WorkBuddy 辅助开发随机试卷生成系统:800 题题库秒出卷,openpyxl 踩坑全记录

原创
作者头像
用户12597793
修改2026-07-02 15:32:30
修改2026-07-02 15:32:30
30
举报

WorkBuddy 辅助开发随机试卷生成系统:800 题题库秒出卷,openpyxl 踩坑全记录

背景:培训需要定期组织考试,手动从 800 道题库里抽题出卷,一次至少半小时。用 WorkBuddy + Python 做了个自动化工具,点击即出卷。


目录

  1. 需求背景:为什么需要这个工具
  2. 系统设计:从题库到试卷的完整链路
  3. 核心实现:WorkBuddy 辅助写代码的过程
  4. 踩坑记录:差点放弃的三个坑
  5. 部署运行:双击 .bat 一键出卷
  6. 总结:两周 AI 辅助开发的真实感受

一、需求背景

我们部门每月要组织一次专业培训考试,考试范围是 800 道题的题库。以前出卷的流程是这样的:

  1. 打开 Excel 题库,800 道题全在一个表格里
  2. 手动拉滚动条,随机挑 50 道单选 + 30 道多选 + 20 道判断
  3. 复制粘贴到 Word,调排版、加页眉页脚
  4. 再做一份答题卡、一份标准答案

整个过程最快也要半小时,如果题目类型要调整、数量要变化,基本重新来一遍。而且有时候要用 A 题库,有时候要用 B 题库(不同题库的 Excel 格式还不完全一样),切换起来更是噩梦。

想做一个自动化工具很久了,但自己从头写 Python 脚本——查文档、调试、处理中文编码问题——想想都觉得烦。后来试了 WorkBuddy,发现一句话描述需求,它就能给你整出来,效率完全不同。


二、系统设计

2.1 功能需求

跟 WorkBuddy 聊了几个回合,把需求敲定下来:

功能

说明

题库读取

从 xlsx 文件读取题目,支持单选/多选/判断三种题型

多题库切换

支持切换不同题库文件,不同格式自动适配

随机抽题

指定每种题型抽多少道,不重复抽取

试卷输出

生成格式化的试卷 xlsx + 答案 xlsx

PDF 导出

试卷导出为 PDF,带页眉页脚(单位名称、考试日期)

2.2 技术选型

这次选型很简单,WorkBuddy 直接帮我锁定了方案:

  • openpyxl — 读题库 xlsx、写试卷 xlsx,一个库搞定
  • random.sample — Python 内置,随机抽样不重复,简单可靠
  • reportlab / openpyxl 打印 — PDF 输出

不需要数据库、不需要 Web 框架,就一个 Python 脚本,双击运行。

2.3 题库格式

A 题库的 Excel 结构(接触网 800 题):

代码语言:javascript
复制
| 序号 | 题型     | 题目                    | 选项A    | 选项B    | 选项C    | 选项D    | 答案 |
|------|---------|------------------------|---------|---------|---------|---------|------|
| 1    | 单选题   | 接触网额定电压是?       | 25kV    | 27.5kV  | 35kV    | 10kV    | B    |
| 2    | 多选题   | 接触网由哪些组成?       | 支柱    | 腕臂    | 接触线  | 钢轨    | ABC  |
| 3    | 判断题   | 接触网是高压设备         | —       | —       | —       | —       | √    |

B 题库的格式略有不同(多了一列"知识点分类"),需要做兼容处理,这是后面的一个坑点。


三、核心实现

3.1 题库读取 + 自动适配

这是整个系统的基础。WorkBuddy 帮我写了读取函数,并且加了一个「自动探测表头」的逻辑,这样不同格式的题库也能自动适配:

代码语言:javascript
复制
from openpyxl import load_workbook
from pathlib import Path

def load_question_bank(file_path: str) -> dict:
    """读取题库 xlsx,返回按题型分类的题目列表"""
    wb = load_workbook(str(Path(file_path)))
    ws = wb.active
    
    # 读表头,自动建立列名→列号的映射
    headers = {}
    for col_idx, cell in enumerate(ws[1], 1):
        if cell.value:
            headers[cell.value.strip()] = col_idx
    
    # 根据实际列名匹配字段(兼容不同题库的表头叫法)
    col_map = {}
    for field, aliases in [
        ('type', ['题型', '题目类型', '类别']),
        ('question', ['题目', '题干', '问题']),
        ('A', ['选项A', 'A', '选项 A']),
        ('B', ['选项B', 'B', '选项 B']),
        ('C', ['选项C', 'C', '选项 C']),
        ('D', ['选项D', 'D', '选项 D']),
        ('answer', ['答案', '正确答案', '参考答案']),
    ]:
        for alias in aliases:
            if alias in headers:
                col_map[field] = headers[alias]
                break
    
    # 按题型归类
    banks = {'单选题': [], '多选题': [], '判断题': []}
    for row in ws.iter_rows(min_row=2, values_only=False):
        qtype = str(row[col_map.get('type', 1) - 1].value or '').strip()
        if qtype not in banks:
            continue
        
        question = {
            'question': str(row[col_map.get('question', 2) - 1].value or ''),
            'A': str(row[col_map.get('A', 3) - 1].value or ''),
            'B': str(row[col_map.get('B', 4) - 1].value or ''),
            'C': str(row[col_map.get('C', 5) - 1].value or ''),
            'D': str(row[col_map.get('D', 6) - 1].value or ''),
            'answer': str(row[col_map.get('answer', 7) - 1].value or '').strip(),
            'type': qtype,
        }
        banks[qtype].append(question)
    
    return banks

这里 WorkBuddy 给了一个很巧妙的思路——用别名表做表头匹配,不同人做的 Excel 表头叫法可能不一样(比如有人写"题型",有人写"题目类型"),用别名映射后不管怎么叫都能对上。

3.2 随机抽题 + 去重

核心就一句话,但细节不少:

代码语言:javascript
复制
import random

def generate_paper(bank: dict, config: dict) -> list:
    """从题库中按配置随机抽取题目"""
    questions = []
    
    for qtype, count in config.items():
        if qtype not in bank or len(bank[qtype]) < count:
            raise ValueError(f"「{qtype}」题库只有 {len(bank.get(qtype, []))} 题,"}
                             f"要求抽取 {count} 题,不够!")
        selected = random.sample(bank[qtype], count)
        questions.extend(selected)
    
    # 打乱题型顺序,让试卷看起来自然
    random.shuffle(questions)
    return questions

这里有个小细节:如果题库里某类题型不够抽,直接报错而不是默默少抽——这是 WorkBuddy 建议加的,避免生成不完整的试卷。

3.3 试卷输出到 Excel

生成的试卷要排版清爽,可以直接打印:

代码语言:javascript
复制
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, Border, Side

def export_paper(questions: list, output_path: str, title: str = "专业培训考试试卷"):
    """将抽取的题目输出为格式化的试卷 xlsx"""
    wb = Workbook()
    ws = wb.active
    ws.title = "试卷"
    
    thin_border = Border(
        left=Side(style='thin'),
        right=Side(style='thin'),
        top=Side(style='thin'),
        bottom=Side(style='thin')
    )
    
    # 标题行
    ws.merge_cells('A1:F1')
    title_cell = ws.cell(row=1, column=1, value=title)
    title_cell.font = Font(size=16, bold=True)
    title_cell.alignment = Alignment(horizontal='center', vertical='center')
    
    # 信息行
    info_row = 2
    ws.cell(row=info_row, column=1, value="姓名:").font = Font(size=11)
    ws.cell(row=info_row, column=3, value="部门:").font = Font(size=11)
    ws.cell(row=info_row, column=5, value="得分:").font = Font(size=11)
    
    question_num = {'单选题': 0, '多选题': 0, '判断题': 0, '总计': 0}
    row = 4  # 从第4行开始输出题目
    
    for q in questions:
        question_num['总计'] += 1
        question_num[q['type']] += 1
        
        # 题目
        ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6)
        num = question_num[q['type']]
        if q['type'] == '单选题':
            prefix = f"{question_num['总计']}. 【单选】"
        elif q['type'] == '多选题':
            prefix = f"{question_num['总计']}. 【多选】"
        else:
            prefix = f"{question_num['总计']}. 【判断】"
        
        qcell = ws.cell(row=row, column=1, value=f"{prefix} {q['question']}")
        qcell.font = Font(size=11)
        qcell.alignment = Alignment(wrap_text=True)
        ws.row_dimensions[row].height = 25
        row += 1
        
        # 选项(判断题不显示选项)
        if q['type'] != '判断题':
            for opt in ['A', 'B', 'C', 'D']:
                if q.get(opt):
                    ws.cell(row=row, column=1, value=f"  {opt}. {q[opt]}").font = Font(size=11)
                    row += 1
        
        row += 1  # 题间空行
    
    # 单独输出答案表
    ws_ans = wb.create_sheet("参考答案")
    ws_ans.cell(row=1, column=1, value="题号").font = Font(bold=True)
    ws_ans.cell(row=1, column=2, value="题型").font = Font(bold=True)
    ws_ans.cell(row=1, column=3, value="答案").font = Font(bold=True)
    
    for i, q in enumerate(questions, 1):
        ws_ans.cell(row=i+1, column=1, value=i)
        ws_ans.cell(row=i+1, column=2, value=q['type'])
        ws_ans.cell(row=i+1, column=3, value=q['answer'])
    
    wb.save(output_path)

四、踩坑记录

坑1:中文路径导致 openpyxl 报编码错误

现象: 题库文件放在 题库/接触网题库.xlsx 这种中文路径下,load_workbook() 直接报错。

排查过程: 把文件移到英文路径就没问题,确认是中文路径的问题。

WorkBuddy 给出的方案:

代码语言:javascript
复制
# ❌ 会报错
wb = load_workbook("题库/接触网题库.xlsx")

# ✅ 用 pathlib.Path 包装一下
from pathlib import Path
wb = load_workbook(str(Path("题库/接触网题库.xlsx")))

同样的问题也出现在 wb.save() 时,统一用 Path() 处理搞定。

坑2:A 题库和 B 题库格式不一样

A 题库的表头是 | 序号 | 题型 | 题目 | 选项A | ... |,B 题库多了一列 "知识点",而且"题目"叫"题干"。

WorkBuddy 说直接用之前写的那个别名表自动匹配就能解决,果然没问题。适配代码在前面 3.1 节已经贴了,核心就是 aliases 表。

坑3:判断题抽了 20 道,但题库里只有 15 道

第一次跑的时候报了错:「判断题」题库只有 15 题,要求抽取 20 题。回去一翻题库,判断题确实一共就 15 道。

WorkBuddy 给的建议是在配置文件里加一个校验,抽题前先检查题库够不够,不够就自动调整:

代码语言:javascript
复制
def smart_config(bank: dict, desired: dict) -> dict:
    """智能调整抽题配置:如果某题型不够,自动降到题库上限"""
    adjusted = {}
    for qtype, count in desired.items():
        available = len(bank.get(qtype, []))
        adjusted[qtype] = min(count, available)
        if adjusted[qtype] < count:
            print(f"⚠️ {qtype} 题库仅有 {available} 题,已自动调整为 {adjusted[qtype]} 题")
    return adjusted

五、部署运行

配置文件

用 JSON 配置,改抽题数量不用改代码:

代码语言:javascript
复制
{
    "题库路径": "题库/接触网题库.xlsx",
    "抽题配置": {
        "单选题": 40,
        "多选题": 20,
        "判断题": 15
    },
    "试卷标题": "接触网专业培训考试试卷",
    "输出目录": "output/"
}

启动脚本

代码语言:javascript
复制
@echo off
cd /d %~dp0
python paper_generator.py
echo.
echo 试卷已生成在 output 目录下
pause

双击 生成试卷.bat → 3 秒出卷 → output/ 目录下拿到试卷 xlsx + 答案 xlsx。


六、总结

实际效果

  • 出卷时间: 从 30 分钟 → 3 秒
  • 题库切换: 切换题库文件就行,自动适配格式
  • 防错机制: 题库不足自动提示,不会生成残缺试卷

用 WorkBuddy 写这个工具的感受

  1. 一句话描述 → 直接出代码 — 我不需要记住 openpyxl 的 API 怎么用,直接说"帮我读 xlsx 题库,按题型分类",它就把完整函数写好了
  2. 踩坑解决快 — 中文路径报错、题库格式兼容这些问题,以前要搜半天,现在直接问,几秒钟给方案
  3. 代码比我自己写的好 — 别名表自动适配、配置文件分离这些思路,我自己可能想不到,但 WorkBuddy 会主动建议

当然也有局限性:生成出来的代码不会一次性完美,偶尔需要微调——但比起从零开始写,效率已经不是一个维度了。

如果你也在做类似的内部小工具,强烈推荐试试 Python + openpyxl + WorkBuddy 这个组合。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • WorkBuddy 辅助开发随机试卷生成系统:800 题题库秒出卷,openpyxl 踩坑全记录
    • 目录
    • 一、需求背景
    • 二、系统设计
      • 2.1 功能需求
      • 2.2 技术选型
      • 2.3 题库格式
    • 三、核心实现
      • 3.1 题库读取 + 自动适配
      • 3.2 随机抽题 + 去重
      • 3.3 试卷输出到 Excel
    • 四、踩坑记录
      • 坑1:中文路径导致 openpyxl 报编码错误
      • 坑2:A 题库和 B 题库格式不一样
      • 坑3:判断题抽了 20 道,但题库里只有 15 道
    • 五、部署运行
      • 配置文件
      • 启动脚本
    • 六、总结
      • 实际效果
      • 用 WorkBuddy 写这个工具的感受
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档