大数跨境
0
0

Python防作弊出题系统V1.0来啦!每位考生的试题都不一样,再也无法抄袭

Python防作弊出题系统V1.0来啦!每位考生的试题都不一样,再也无法抄袭 科技码源
2024-06-13
1
导读:这才是效率:10秒出题30套。每套题的题序不仅发生了变化,而且选择题选项的顺序在每套题中也是不一样的,这样就会产生不同的答案​,能有效防止学生抄袭​。

前言

前一段时间给大家分享了一系列有关VBA编程的文章,如《VBA编程——批量重命名文件》《VBA编程——快速生成三好、合格奖励学生名单》《VBA编程——秒做奖励决定附件》《VBA编程——通过自定义函数实现十万阿拉伯数字和中文数字的转换》,感受到了VBA功能的强大。VBA的优势在于依托Office而存在,不需要额外安装编程软件,编程后的程序大约只有几十kB,体积小,便于存储。但VBA和Python相比,其编程功能仅仅是Python的冰山一角。今天我们就用Python来编写一款防作弊出题系统。

正文
因为教学工作反复性极强,重复的工作较多,所以就想通过编程来解放劳动力,让教学一线的老师得到充分减负。然而能用于教学一线的辅助软件少之又少,而且有很多都是收费的,且有些功能和老师们预期的有差异。这时,就想着自己动手编一些程序,来帮助老师们减轻工作量。
就在前一段时间,同一办公室的董老师提到,他耗时整整两天,给学生出了一套AB卷数学题。什么是AB卷?AB卷就是为了防止学生作弊,出了两套相似但又有差异的试卷,可以有效杜绝学生抄袭答案。我瞬间被这位老师的敬业精神所折服。后来就想着,如果能让电脑辅助老师们来出试卷,出题效率不仅会大幅度提升,而且还会批量产生ABCD……XYZ等多个版本的试题,远比AB卷防作弊的效果要好得多。

这才是效率:10秒出题30套
借助Python强大的编程功能,耗时一周,终于编成了这款防作弊出题系统,10秒出题30套,效率极高。
再来看看所出的题的质量如何?以下展示的是试题1(试卷编号:20240613225930001),注意对比试卷编号。

为了有效对比试卷内容的差异,我再随机选一套试题:试题23(试卷编号:20240613225940023)。

不知道大家看清楚差异了没有?这是用一套原始的《简单机械单元检测题》,批量生成了30套不同的试题。就拿选择题来说,选择题的题序不仅发生了变化,而且选项的顺序在每套题中也是不一样的,这样就会产生不同的答案,能有效防止学生抄袭。
因为每套题都有不同的答案,所以在出题时,每一套试题都编有试卷编号,同时也配套生成了对应答案的Word版,方便老师阅卷。
现将Python防作弊出题系统V1.0版本Python源代码作以分享!




Python防作弊出题系统V1.0代码


