招聘数据分析

作为大数据方向的学生,了解市场对大数据相关岗位的需求,对将来的就业和职业规划很有帮助。现要求从招聘网站,爬取大数据相关岗位的招聘信息,对招聘信息进行处理和分析,并进行可视化展示。

任务一:数据获取与存储

点此查看此爬虫视频教程

点此查看爬虫代码或者在网页下方查看代码

从“前程无忧”招聘网站获取大数据相关岗位的招聘信息,并分别存储至数据库和csv文件中。

具体要求:

1.从网址:https://search.51job.com/list/000000,000000,0000,00,9,99,%25E5%25A4%25A7%25E6%2595%25B0%25E6%258D%25AE,2,1.html 入口获取所有的大数据相关岗位招聘信息。如从多个招聘网站获取更多招聘信息更好。

提取数据项至少包括以下字段:

(1)职位名称(岗位名称)、公司名称、 工作地点、薪资(底薪-上限)、发布时间(月-日);

(2)工作年限要求,学历要求,招聘人数

(3)公司性质  公司规模(人数)  公司所属行业

(2)和(3)字段对应网页位置:

说明:如从招聘列表处点击进入详细招聘信息,进入的不是前程无忧网页模板,则不用获取。

爬虫代码

1.使用类

import requests
from lxml import etree
from pymongo import MongoClient


class WuyouSpider:
    def __init__(self):
        '''初始化'''
        # 职位链接url地址 从1开始
        self.url = 'https://search.51job.com/list/000000,000000,0000,00,9,99,%25E5%25A4%25A7%25E6%2595%25B0%25E6%258D%25AE,2,{}.html'
        self.file = open('qiancheng.csv', 'w', encoding='utf-8')
        # 连接mongodb数据库
        mongo = MongoClient(host='172.26.97.83', port=27017)
        db = mongo['qianchengwuyou']
        self.cursor = db['wuyou']
        self.file.write(
            '{},{},{},{},{},{},{},{},{},{},{}\n'.format('work_name', 'company_name', 'edu_level', 'work_require_people',
                                                        'work_exp', 'release_date',
                                                        'work_location', 'work_salary', 'company_type', 'company_size',
                                                        'company_industry'))

    def get_response(self, url):
        '''获取response响应'''
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'
        }
        response = requests.get(url,headers=headers)
        response.encoding = 'gbk'
        return response.text

    def parse_list(self, data):
        '''处理数据,消除列表符号'''
        return ''.join(data)

    def parse_work_info(self, url):
        '''根据职位链接获取职位信息'''
        response = self.get_response(url)
        html_content = etree.HTML(response)
        #  工作年限要求,学历要求,招聘人数  职能类别
        work = {}
        #  职位名称(岗位名称)、公司名称、 工作地点、薪资(底薪-上限)、发布时间(月-日);
        work['work_name'] = self.parse_list(html_content.xpath("//div[@class='in']/div[@class='cn']/h1/@title"))
        work['company_name'] = self.parse_list(
            html_content.xpath("//div[@class='in']/div[@class='cn']/p[@class='cname']/a/@title"))
        # 结果为一串字符,需要进行分割 并去除空字符
        content1 = self.parse_list(
            html_content.xpath("//div[@class='in']/div[@class='cn']/p[@class='msg ltype']/@title")).split('|')
        content1 = [i.strip() for i in content1]
        # 提取结果 ['广州-天河区', '5-7年经验', '本科', '招1人', '06-03发布']
        for con in content1:
            if con in ['初中及以下', '高中/中技/中专', '本科', '无工作经验', ' 大专']:
                work['edu_level'] = con
            if con.find('招') != -1:
                work['work_require_people'] = con
            if con.find('经验') != -1:
                work['work_exp'] = con
            if con.find('发布') != -1:
                work['release_date'] = con
        for column in ['edu_level', 'work_require_people', 'work_exp', 'release_date']:
            if column not in work.keys():
                work[column] = None
        work['work_location'] = content1[0]
        work['work_salary'] = self.parse_list(html_content.xpath("//div[@class='in']/div[@class='cn']/strong/text()"))
        # 公司性质  公司规模(人数)  公司所属行业
        work['company_type'] = self.parse_list(html_content.xpath("//div[@class='com_tag']/p[1]/@title"))
        work['company_size'] = self.parse_list(html_content.xpath("//div[@class='com_tag']/p[2]/@title"))
        work['company_industry'] = self.parse_list(html_content.xpath("//div[@class='com_tag']/p[3]/@title")).replace(
            ',', ' ')

        # 将数据写入mongodb数据库
        self.cursor.insert_one(work)

        print(work)
        # 将数据写入csv
        self.file.write(
            '{},{},{},{},{},{},{},{},{},{},{}\n'.format(work.get('work_name') or '空', work.get('company_name') or '空',
                                                        work.get('edu_level') or '空',
                                                        work.get('work_require_people') or '空',
                                                        work.get('work_exp') or '空',
                                                        work.get('release_date') or '空',
                                                        work.get('work_location') or '空',
                                                        work.get('work_salary') or '空', work.get('company_type') or '空',
                                                        work.get('company_size') or '空',
                                                        work.get('company_industry') or '空'))
        if len(work) < 2:
            return False
        else:
            return True

    def parse_page_href(self, page):
        '''获取页面内职位链接
        参数:   page  页数(从1开始)
        '''
        url = self.url.format(page)
        response = self.get_response(url)
        html_content = etree.HTML(response)
        # 提取链接
        works_url = html_content.xpath("//div[@class='dw_table']//div[@class='el']/p/span/a/@href")

        # 根据职位链接获取职位信息
        for url in works_url:
            resu = self.parse_work_info(url)
            if resu is False:
                print('爬取结束')
                return False

    def run(self):
        '''爬取前程无忧大数据相关岗位招聘信息
        步骤:
            1.获取第一页所有职位链接
            2.根据职位链接获取职位信息
            3.获取下一页职位链接
            4.根据职位链接获取职位信息...
        '''
        # 1.获取第一页所有职位链接
        page = 1
        # while 循环中的page用于控制爬取页面数 此处小于三页
        while page < 3:
            resu = self.parse_page_href(page)
            if resu is False:
                break
            page += 1
        self.file.close()


