使用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 录制视频

opencv—python录制视频、保存视频

3.利用Python来完成屏幕录制

利用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.软件下载

来源:蓝奏网盘

 

微信关注

编程那点事儿

本站为非盈利性站点,所有资源、文章等仅供学习参考,并不贩卖软件且不存在任何商业目的及用途,如果您访问和下载某文件,表示您同意只将此文件用于参考、学习而非其他用途。
本站所发布的一切软件资源、文章内容、页面内容可能整理来自于互联网,在此郑重声明本站仅限用于学习和研究目的;并告知用户不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
如果本站相关内容有侵犯到您的合法权益,请仔细阅读本站公布的投诉指引页相关内容联系我,依法依规进行处理!
作者:理想
链接:https://www.imyjs.cn/archives/855
THE END
二维码
使用Python开发一个AI模拟面试系统
1.需求分析 对于IT技术大部分岗位人员来说,求职面……
<<上一篇
下一篇>>
文章目录
关闭
目 录