import randomfrom openpyxl import load_workbookfrom docx import Documentfrom docx.shared import Pt ,Cm # 用于设定字体大小(磅值)from docx.oxml.ns import qn  # 用于应用中文字体from datetime import datetimefrom docx.enum.text import WD_ALIGN_PARAGRAPHfrom PIL import Image# 不重复随机整数生成函数def Random_num(maxNum, questNum):    num_list = []  # 储存生成的随机数    if questNum == maxNum :        num_list = list(range(2,maxNum + 2))        random.shuffle(num_list)    else:        while len(num_list) < questNum:  # 控制随机数的个数            num = random.randint(2, maxNum + 1)  # 设定在此范围内取数            if num in num_list:  # 判断随机数是否重复                continue  # 若重复,则重新生成            else:                num_list.append(num)  # 将不重复的随机数放入列表    return num_list  # 生成完成后返回随机数列表# 定义函数,按随机数在题库中抽取对应编号的题目
def Question(que_type, numbers): ''' numbers:需要抽取的试题编号 ''' questions = [] # 储存抽取的题目 wb = load_workbook("题库.xlsx") # 载入题库 right_answer = '' if que_type == "单选题": ws = wb[que_type] for i in numbers: # 按随机生成的编号抽题 options = [] question = ws.cell(i,2).value # 问题在B列 options = [ws.cell(i,3).value,ws.cell(i,4).value,ws.cell(i,5).value,ws.cell(i,6).value] random.shuffle(options) answerA = "A.\t" + str(options[0]) # "\t"相当于按一下tab键 answerB = "B.\t" + str(options[1]) answerC = "C.\t" + str(options[2]) answerD = "D.\t" + str(options[3]) if ws.cell(i,7).value == 'A': right_answer = ws.cell(i,3).value if ws.cell(i,7).value == 'B': right_answer = ws.cell(i,4).value if ws.cell(i,7).value == 'C': right_answer = ws.cell(i,5).value if ws.cell(i,7).value == 'D': right_answer = ws.cell(i,6).value
if right_answer == str(options[0]): right_answer = 'A' if right_answer == str(options[1]): right_answer = 'B' if right_answer == str(options[2]): right_answer = 'C' if right_answer == str(options[3]): right_answer = 'D' pic_address = ws.cell(i,9).value single_question = [question, answerA, answerB, answerC, answerD, right_answer,pic_address] # 每行的数据存入列表 questions.append(single_question) # 每个题目的数据存入总列表
else: # 判断题和填空题,内容只取题干和答案 ws = wb[que_type] for i in numbers: question = ws.cell(i,2).value right_answer = ws.cell(i,3).value pic_address = ws.cell(i, 5).value single_question = [question, right_answer,pic_address] questions.append(single_question) wb.close() return questions
# 写入考试题到word文件def To_word(number, questions_data,examNum): doc = Document("试题模板.docx") p = doc.add_paragraph() # 插入段落 p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐 r = p.add_run(examNum) # 插入文字块 r.bold = False # 字体加粗 r.font.size = Pt(10.5) # 字号设为10.5磅——五号 r.font.name = '宋体' # 控制是西文时的字体 r.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体 #分成两栏 # section = doc.sections[0] # section._sectPr.xpath('./w:cols')[0].set(qn('w:num'), '2') # 写入单选题 title1 = "一、单项选择题(共25题,每题2分)" p = doc.add_paragraph() # 插入段落 r = p.add_run(title1) # 插入文字块 r.bold = False # 字体加粗 r.font.size = Pt(10.5) # 字号设为10.5磅 r.font.name = u'黑体' r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') # 控制是中文时的字体 for index, i in enumerate(questions_data["单选题"], start=1): # 给题目从1开始编号 paraghNum = 0 doc.add_paragraph(f"{index}. {i[0]}") # 题干部分在单独一段 paraghNum = paraghNum +1 if len(i[1]) + len(i[2]) < 20: doc.add_paragraph(f"\t{i[1]}\t\t{i[2]}") # 选项A和选项B在同一段落 paraghNum = paraghNum + 1 else: doc.add_paragraph(f"\t{i[1]}") doc.add_paragraph(f"\t{i[2]}") paraghNum = paraghNum + 2 if len(i[3]) + len(i[4]) < 20: doc.add_paragraph(f"\t{i[3]}\t\t{i[4]}") # 选项C和选项D在同一段落 paraghNum = paraghNum + 1 else: doc.add_paragraph(f"\t{i[3]}") doc.add_paragraph(f"\t{i[4]}") paraghNum = paraghNum + 2 for paragh in range(-paraghNum,0): for run in doc.paragraphs[paragh].runs: run.font.size = Pt(10.5) # 字号设为10.5磅——五号 run.font.name = '宋体' # 控制是西文时的字体 run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体 if i[6]: image = Image.open(i[6]) width = image.width height = image.height n = width / height if n > 1: new_width = Cm(3.5) new_height = new_width / n if width > (2.5 * height): new_width = Cm(6) new_height = new_width / n else: new_height = Cm(3.5) new_width = new_height * n doc.add_picture(i[6],width=new_width,height=new_height) doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 写入填空题 title2 = "二、填空题(共10题,每题2分)" p = doc.add_paragraph() r = p.add_run(title2) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅 r.font.name = u'黑体' r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') for index, i in enumerate(questions_data["填空题"], start=1): doc.add_paragraph(f"\t{index}. {i[0]}") for run in doc.paragraphs[-1].runs: run.font.size = Pt(10.5) # 字号设为10.5磅——五号 run.font.name = '宋体' # 控制是西文时的字体 run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体 if i[2]: image = Image.open(i[2]) width = image.width height = image.height n = width / height if n > 1: new_width = Cm(3.5) new_height = new_width / n if width > (2.5 * height): new_width = Cm(6) new_height = new_width / n else: new_height = Cm(3.5) new_width = new_height * n doc.add_picture(i[2], width=new_width, height=new_height) doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 写入作图题 title3 = "三、作图题(共2题,每题5分)" p = doc.add_paragraph() r = p.add_run(title3) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅 r.font.name = u'黑体' r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') for index, i in enumerate(questions_data["作图题"], start=1): doc.add_paragraph(f"\t{index}. {i[0]}") for run in doc.paragraphs[-1].runs: run.font.size = Pt(10.5) # 字号设为10.5磅——五号 run.font.name = '宋体' # 控制是西文时的字体 run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体 if i[2]: image = Image.open(i[2]) width = image.width height = image.height n = width / height if n > 1: new_width = Cm(3.5) new_height = new_width / n if width > (2.5 * height): new_width = Cm(6) new_height = new_width / n else: new_height = Cm(3.5) new_width = new_height * n doc.add_picture(i[2], width=new_width, height=new_height) doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 写入计算题 title4 = "四、计算题(共2题,每题10分)" p = doc.add_paragraph() r = p.add_run(title4) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅 r.font.name = u'黑体' r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') for index, i in enumerate(questions_data["计算题"], start=1): doc.add_paragraph(f"\t{index}. {i[0]}") for run in doc.paragraphs[-1].runs: run.font.size = Pt(10.5) # 字号设为10.5磅——五号 run.font.name = '宋体' # 控制是西文时的字体 run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体 if i[2]: image = Image.open(i[2]) width = image.width height = image.height n = width / height if n > 1: new_width = Cm(3.5) new_height = new_width / n if width > (2.5 * height): new_width = Cm(6) new_height = new_width / n else: new_height = Cm(3.5) new_width = new_height * n doc.add_picture(i[2], width=new_width, height=new_height) doc.paragraphs[-1].paragraph_format.alignment=WD_ALIGN_PARAGRAPH.RIGHT
doc.save(f"试卷及答案\\考试题{number}({examNum}).docx")
# 写入答案def Answer(number, questions_data,examNum): doc = Document() # 全局字体设为“宋体” doc.styles['Normal'].font.name = u'宋体' doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')
title = "泾川二中八年级第二学期考试题(答案)" p = doc.add_paragraph() p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐 r = p.add_run(title) r.bold = True r.font.size = Pt(20) p = doc.add_paragraph() # 插入段落 p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐 r = p.add_run(examNum) # 插入文字块 r.bold = True # 字体加粗 r.font.size = Pt(10.5) # 字号设为12磅 # 写入单选题答案 title1 = "一、单项选择题答案(共25题,每题2分)" p = doc.add_paragraph() r = p.add_run(title1) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅——五号 r.font.name = '黑体' # 控制是西文时的字体 r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
p = doc.add_paragraph() for index, i in enumerate(questions_data["单选题"], start=1): p.add_run(f"{index}. {i[-2]}\t") if index % 10 == 0: # 每段只显示10个答案 p = doc.add_paragraph() # 满10个,则新建段落