if __name__ == '__main__':
    wuyou = WuyouSpider()
    wuyou.run()

2.使用函数


import requests
from lxml import etree
from pymongo import MongoClient

# 职位链接url地址 从1开始
index_url = 'https://search.51job.com/list/000000,000000,0000,00,9,99,%25E5%25A4%25A7%25E6%2595%25B0%25E6%258D%25AE,2,{}.html'
file = open('qiancheng.csv', 'w', encoding='utf-8')
# 连接mongodb数据库
mongo = MongoClient(host='172.26.97.83', port=27017)
db = mongo['qianchengwuyou']
cursor = db['wuyou']
file.write('{},{},{},{},{},{},{},{},{},{},{}\n'.format('work_name', 'company_name', 'edu_level', 'work_require_people',
                                                       'work_exp', 'release_time',
                                                       'work_location', 'salary', 'company_type', 'company_size',
                                                       'company_industry'))


def get_response(url):
    '''获取response响应'''
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'
    }
    response = requests.get(url, headers=headers)
    response.encoding = 'gbk'
    return response.text


def parse_list(data):
    '''处理数据,消除列表符号'''
    return ''.join(data)


def parse_work_info(url):
    '''根据职位链接获取职位信息'''
    response = get_response(url)
    html_content = etree.HTML(response)
    #  工作年限要求,学历要求,招聘人数  职能类别
    work = {}
    #  职位名称(岗位名称)、公司名称、 工作地点、薪资(底薪-上限)、发布时间(月-日);
    work['work_name'] = parse_list(html_content.xpath("//div[@class='in']/div[@class='cn']/h1/@title"))
    work['company_name'] = parse_list(
        html_content.xpath("//div[@class='in']/div[@class='cn']/p[@class='cname']/a/@title"))
    # 结果为一串字符,需要进行分割 并去除空字符
    content1 = parse_list(html_content.xpath("//div[@class='in']/div[@class='cn']/p[@class='msg ltype']/@title")).split(
        '|')
    content1 = [i.strip() for i in content1]
    # 提取结果 ['广州-天河区', '5-7年经验', '本科', '招1人', '06-03发布']
    for con in content1:
        if con in ['初中及以下', '高中/中技/中专', '本科', '无工作经验', ' 大专']:
            work['edu_level'] = con
        if con.find('招') != -1:
            work['work_require_people'] = con
        if con.find('经验') != -1:
            work['work_exp'] = con
        if con.find('发布') != -1:
            work['release_time'] = con
    for column in ['edu_level', 'work_require_people', 'work_exp', 'release_time']:
        if column not in work.keys():
            work[column] = None
    work['work_location'] = content1[0]
    work['salary'] = parse_list(html_content.xpath("//div[@class='in']/div[@class='cn']/strong/text()"))
    # 公司性质  公司规模(人数)  公司所属行业
    work['company_type'] = parse_list(html_content.xpath("//div[@class='com_tag']/p[1]/@title"))
    work['company_size'] = parse_list(html_content.xpath("//div[@class='com_tag']/p[2]/@title"))
    work['company_industry'] = parse_list(html_content.xpath("//div[@class='com_tag']/p[3]/@title")).replace(',', ' ')

    # 将数据写入mongodb数据库
    cursor.insert_one(work)

    print(work)
    # 将数据写入csv
    file.write(
        '{},{},{},{},{},{},{},{},{},{},{}\n'.format(work.get('work_name') or '空', work.get('company_name') or '空',
                                                    work.get('edu_level') or '空',
                                                    work.get('work_require_people') or '空', work.get('work_exp') or '空',
                                                    work.get('release_time') or '空', work.get('work_location') or '空',
                                                    work.get('salary') or '空', work.get('company_type') or '空',
                                                    work.get('company_size') or '空',
                                                    work.get('company_industry') or '空'))
    if len(work) < 2:
        return False
    else:
        return True


