• 基于Flask的岗位就业可视化系统(总)


    前言

    开发环境

    • 系统:Window 10 家庭中文版。
    • 语言:Python(3.9)、MySQL。
    • Python所需的库:pymysql、pandas、numpy、time、datetime、requests、etree、jieba、re、json、decimal、flask(没有的话pip安装一下就好)。
    • 编辑器:jupyter notebook、Pycharm、SQLyog。
      (如果下面代码在jupyter中运行不完全,建议直接使用Pycharm中运行)

    文件说明

    在这里插入图片描述
    本项目下面有四个.ipynb的文件,下面分别阐述各个文件所对应的功能:(有py版本 可后台留言)

    • 数据采集:分别从前程无忧网站和猎聘网上以关键词数据挖掘爬取相关数据。其中,前程无忧上爬取了270页,有超过1万多条数据;而猎聘网上只爬取了400多条数据,主要为岗位要求文本数据,最后将爬取到的数据全部储存到csv文件中。

    • 数据清洗:对爬取到的数据进行清洗,包括去重去缺失值、变量重编码、特征字段创造、文本分词等。

    • 数据库存储:将清洗后的数据全部储存到MySQL中,其中对文本数据使用jieba.analyse下的extract_tags来获取文本中的关键词和权重大小,方便绘制词云。

    • 基于Flask的前后端交互:使用Python一个小型轻量的Flask框架来进行Web可视化系统的搭建,在static中有css和js文件,js中大多为百度开源的ECharts,再通过自定义controller.js来使用ajax调用flask已设定好的路由,将数据异步刷新到templates下的main.html中。

    技术栈

    • Python爬虫:(requests和xpath
    • 数据清洗:详细了解项目中数据预处理的步骤,包括去重去缺失值、变量重编码、特征字段创造和文本数据预处理 (pandas、numpy
    • 数据库知识:select、insert等操作,(增删查改&pymysql) 。
    • 前后端知识:(HTML、JQuery、JavaScript、Ajax)。
    • Flask知识:一个轻量级的Web框架,利用Python实现前后端交互。(Flask

    一、数据采集(爬虫)

    具体请看数据采集

    爬取数据

    浏览器伪装和相关参数

    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'
    }
    job, salary, area, edu, exp, company, href, content = [], [], [], [], [], [], [], []
    session = requests.Session()
    session.get('https://www.liepin.com/zhaopin/', headers = headers)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    for i in range(int(page)):
        url = f'https://www.liepin.com/zhaopin/?key={job_name}&curPage={i}'
        time.sleep(np.random.randint(1, 2))
        response = session.get(url, headers = headers)
        html = etree.HTML(response.text)
        for j in range(1, 41):
            job.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/h3/@title')[0])
            info = html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/p[1]/@title')[0]
            ss = info.split('_')
            salary.append(ss[0])
            area.append(ss[1])
            edu.append(ss[2])
            exp.append(ss[-1])
            company.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[2]/p[1]/a/text()')[0])
            href.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/h3/a/@href')[0])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    遍历每一个岗位的数据

    for job_href in href:
        time.sleep(np.random.randint(1, 2))
        # 发现有些岗位详细链接地址不全,需要对缺失部分进行补齐
        if 'https' not in job_href:
            job_href = 'https://www.liepin.com' + job_href
        response = session.get(job_href, headers = headers)
        html = etree.HTML(response.text)
        content.append(html.xpath('//section[@class="job-intro-container"]/dl[1]//text()')[3])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    保存数据

    df.to_csv('xxx.csv', encoding = 'gb18030', index = None)
    
    • 1

    二、数据清洗

    具体请看数据清洗

    read_csv读取数据集
    data.head()
    
    • 1
    • 2

    在这里插入图片描述

    去重去缺失

    data.isnull().mean()
    
    • 1

    在这里插入图片描述
    考虑舍弃薪水和工作职责上的缺失数据

    data.dropna(subset = ['薪水'], inplace = True)
    # 去除重复值
    data.drop_duplicates(inplace = True)
    # 索引重置
    data.index = range(data.shape[0])
    data.shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    薪水字段重编码

    初始化列表来接收正则提取后的每个字符串

    # 初始化列表来接收正则提取后的每个字符串
    salary_list = []
    for i in range(len(data)):
        
        # 使用正则表达式需要将每一列都转换为字符串形式
        data['薪水'][i] = str(data['薪水'][i])
        
        # 注意join之后便为一个字符串,我们使用正则筛选掉数字和其它符号,findall在下列中只会切割每个字,[]表示或,()表示与,{}表示匹配次数
        salary_list.append(''.join(re.findall(r'[^0-9\.]', data['薪水'][i])))
    
    # 使用unique知道有多少种写法
    np.unique(salary_list)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    地域字段重编码

    对地域这一列进行字段重编码

    data['地区'] = data['地域'].apply(lambda x: x.split('-')[0])
    data
    
    • 1
    • 2

    在这里插入图片描述

    对31个省市划分and遍历

    人数、学历和经验特征提取

    生成人数这一列

    num_list = []
    k = 0
    for i in range(len(data)):
        s = str(data['其他信息'][i])
        num_str = s.split(' ')[-1]
        if re.findall(r'\d', num_str):
            num_list.append(int(re.findall(r'\d', num_str)[0]))
        else:
            num_list.append(np.nan)
            k += 1
    data['人数'] = num_list
    
    print('招聘人数缺失数量有%s条,占总样本比例为%s' %(k, k / data.shape[0]))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    生成学历这一列

    k = 0
    edu_list = []
    for i in range(len(data)):
        s = str(data['其他信息'][i])
        edu_str = s.split(' ')[-2]
        if edu_str in ['博士', '硕士', '本科', '大专', '中专', '高中', '初中及以下']:
            edu_list.append(edu_str)
        else:
            edu_list.append(np.nan)
            k += 1
    data['学历'] = edu_list
    print('学历未标明的数量有%s条,占总样本比例为%s' %(k, k / data.shape[0]))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    生成经验这一列

    jingyan_list = []
    for i in range(len(data)):
        try:
            ss = data['其他信息'][i].split(' ')[-3]
            if '经验' in ss:
                if re.findall(r'(.*)\-(.*)\年', ss):
                    ss_num = re.findall(r'(.*)\-(.*)年', ss)[0]
                    jingyan = np.round((float(ss_num[0]) + float(ss_num[1])) / 2, 0)
                    jingyan_list.append(format(jingyan, '.0f'))
                elif re.findall(r'(.*)\年', ss):
                    ss_num = re.findall(r'(.*)\年', ss)[0]
                    jingyan_list.append(format(float(ss_num), '.0f'))
                else:
                    jingyan_list.append(0)
            else:
                jingyan_list.append(0)
        except:
            jingyan_list.append(0)
    data['经验'] = jingyan_list
    data[:2]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    将人数和学历的缺失值全部去除

    data.dropna(subset = ['人数', '学历'], inplace = True)
    data.index = range(data.shape[0])
    
    • 1
    • 2

    去除无意义的字段

    data.drop(labels = ['其他信息', '地域', '地区'], axis = 1, inplace = True)
    data['公司类型'] = data['公司类型'].fillna(data['公司类型'].mode().values[0])
    data.shape
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    保存数据

    data.to_csv('./51job_data_preprocessing.csv', encoding = 'gb18030', index = None)
    
    • 1
    data.isnull().mean()
    
    • 1

    在这里插入图片描述

    清洗岗位上的要求文本

    read_csv数据集
    df
    
    • 1
    • 2

    在这里插入图片描述

    for i in range(len(df)):
        df['岗位要求'][i] = str(df['岗位要求'][i])
    
        if len(df['岗位要求'][i]) < 30 or len(re.findall(r'\?', df['岗位要求'][i])) / len(df['岗位要求'][i]) > 0.4:
            df.drop(i, axis=0, inplace=True)
    
    # 索引重置
    df.index = range(df.shape[0])
    df.shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    自定义函数来实现分词和去除停用词操作

    import jieba
    def m_cut(tmpstr):
        return [w.strip() for w in jieba.lcut(tmpstr) if w not in stoplist and len(w) > 1]
    
    • 1
    • 2
    • 3

    导入自定义词典

    dic = './自定义词典.txt'
    jieba.load_userdict(dic)
    
    • 1
    • 2

    先去除部分不相关的词汇,之后调用上述函数进行分词及去除停用词的操作

    在这里插入图片描述

    将处理后的数据保存下来

    df0.to_csv('./liepin_job_detail.csv', encoding = 'gb18030', index = None)
    
    • 1

    三、数据库存储

    具体请看数据库存储

    将数据存储到sql中

    需要先在数据库中定义好数据库以及表

    这里改成自己数据库的用户名和密码

    下面是 连接数据库 和 关闭数据库

    def get_con():
        con = pymysql.connect(host = 'localhost', user = '用户名', password = '密码', database = '数据库名', charset = 'utf8')
        cursor = con.cursor()
        return con, cursor
    
    def con_close(con, cursor):
        if cursor:
            cursor.close()
        if con:
            con.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    读取数据

    df = pd.read_csv('51job_data_preprocessing.csv', encoding = 'gb18030')
    df
    
    • 1
    • 2

    在这里插入图片描述

    将每行数据都转变为tuple数据类型,然后遍历把每条数据都添加到sql中

    con, cursor = get_con()
    for i in range(len(df)):
        s = tuple(df.iloc[i, :])
        print({s})
        sql = f'insert into data_mining values{s}'
        cursor.execute(sql)
    con.commit()
    con_close(con, cursor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    将岗位要求数据存储到sql以及数据集中

    把词云部分数据也存放进数据库中

    df_cloud = pd.read_csv('liepin_job_detail.csv', encoding = 'gb18030')
    df_cloud
    
    • 1
    • 2

    在这里插入图片描述
    将每一列英文全部转换为大写的

    df_cloud = df_cloud.apply(lambda x: [i.upper() for i in x])
    df_cloud.head()
    
    • 1
    • 2

    在这里插入图片描述

    对文本进行去重操作

    s = np.unique(df_cloud.sum().tolist()).tolist()
    
    • 1

    由于后期使用echarts绘制词云需要知道各个关键词的权重大小,所以下面使用jieba下的extract_tags来挖掘各个关键词和权重大小,注意extract_tags输入的是一个字符串,我们挑选出前150个关键词及权重

    ss = aa.extract_tags(' '.join(s), topK = 150, withWeight = True)
    ss
    
    • 1
    • 2

    在这里插入图片描述

    con, cursor = get_con()
    for i in range(len(ss)):
        sql = "insert into data_mining_cloud(词语, 权重) value ({0}, {1})".format(repr(ss[i][0]), ss[i][1])
        cursor.execute(sql)
    con.commit()
    con_close(con, cursor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、基于Flask的前后端交互

    具体请看基于Flask的前后端交互

    使用flask框架进行网页的搭建

    class JSONEncoder(_JSONEncoder):
        def default(self, o):
            if isinstance(o, decimal.Decimal):
                return float(o)
            super(_JSONEncoder, self).default(o)
    
    
    class Flask(_Flask):
        json_encoder = JSONEncoder
    
    
    app = Flask(__name__)
    
    
    # 路由解析
    @app.route('/')
    def index():
        return render_template("main.html")
    
    
    # 获取系统当前时间
    @app.route('/time')
    def get_time1():
        return get_time()
    
    
    # 对数据库中的数据进行计数、薪资取平均值、省份和学历取众数
    @app.route('/c1')
    def get_c1_data1():
        data = get_c1_data()
        return jsonify({"employ": data[0], "avg_salary": data[1], "province": data[2], "edu": data[3]})
    
    
    # 对省份进行分组,之后统计其个数
    @app.route('/c2')
    def get_c2_data1():
        res = []
        for tup in get_c2_data():
            res.append({"name": tup[0], "value": int(tup[1])})
        return jsonify({"data": res})
    
    
    # 统计每个学历下公司数量和平均薪资(上下坐标折线图)
    @app.route('/l1')
    def get_l1_data1():
        data = get_l1_data()
        edu, sum_company, avg_salary = [], [], []
        for s in data:
            edu.append(s[0])
            sum_company.append(int(s[1]))
            avg_salary.append(float(s[2]))
        return jsonify({"edu": edu, "sum_company": sum_company, "avg_salary": avg_salary})
    
    
    # 统计不同学历下公司所招人数和平均经验(折线混柱图)
    @app.route('/l2')
    def get_l2_data1():
        data = get_l2_data()
        edu, num, exp = [], [], []
        for s in data:
            edu.append(s[0])
            num.append(float(s[1]))
            exp.append(float(s[2]))
        return jsonify({'edu': edu, 'num': num, 'exp': exp})
    
    
    # 统计不同类型公司所占的数量(饼图)
    @app.route('/r1')
    def get_r1_data1():
        res = []
        for tup in get_r1_data():
            res.append({"name": tup[0], "value": int(tup[1])})
        return jsonify({"data": res})
    
    
    # 可视化词云
    @app.route('/r2')
    def get_r2_data1():
        d = []
        text, weight = get_r2_data()
        for i in range(len(text)):
            d.append({'name': text[i], 'value': weight[i]})
        return jsonify({"kws": d})
    
    
    if __name__ == '__main__':
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    未加数据库效果图

    在这里插入图片描述

    添加加数据库效果图

    在这里插入图片描述

  • 相关阅读:
    3分钟让你学会axios在vue项目中的基本用法(建议收藏)
    ImmunoChemistry艾美捷通用阻断ELISA阻断缓冲液说明书
    算法竞赛进阶指南 基本算法 0x04 二分与三分
    SQL数据分析极简入门——SQL简介与基础知识
    测开笔试笔记(1)
    notepad++下载 地址真实有效
    PyTorch安装步骤
    Java面试八股2000道,高频经典难题,实力解读(面试成功率达95%,涵盖Java全知识体系+Leetcode算法题+项目实战)
    PythonOcc + pyqt 显示——stp step文件导入 部件识别 爆炸图展示
    springboot闲置衣物捐赠系统毕业设计源码021009
  • 原文地址:https://blog.csdn.net/m0_53054984/article/details/137790993