title2 = "二、填空题答案(共10题,每题2分)" p = doc.add_paragraph() r = p.add_run(title2) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅——五号 r.font.name = '黑体' # 控制是西文时的字体 r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') p = doc.add_paragraph() for index, i in enumerate(questions_data["填空题"], start=1): p.add_run(f"{index}. {i[-2]}\t\t") if index % 2 == 0: # 每段只显示2个答案 p = doc.add_paragraph() # 满2个,则新建段落
title3 = "三、作图题(共2题,每题5分)" p = doc.add_paragraph() r = p.add_run(title3) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅——五号 r.font.name = '黑体' # 控制是西文时的字体 r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') p = doc.add_paragraph() for index, i in enumerate(questions_data["作图题"], start=1): p.add_run(f"{index}. {i[-2]}\t\t") if index % 2 == 0: # 每段只显示2个答案 p = doc.add_paragraph() # 满2个,则新建段落
title4 = "四、计算题(共2题,每题10分)" p = doc.add_paragraph() r = p.add_run(title4) r.bold = False r.font.size = Pt(10.5) # 字号设为10.5磅——五号 r.font.name = '黑体' # 控制是西文时的字体 r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') p = doc.add_paragraph() for index, i in enumerate(questions_data["计算题"], start=1): p.add_run(f"{index}. {i[-2]}\t\t") if index % 2 == 0: # 每段只显示2个答案 p = doc.add_paragraph() # 满2个,则新建段落
doc.save(f"试卷及答案\\考试题{number}({examNum})答案.docx")
# 主函数def main(): test_paperNum = int(input('请输入试卷份数!')) for number in range(1, test_paperNum +1): # 不同的试卷数量 max_num_single_choice = 0 max_num_completion =0 max_num_drawing = 0 max_num_computational =0 wb = load_workbook("题库.xlsx") while wb['单选题'].cell(max_num_single_choice + 2,2).value: max_num_single_choice = max_num_single_choice +1 while wb['填空题'].cell(max_num_completion + 2,2).value: max_num_completion = max_num_completion +1 while wb['作图题'].cell(max_num_drawing + 2,2).value: max_num_drawing = max_num_drawing +1 while wb['计算题'].cell(max_num_computational + 2,2).value: max_num_computational = max_num_computational +1 wb.close() # 生成随机题目编号 num_single_choice = Random_num(max_num_single_choice, 25) num_completion = Random_num(max_num_completion, 10) num_drawing = Random_num(max_num_drawing, 2) num_computational = Random_num(max_num_computational, 2) # 将生成的编号存入字典`question_num` question_num = {"单选题号": num_single_choice, "填空题号": num_completion, "作图题号": num_drawing, "计算题号": num_computational } # 根据随机生成的题目编号去题库选题,并存入`questions_data` questions_data = { "单选题": Question("单选题", question_num["单选题号"]), "填空题": Question("填空题", question_num["填空题号"]), "作图题": Question("作图题", question_num["作图题号"]), "计算题": Question("计算题", question_num["计算题号"]) } if number < 10: NumText = '00' + str(number) elif number <100: NumText = '0' + str(number) else: NumText = str(number) examNum = '试卷编号:' + datetime.now().strftime("%Y%m%d%H%M%S") + NumText # 将试题写入word文档,并保存 To_word(number, questions_data,examNum) # 将试题答案写入word文档,并保存 Answer(number, questions_data,examNum) print(f"试卷{number}({examNum})及答案完成!")

if __name__ == '__main__': main()



当然,这只是用了近一周的时间写出来的无UI界面的1.0版本,主要是依据物理监测赋了试题分值。后续还准备抽时间编写带有UI界面的2.0版本,以及后期还准备研发成倍数修改题目数据的3.0版本,敬请关注!









编程不易,且用且珍惜!

作者:Longefieder,西北某中学一名物理老师。在物理教学研究的同时,集一线管理者、班主任及学科老师的需求,致力于教学小程序的编写。截至目前,已成功编写VBA小程序20余款,Python程序9款。

感谢关注

2024.06.13

【声明】内容源于网络
0
0
科技码源
科技码源
内容 201
粉丝 0
科技码源 科技码源
总阅读133
粉丝0
内容201