def parse_page_href(page):
    '''获取页面内职位链接
    参数:   page  页数(从1开始)
    '''
    url = index_url.format(page)
    response = get_response(url)
    html_content = etree.HTML(response)
    # 提取链接
    works_url = html_content.xpath("//div[@class='dw_table']//div[@class='el']/p/span/a/@href")
    # 根据职位链接获取职位信息
    for url in works_url:
        resu = parse_work_info(url)
        if resu is False:
            print('爬取结束')
            return False


def run():
    '''爬取前程无忧大数据相关岗位招聘信息
    步骤:
        1.获取第一页所有职位链接
        2.根据职位链接获取职位信息
        3.获取下一页职位链接
        4.根据职位链接获取职位信息...
    '''
    # 1.获取第一页所有职位链接
    page = 1
    while page:
        resu = parse_page_href(page)
        if resu is False:
            break
        page += 1
    file.close()


if __name__ == '__main__':
    run()

任务二:数据清洗

代码在题目下方(注意:代码内的部分内容可能与上面爬虫所爬取的数据不同(如字段、文件名),请根据实际情况修改)

1.规范岗位名称

有的岗位名称含有()或者()或者- 或者空格 或者/,补充说明地点,背景方向要求,福利,学历等信息。把岗位名称中的补充信息全部去掉。

2.从工作地点中提取出城市名

将含有“异地招聘”字样或工作地点缺失的招聘信息直接删除

3.将薪资分为底薪和上限两列。一律转换为xxx元/月,保留纯整数。

将薪资缺失的招聘信息直接删除。

4.将发布时间变为日期时间型格式,形如年-月-日格式。

5.将“学历要求”转换为如下6个类别:

无   初中及以下 高中/中技/中专   大专  本科  硕士  博士

如果有缺失,则用“无”填充

6.招聘人数:转换为整型;

 如果为 缺失或“若干人”,都用“1”填充或替换;

7.工作年限要求:转换为整型;如果为范围,则取最小值。如3-5年,就取3;如无经验要求或缺失,用“0”表示。

8.职能类别:如果含多个职能类别,如大数据开发/分析,就取第1个。

import re
import pandas as pd
from numpy import NaN
df =pd.read_csv("WuyouBigdata.csv")
#print(df)
# 1.规范岗位名称
# 有的岗位名称含有()或者()或者- 或者空格 或者/,补充说明地点,背景方向要求,福利,学历等信息。把岗位名称中的补充信息全部去掉。
df = df[~df['work_name'].str.contains('-')]#~取反
df = df[~df['work_name'].str.contains('/')]
df = df[~df['work_name'].str.contains('\(')]
df = df[~df['work_name'].str.contains('(')]
# 2.从工作地点中提取出城市名
# 将含有“异地招聘”字样或工作地点缺失的招聘信息直接删除
df = df[~df['work_location'].str.contains('异地招聘')]
df=df.dropna(subset=['work_location'])
df = df[~df['work_location'].str.contains('空')]
df = df.reset_index().drop('index', axis=1)#清洗 链接起来去空的数据
# 提取城市名
df['work_location'] = [i.split('-')[0] for i in df['work_location']]
# 3.将薪资分为底薪和上限两列。一律转换为xxx元/月,保留纯整数。
# 将薪资缺失的招聘信息直接删除
salary_min=[]
salary_max=[]
df= df.dropna(axis=0, how='any')    # 将薪资缺失的招聘信息直接删除
for salary in df['work_salary']:
    split_data = salary.split('-')
    salary_min.append(float(split_data[0]))
    if '万' in split_data[1]:
        salary_max.append(str(float(split_data[1].split('万')[0])*10000)+'元/月')
    elif '千' in split_data[1]:
        salary_max.append(str(float(split_data[1].split('千')[0])*1000)+'元/月')
