使用Python开发一个AI模拟面试系统
1.需求分析
对于IT技术大部分岗位人员来说,求职面试无疑是很重要的一关,然而作为一名即将面临求职的Java后端开发新人,为了以后能够更好应试,需要做大量的准备,在经历了几个月甚至一年几年的学习生涯,从编程语言基础语法,进阶知识到各种框架以及中间件技术,还有尤为重要的计算机基础理论知识,比如数据结构和算法,操作系统,数据库技术,网络……仿佛学了很多学不完的东西,这个庞大的知识体系的内容在面试中都是有可能会考到的,然而在看一些面经时,发现好像自己又什么都没有学,根本答不出来,面试其实就是一种考试,并不是在学习时见过,学过,动手实现过在面试过程中就能回答出来,所以准备面试还是需要脚踏实地,回归基础,沉淀知识,积累了大量知识后,最后既然是面试就是要求能够将面试官提问的问题流利的、清晰的、正确的回答出来。
所以既然是考试就要经常答题反复练习,面试通常是远程线上视频面试形式,为了模仿真实面试环境而并不只是去拿着知识点去死记硬背,所以需要有面试官进行提问,那么在自己一个人的情况下,怎么办?有人知道牛客网有一个模拟线上视频面试系统,在我使用几次后发现他对于面试小白来说并不友好,因为他面试题目来源于网上,主要是各种难度的题目都有可能出现,此时作为新手来说几乎答不出来几个题,总之就是不能根据自己情况自定义题目,他适合那些有了充足的知识储备的面试选手去使用,但是作为新手直接去练习前期肯定是很痛苦。
于是我就想自己可以去写一个模拟线上面试系统,这个系统需要满足以下需求:
- 自定义题库
- 面试官语音提问
- 面试官友情提示
- 视频记录
- 语音答题
- 计时答题
- 难度自定义
- 面试模式自定义
- 作答评估
- 记录系统
- ... ...
2.技术选型
- 编程语言:Python
- UI界面:QT
- 数据库:MySQL
- 第三方库
- PySide2
- PIL
- cv2
- numpy
- pynput
- pyaudio
- wave
- pymysql
- pyttsx3
由于Python有大量丰富的第三方库,以及其语法简单,感觉适合写一些小工具,所以我这里使用Python作为开发语言,题库就使用MySQL数据库去存储,至于UI界面的开发设计,其实Python也有很多选择,我这里使用的PySide2里面的QT Designer进行设计的,作为一名后端开发者,哪有UI设计师的审美和设计,再加上对于QT 的使用不熟悉,就本着只求功能不看外观的原则简单写个UI界面。
3.系统设计
数据库设计
这里没有任何的业务逻辑,就是为了给自己模拟一个环境使用,只有一张题库表。
字段 | 数据类型 | 数据约束 | 字段含义 |
---|---|---|---|
id | Int(11) | NOT NULL AUTO_INCREMENT PRIMARY KEY | ID |
title | varchar(255) | NOT NULL | 面试题目 |
answer | varchar(255) | DEFAULT NULL | 参考解析 |
type | varchar(64) | NOT NULL | 题目类型 |
minute | int(2) | DEFAULT '3' | 作答时间(minute) |
difficulty | tinyint(1) | DEFAULT '1' | 难易程度 |
tip1 | varchar(255) | DEFAULT NULL | 提示一 |
tip2 | varchar(255) | DEFAULT NULL | 提示二 |
tip2 | varchar(255) | DEFAULT NULL | 提示三 |
answer_count | int(11) | DEFAULT NULL | 答题次数 |
createtime | datetime | DEFAULT NULL | 添加时间 |
updatetime | datetime | DEFAULT NULL | 修改时间 |
deleted | tinyint(1) | DEFAULT '0' | 逻辑删除 |
CREATE TABLE `question` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`title` varchar(255) NOT NULL COMMENT '面试题目',
`answer` varchar(255) DEFAULT NULL COMMENT '参考解析',
`type` varchar(64) NOT NULL COMMENT '题目类型',
`minute` int(2) DEFAULT '3' COMMENT '作答时间(minute)',
`difficulty` tinyint(1) DEFAULT '1' COMMENT '难易程度',
`tip1` varchar(255) DEFAULT NULL COMMENT '提示一',
`tip2` varchar(255) DEFAULT NULL COMMENT '提示二',
`tip3` varchar(255) DEFAULT NULL COMMENT '提示三',
`answer_count` int(11) DEFAULT '0' COMMENT '答题次数',
`createtime` datetime DEFAULT NULL COMMENT '添加时间',
`updatetime` datetime DEFAULT NULL COMMENT '修改时间',
`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
概要设计
整个系统主要包括以下几个核心模块:UI界面、录音功能、摄像功能、屏幕录制功能、题目CURD、面试官语音播放。系统模块图如下:
系统执行流程图:
详细设计
UI设计
使用PySide2中提供的QT Designer进行设计:
面试官语音播放
这里可以使用Python的第三方库,也可以去对接第三方平台API,比如百度AI开发平台的语音合成技术。二者的区别主要就是使用本地库免费、可离线使用,但是功能单一;第三方平台收费、在线使用,但功能丰富。我这里使用的第三方库pyttsx3。
录音功能、摄像功能、屏幕录制功能
这些功能借助于第三方库的实现有许多现成的代码可以使用,可以在此基础上根据自己的特殊需求进行自定义实现,可以作为工具类来继承到系统中,那么在这个过程中,困难点在于需要使用多线程技术将其嵌入到QT桌面主程序中。
题目CURD
这部分由于没有太复杂的业务逻辑,就是基本的数据库连接,以及各种查询、增加、删除、修改操作。
4.编码测试
语音提问
import pyttsx3
class SayThread:
engine = ''
def __init__(self):
# 初始化语音引擎
self.engine = pyttsx3.init()
def say(self, context):
self.engine.say(context)
self.engine.runAndWait()
屏幕录制
from PIL import ImageGrab, Image
import cv2
import numpy as np
import datetime
from pynput import keyboard
import threading
# opencv 屏幕录制 有黑框
# def screeRecord():
# name = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S') # 当前的时间
# p = ImageGrab.grab() # 获得当前屏幕
# k = np.zeros((200, 200), np.uint8)
# a, b = p.size # 获得当前屏幕的大小
# fourcc = cv2.VideoWriter_fourcc(*'XVID') # 编码格式
# video = cv2.VideoWriter(name + '.avi', fourcc, 16, (a, b)) # 输出文件命名为 时间.avi,帧率为16,可以自己设置
# while True:
# im = ImageGrab.grab()
# imm = cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR) # 转为opencv的BGR格式
# video.write(imm)
# cv2.imshow('imm', k)
# if cv2.waitKey(1) & 0xFF == ord('q'):
# break
# video.release()
# cv2.destroyAllWindows()
# 屏幕录制改进版,无opencv黑框显示!
flag = False # 停止标志位
def video_record():
"""
屏幕录制!
:return:
"""
name = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S') # 当前的时间
p = ImageGrab.grab() # 获得当前屏幕
a, b = p.size # 获得当前屏幕的大小
fourcc = cv2.VideoWriter_fourcc(*'XVID') # 编码格式
video = cv2.VideoWriter('%s.avi' % name, fourcc, 20, (a, b)) # 输出文件命名为 时间.avi,帧率为16,可以自己设置
while True:
im = ImageGrab.grab()
imm = cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR) # 转为opencv的BGR格式
video.write(imm)
if flag:
print("录制结束!")
break
video.release()
def on_press(key):
"""
键盘监听事件!!!
:param key:
:return:
"""
# print(key)
global flag
if key == keyboard.Key.esc:
flag = True
print("stop monitor!")
return False # 返回False,键盘监听结束!
摄像监控
import time
import cv2
import threading
import inspect
import ctypes
# 录制视频
def VideoCapture():
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) # 创建一个 VideoCapture 对象
# cap = cv2.VideoCapture(0) # `anonymous-namespace'::SourceReaderCB::~SourceReaderCB terminating async callback
# 保存视频
# fourcc = cv2.VideoWriter_fourcc(*'XVID')
# out = cv2.VideoWriter('output.avi', fourcc, 30.0, (640, 480))
flag = 1 # 设置一个标志,用来输出视频信息
num = 1 # 递增,用来保存文件名
while (cap.isOpened()): # 循环读取每一帧
ret_flag, Vshow = cap.read() # 返回两个参数,第一个是bool是否正常打开,第二个是照片数组,如果只设置一个则变成一个tumple包含bool和图片
cv2.imshow("Interview", Vshow) # 窗口显示,显示名为 Interview
# out.write(Vshow)
k = cv2.waitKey(1) & 0xFF # 每帧数据延时 1ms,延时不能为 0,否则读取的结果会是静态帧
if k == ord('s'): # 若检测到按键 ‘s’,打印字符串
cv2.imwrite("./getpics/" + str(num) + ".jpg", Vshow)
print(cap.get(3)) # 得到长宽
print(cap.get(4))
print("success to save" + str(num) + ".jpg")
print("-------------------------")
num += 1
elif k == ord('q'): # 若检测到按键 ‘q’,退出
break
cap.release() # 释放摄像头
cv2.destroyAllWindows() # 删除建立的全部窗口
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
题库CURD
import pymysql
class Question:
db = ''
def __init__(self):
try:
self.db = pymysql.connect(
user='root',
password='3105501510',
host='127.0.0.1',
db='interview',
charset='utf8'
)
except Exception as e:
# print(e)
# print("连接数据库异常!")
return
def getAllCounts(self):
"""
获取数据表记录条数
:return: 数据表记录条数
"""
cursor = self.db.cursor()
sql = 'select count(*) from question'
cursor.execute(sql)
count = cursor.fetchone()
# print(f"共有{count[0]}条数据!")
cursor.close()
return count[0]
def insertData(self, question):
"""
添加数据
:param question: 数据列表
:return: null
"""
cursor = self.db.cursor()
try:
sql = 'insert into question(title, answer, type, difficulty, tip1, tip2, tip3, createtime, updatetime) ' \
'values (%s,%s,%s,%s,%s,%s,%s,%s,%s)'
values = (
question[0], question[1], question[2], question[3], question[4], question[5], question[6], question[7],
question[8])
print(values)
cursor.execute(sql, values)
# self.db.rollback()
except Exception as e:
print(e)
print("SQL执行异常!")
return
self.db.commit()
print('数据添加成功!')
cursor.close()
def deleteById(self, id):
cursor = self.db.cursor()
try:
sql = 'delete from question where id={}'.format(id)
cursor.execute(sql)
print(sql)
except Exception as e:
print(e)
print("SQL执行异常!")
return
self.db.commit()
if cursor.rowcount > 0:
print('数据删除成功!')
else:
print('非法删除!')
cursor.close()
def updateById(self, id, type_, answer):
cursor = self.db.cursor()
try:
sql = 'update question set type="{}",answer="{}" where id={}'.format(type_, answer, id)
cursor.execute(sql)
print(sql)
except Exception as e:
print(e)
print("SQL执行异常!")
return
self.db.commit()
if cursor.rowcount > 0:
print('数据修改成功!')
else:
print('非法修改!')
cursor.close()
def selectAll(self):
"""
获取全部数据
:return: 面试题数据
"""
cursor = self.db.cursor()
sql = 'select * from question'
cursor.execute(sql)
# allData = cursor.fetchall()
# print(type(allData)) # tuple
# print(allData)
print(cursor.rowcount)
courses = []
for i in range(cursor.rowcount):
course = {}
record = cursor.fetchone()
print(type(record))
course["id"] = record[0]
course["title"] = record[1]
course["answer"] = record[2]
course["type"] = record[3]
course["difficulty"] = record[4]
course["tip1"] = record[5]
course["tip2"] = record[6]
course["tip3"] = record[7]
course["createtime"] = record[8].strftime('%Y-%m-%d %H:%M:%S')
course["updatetime"] = record[9].strftime('%Y-%m-%d %H:%M:%S')
courses.append(course)
cursor.close()
return courses
def selectAllByType(self, type_):
"""
获取指定题目类型的全部数据
:return: 面试题数据
"""
pass
def selectByLike(self, keyword):
"""
模糊查询
:return: 面试题数据
"""
pass
def getAllType(self):
cursor = self.db.cursor()
sql = 'select distinct type from question'
cursor.execute(sql)
types = ['请选择考察范围']
for i in range(cursor.rowcount):
type_list = list(cursor.fetchone())
types += type_list
return types
5.问题解决
1.Python3.9 Pycharm 安装 PyAudio报错
Python3.9 Pycharm 安装 PyAudio报错
2.Python 录制视频
3.利用Python来完成屏幕录制
6.经验总结
由于时间仓促,从开始构思设计到编码实现以及测试使用,整个过程用了将近两天,因为自己目前主要学习Java后端开发,对于Python的使用并不是太熟练,在编码实现上就很可能有一些不优雅、不高效甚至于不合理的代码,本着能跑起来就行的原则,最终实现了大部分功能,对于需求分析中的面试评估、系统记录还没有开发,但是有了思路还是不难实现的。再有就是整个过程对于有些工具类的代码几乎就是在面向百度编程,因为好像没必要重复造轮子,只需要加以修改拿来使用能完成需求就行。在开发过程中遇到了很多问题,其中大部分时间花费在了使用QT进行UI界面的设计,主要就是调整布局,然后就是多线程问题,因为在程序运行中,需要同时使用麦克风、摄像头、计时器等,很显然只在一个主线程是不能完成的,对于多线程技术的使用深感其难度以及重要性,比如线程间通信、线程互斥等。总之目的不是为了去完美实现这个软件,而是简单写个小工具去方便自己去准备面试,所以有些功能或是小问题并没有做处理,如果有时间后面在随时完善吧、
最后我想说,技术是用来使用的,而不是去死记硬背的,只有在真正的开发场景遇到才能更加有所体会!
希望面试顺利,成功获取满意Offer!
7.更新日志
20220722(V2.1.6)
-
新增日志增加备注项
(finish)
20220720(V2.1.5)
-
新增出题类型处显示题目数量
20220702(V2.1.4)
-
优化题目显示当前题序
-
修复一些BUG
20220701(V2.1.3)
-
新增背题模式
-
增加取消自动下一题目按钮
20220630(V2.1.2)
-
日志记录增加使用题库项
(finish)
-
优化添加题目后清空表单内容
(finish)
-
优化日志文件存储为json格式
(Cancel)
20220629(V2.1.1)
master
-
新增按照题目出现次数出题的面试模式
-
新增面试官语速设置
-
新增版本更新设计:是否必须更新
master
-
(finish)
-
修复修改本题题目不正确BUG
(finish)
-
修复其他一些BUG
(finish)
-
优化UI界面
(finish)
-
提高用户友好性
(finish)
重要更新
3.0版本预测:
-
用户系统(注册、登录、管理)
-
积分系统(永久免费)
-
等级系统
-
支持用户题目提交至官方题库
-
支持自定义配置AI语音库(比如百度AI语音)
-
未完待续.....(看心情)
2.0版本主要重大更新如下:
-
新增网络面试题库
-
新增用户提交反馈功能
-
支持本地题库自定义设置
-
新增面试日志记录窗口
-
新增面试窗口修改题目内容
-
支持提示参考答案关闭功能
-
增加面试统计时间
-
增加答题次数记录
-
支持取消阅读答案
20220625(V2.0.1)
-
新增网络面试题库
(finish)
-
新增用户提交反馈功能
(finish)
-
支持本地题库自定义设置
(finish)
-
取消选择出题顺序设置
(finish)
-
取消选择出题数量设置
(finish)
-
优化UI界面
(finish)
20220623(V1.3.8)
-
新增自定义题目面试模式
(Cancel)
-
新增手动评价打分机制
(Cancel)
-
优化UI界面
(finish)
-
修复部分BUG
(finish)
20220621(V1.3.7)
-
优化软件更新检测
(finish)
-
提高用户友好性
(finish)
20220620(V1.3.6)
-
新增是否语音提示答案设置
(finish)
20220616(V1.3.5)
-
新增面试窗口修改题目内容
(finish)
-
新增软件更新日志窗口
(finish)
-
优化UI界面
(finish)
-
提高用户友好性
(finish)
20220613(V1.3.4)
-
支持提示参考答案关闭功能
(finish)
-
优化关闭摄像头提醒
(finish)
20220611(V1.3.3)
-
优化题目显示该题类型
(finish)
20220610(V1.3.2)
-
增加面试结束后显示本次面试题目
(finish)
ps:提前结束面试不进行显示
20220609(V1.3.1)
-
优化内容弹窗标题为面试题目
(finish)
-
优化UI界面(题库管理窗口)
(finish)
-
修复刷新数据时重置关键字
(finish)
20220608(V1.3.0)
-
增加面试统计时间
(finish)
-
增加答题次数记录
(finish)
-
增加面试题目设置
(Cancel)
-
增加面试日志记录
(finish)
-
支持取消阅读答案
(finish)
-
优化UI界面(主窗口)
(finish)
-
优化面试结束后相关处理
(finish)
-
修复部分已知BUG
(finish)
更早
V1.2.0
-
增加本地题库管理系统
-
修复已知BUG
-
优化UI界面
V1.1.0
-
修复大量BUG
-
优化UI界面
-
提高用户友好性
V1.0.0
-
软件发布
8.软件下载
微信关注

编程那点事儿