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

🌟欢迎来到 我的博客 —— 探索技术的无限可能!


🌟博客的简介(文章目录)

前言

  • 本项目综合了基本数据分析的流程,包括数据采集(爬虫)、数据清洗、数据存储、数据前后端可视化等

  • 推荐阅读顺序为:数据采集——>数据清洗——>数据库存储——>基于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)
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])

遍历每一个岗位的数据

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])

保存数据

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

二、数据清洗

具体请看数据清洗

read_csv读取数据集
data.head()

在这里插入图片描述

去重去缺失

data.isnull().mean()

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

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

在这里插入图片描述

薪水字段重编码

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

# 初始化列表来接收正则提取后的每个字符串
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)

在这里插入图片描述

地域字段重编码

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

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

在这里插入图片描述

对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]))

在这里插入图片描述

生成学历这一列

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]))

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

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]

在这里插入图片描述

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

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

去除无意义的字段

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

在这里插入图片描述

保存数据

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

在这里插入图片描述

清洗岗位上的要求文本

read_csv数据集
df

在这里插入图片描述

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

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

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

导入自定义词典

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

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

在这里插入图片描述

将处理后的数据保存下来

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

三、数据库存储

具体请看数据库存储

将数据存储到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()

读取数据

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

在这里插入图片描述

将每行数据都转变为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)

在这里插入图片描述

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

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

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

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

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

在这里插入图片描述

对文本进行去重操作

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

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

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

在这里插入图片描述

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)

四、基于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()

未加数据库效果图

在这里插入图片描述

添加加数据库效果图

在这里插入图片描述


🔍 温馨提示
        如果对本文有任何疑问,欢迎点击下方名片,了解更多详细信息!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZShiJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值