df['salary_min'] = salary_min
df['salary_max'] = salary_max
# 4.将发布时间变为日期时间型格式,形如年-月-日格式。
df['release_date'] = ['2019-'+str(i) for i in df['release_date']]
df['release_date'] = [pd.to_datetime(i, format="%Y-%m-%d") for i in df['release_date']]
# 5.将“学历要求”转换为如下6个类别:
# 无   初中及以下 高中/中技/中专   大专  本科  硕士  博士
# 数据有问题,该题不做
# 6.招聘人数:转换为整型;
# 如果为 缺失或“若干人”,都用“1”填充或替换;
people_list=[]
for i in df['work_require_people']:
    count = re.findall('招(.*?)人',i)
    if '若干' in count[0]:
        people_list.append(1)
    else:
        people_list.append(int(count[0]))
df['work_require_people'] = people_list
# 7.工作年限要求:转换为整型;如果为范围,则取最小值。如3-5年,就取3;如无经验要求或缺失,用“0”表示。
exp_list = []
for i in df['work_exp']:
    '3-4年经验'
    split_data = i.split('-')
    if len(split_data)>1:
        exp_list.append(int(split_data[1].split('年')[0]))
    else:
        if '无' in split_data[0]:
            exp_list.append(0)
        else:
            exp_list.append(int(split_data[0].split('年')[0]))
df['work_exp'] = exp_list
# 8.职能类别:如果含多个职能类别,如大数据开发/分析,就取第1个。
# 数据原因(没有该列数据) 此题不做
# 将数据保存到新的csv文件
df.to_csv('new_bigdata.csv',index=False)

三、数据分析与可视化

代码在题目下方 (注意:代码内的部分内容可能与上面爬虫所爬取的数据不同(如字段、文件名),请根据实际情况修改)

1.统计各个城市的大数据相关岗位的招聘人数,将人数排名前10的用柱状图展示。

2.统计大数据各个岗位的招聘人数,用柱状图展示最受欢迎(需求量大)的十大岗位。

3.统计各地平均底薪薪资,将排名前三和后三的用合适的图表展示。

4.统计各种学历要求所占的比例,用饼状图展示。

6.统计各种公司性质(事业或私营等)所需大数据相关岗位的人数,用柱状图显示。

7.统计每种职能类别下每种学历要求对应的岗位数量。

【比如:对于大数据开发这种职能类别,分别统计要求博士,硕士等的岗位分别有多少。】

8.统计每个城市中每种学历需求对应的岗位数量。

9.统计每个城市中每种岗位或职能类别的平均薪资(底薪,上限,平均都行)。

说明:数据分析与可视化部分完成5个即可。多多益善。

import matplotlib.pyplot as plt
import pandas as pd
# 解决中文显示问题
font = {'family' : 'SimHei',
        'weight' : 'bold',
        'size'   : '12'}
plt.rc('font', **font)               # 步骤一(设置字体的更多属性)
plt.rc('axes', unicode_minus=False)  # 步骤二(解决坐标轴负数的负号显示问题)
# 读取数据
data = pd.read_csv('new_bigdata.csv')
def question1():
    # 1.统计各个城市的大数据相关岗位的招聘人数,将人数排名前10的用柱状图展示。
    # 根据城市分组求和 降序排序
    new_data = data.groupby('work_location')['work_require_people'].sum().sort_values(ascending=False)
    question1_result_list = new_data[:10]
    # 画图
    x = [i for i in range(len(question1_result_list))]
    y = question1_result_list
    plt.bar(x,y,tick_label=list(question1_result_list.index))
    plt.title('招聘人数排名前10的城市')
    for i,j in zip(x,y):
        plt.text(i,j,'{}人'.format(j))
    plt.show()
def question2():
    # 2.统计大数据各个岗位的招聘人数,用柱状图展示最受欢迎(需求量大)的十大岗位。
    # 由于数据原因 此处根据职位名分组
    new_data = data.groupby('work_name')['work_name'].count().sort_values(ascending=False)
    question2_result_list = new_data[:10]
    # 画图
    x = [i for i in range(len(question2_result_list))]
    y = question2_result_list
    # 解决显示重叠问题
    label = [i[:3]+'\n'+i[3:-2]+'\n'+i[-2:] for i in list(question2_result_list.index)]
    plt.bar(x,y,tick_label=label)
    plt.title('最受欢迎(需求量大)的十大岗位')
    for i,j in zip(x,y):
        plt.text(i,j,'{}人'.format(j))
    plt.show()
# 3.统计各地平均底薪薪资,将排名前三和后三的用合适的图表展示。
def question3():
    # 将薪资后面的“元/每月”去掉
    salary_min = []
    for i in data['salary_min']:
        try:
            salary_min.append(float(i[:-3]))
        except:
            salary_min.append(float(data['salary_min'][0][:-3]))
    #salary_min = [float(i[:-3]) for i in data['salary_min']]
    data['salary_min'] = salary_min
    # 根据城市分组,对薪资求平均值
    new_data = data.groupby('work_location')['salary_min'].mean().sort_values(ascending=False)
    head3_data = new_data[:3]
    tail3_data = new_data[-3:]
    # 将两个数据在一个柱状图上展示 用图例区分
    x1 = [0,1,2]
    x2 = [3,4,5]
    y1 = head3_data
    y2 = tail3_data
    label = list(head3_data.index)
    [label.append(i) for i in (list(tail3_data.index))]
    plt.title('平均底薪排名前三和后三的城市')
    plt.bar(x1,y1,)
    plt.bar(x2,y2,)
    plt.xticks([i for i in range(6)],label)
    plt.legend(['前三名','后三名'])
    plt.show()
# 4.统计各种学历要求所占的比例,用饼状图展示。
def question4():
    # 根据学历分组,统计总数
    new_data = data.groupby('edu_level')['edu_level'].count()
    labels = list(new_data.index)
    sizes = [i for i in new_data]
    print(labels,sizes)
    fig1, ax1 = plt.subplots()
    ax1.pie(sizes,  labels=labels, autopct='%1.1f%%',
            shadow=True, startangle=90)
    ax1.axis('equal')  # 等长径比确保饼图被画成圆形。
    ax1.set_title('各种学历要求所占的比例')
    plt.show()
# 6.统计各种公司性质(事业或私营等)所需大数据相关岗位的人数,用柱状图显示。
def question6():
    # 根据公司性质分组,对招聘人数求和
    new_data = data.groupby('company_type')['work_require_people'].sum().sort_values(ascending=False)
    # 画图
    x = [i for i in range(len(new_data))]
    y = new_data
    plt.bar(x, y, tick_label=[i[:2]+'\n'+i[2:] for i in list(new_data.index)])
    plt.title('各种公司性质(事业或私营等)所需大数据相关岗位的人数')
    for i, j in zip(x, y):
        plt.text(i, j, '{}人'.format(j))
    plt.show()
# 7.统计每种职能类别下每种学历要求对应的岗位数量。
# 【比如:对于大数据开发这种职能类别,分别统计要求博士,硕士等的岗位分别有多少
def question7():
    # 数据原因 对职位名称进行分组,再对学历进行分组,求和
    new_data = data.groupby(['work_name','edu_level'])['work_require_people'].sum()
    print(new_data,type(new_data))
# 8.统计每个城市中每种学历需求对应的岗位数量。
def question8():
    # 对城市、学历分组 对需求求和
    new_data = data.groupby(['work_location','edu_level'])['work_require_people'].sum()
    print(new_data)
# 9.统计每个城市中每种岗位或职能类别的平均薪资(底薪,上限,平均都行)。
def question9():
    # 将薪资后面的“元/每月”去掉
    try:
        salary_min = [float(i[:-3]) for i in data['salary_min']]
    except:
        salary_min = [i for i in data['salary_min']]
    data['salary_min'] = salary_min
    # 对城市、岗位进行分组 求底薪平均值
    new_data = data.groupby(['work_location', 'work_name'])['salary_min'].mean()
    print(new_data)
if __name__ == '__main__':
    question1()
    question2()
    question3()
    question4()
    question6()
    question7()
    question8()
    question9()

发表回复