Python项目:爬取IT互联网高薪热门职位数据并进行可视化分析

作者CSDN:进击的西西弗斯
本文链接:https://blog.csdn.net/qq_42216093/article/details/120943532
版权声明:本文为作者原创文章,未经作者同意不得转载




1.项目说明以及流程概要


爬取网站:智联招聘(https://sou.zhaopin.com/)

开发环境:Python3.8(Pycharm编辑器),全流程通过代码实现

爬取时间:2021/10/24 晚上9:13 的实时招聘信息数据


爬取职位及城市:共9个职位,java开发、前端开发、算法、数据挖掘、数据开发、数据分析、硬件开发、运维、产品经理;由于本人在上海工作,因此本次爬取的城市限定为上海

主要用到的python库:requestsBeautifulSouppandasmatplotlibseaborn

项目说明:本人初入互联网行业工作,为了更深入地了解互联网行业各类职位的相关信息,我使用python在智联招聘网站爬取了9大热门职位的全部招聘信息数据,包括薪资、公司名称以及规模、学历要求、技能要求、工作经验要求等数据,对数据清洗和整理后进行可视化分析,得到了薪资分布、不同学历占比、技能词频图等结果,目的是能从繁杂的招聘数据中直观地看到有价值的信息。



2.爬取网站数据并整理为csv


流程概要:


根据url和相关参数获取网页的html,对html解析后正则提取我们需要的标签信息,最终以dataframe二维表形式保存为csv文件,**其中要注意:**智联招聘在未登陆状态下无法爬取职位数据,于是我们可以先登陆网站,然后在浏览器开发者模式下找到需求头信息(Request Headers),复制下来后通过copyheaders库转换为字典后加入requests请求的headers参数中。(建议不要直接使用我的代码,虽然可以运行但运行多了可能会被网站检测出爬虫并被反爬!)


代码:(附注释)

# version : python3.8
# -*- coding : utf-8 -*-
"""
Created on : 2021/10/24 5:13 下午
@Author  : liudong
@Software: PyCharm
"""


import requests
import re
from copyheaders import headers_raw_to_dict
from bs4 import BeautifulSoup
import pandas as pd


# 根据url和参数获取网页的HTML:

def get_html(url, params):

    my_headers = b'''
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Cache-Control: max-age=0
    Connection: keep-alive
    Cookie: x-zp-client-id=448f2b96-6b3a-48e3-e912-e6c8dd73e6cb; sts_deviceid=178832cf3f2680-0b20242883a4a9-6618207c-1296000-178832cf3f3780; Hm_lvt_38ba284938d5eddca645bb5e02a02006=1624877846; urlfrom2=121114584; adfcid2=www.google.com; adfbid2=0; FSSBBIl1UgzbN7NO=5QbLj2_L5kKhv8gnuJa.E1._8RKksG1y5Nt4FRrSajQ7PKGJ8CcWopqTuOLay__ida1esO2ud4AdXKKDI69j9UA; locationInfo_search={%22code%22:%22538%22%2C%22name%22:%22%E4%B8%8A%E6%B5%B7%22%2C%22message%22:%22%E5%8C%B9%E9%85%8D%E5%88%B0%E5%B8%82%E7%BA%A7%E7%BC%96%E7%A0%81%22}; selectCity_search=538; ssxmod_itna=7qGxnDRG0=KGqAKGHKiQRSDQwKfkKqYteb87Dla=xA5D8D6DQeGTb0NpYeYietdigMWPqKYG4iteiFlYfPtb+4OEdD84i7DKqibDCqD1D3qDkbCYxiinDCeDIDWeDiDG+8D0hXl7DjQNXZKkULfBNDz4X2/4UgQDDHfG024dLRIqIgFA+5HYbDbxp9DB6rxBQ/Iqj6znUDgMTTibwbj8DoGiP=fifwn7Dq0YoYCA44fDx=bb4ee2hso7DYFDqojR8DG4xL2iD===; ssxmod_itna2=7qGxnDRG0=KGqAKGHKiQRSDQwKfkKqYteb8D61Fgj40y4rP03aKenjt6D6QMTiBeG2Yn408DewGD; urlfrom=121114584; adfcid=www.google.com; adfbid=0; sts_sg=1; sts_chnlsid=Unknown; zp_src_url=https%3A%2F%2Fwww.google.com.hk%2F; LastCity=%E4%B8%8A%E6%B5%B7; LastCity%5Fid=538; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%221071739258%22%2C%22first_id%22%3A%22178832cf3bd20f-0be4af1633ae3d-6618207c-1296000-178832cf3be4b8%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E8%87%AA%E7%84%B6%E6%90%9C%E7%B4%A2%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC%22%2C%22%24latest_referrer%22%3A%22https%3A%2F%2Fwww.google.com%2F%22%7D%2C%22%24device_id%22%3A%22178832cf3bd20f-0be4af1633ae3d-6618207c-1296000-178832cf3be4b8%22%7D; sts_sid=17cb28d33471536-08566241781bac-123b6650-1296000-17cb28d33481380; at=903f2985908c40b08a5f94a31f3ce3dc; rt=87ef0544ac924abaac9cfcddf07c9ea0; sts_evtseq=10; acw_tc=2760828b16350838102677392e6fd947253c8ceb8c6d07bf99f3246521c8ce; d4d6cd0b4a19fa72b8cc377185129bb7=843b9b24-97dd-410b-a554-cd40e82436ac; FSSBBIl1UgzbN7NP=53UYbECmTRPWqqqmZ7NMd1qzBpaBQmALgVHgfOadtkb30AZnPQLBe7tq5ITop.QpE0UAFelJGyiJUw4o9YrykPPl30M59JtVADHIlrqeL2qh7oUUJiEIrkb73XNoNod5EeRNheLNmf8wvi8F3yFwLBqPZ7.6CapWrCtXpfI4HapeY7.pC.nlnkSsRFcOC30dWn.1t5.wXdIYcgGeYZbRTyH2cYoRKwMJTnAAuuC6LHa9Ba4GAk0Ro6G8LN58ssLfFaIH8rfFDzN9_YTitsrxNXt; zpfe_probe_token=12c91e38s94ee345139aa73548b089ce0f18
    Host: sou.zhaopin.com
    Referer: https://www.zhaopin.com/
    sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "macOS"
    Sec-Fetch-Dest: document
    Sec-Fetch-Mode: navigate
    Sec-Fetch-Site: same-origin
    Sec-Fetch-User: ?1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36    '''     # b:将字符串转换为二进制; 三引号:为输入多行字符串,即包含\n的字符串
    my_headers = headers_raw_to_dict(my_headers)  # 把复制的浏览器需求头转化为字典形式
    req = requests.get(url, headers=my_headers, params=params)
    req.encoding = req.apparent_encoding
    html = req.text

    return html


# 输入url和城市编号,获取由所有职位信息的html标签的字符串组成的列表:

def get_html_list(url, position):

    html_list = list()

    for i in range(1, 20):
        params = {'jl': '538', 'kw': position, 'p': str(i)}
        html = get_html(url, params)
        soup = BeautifulSoup(html, 'html.parser')
        html_list += soup.find_all(name='a', attrs={'class': 'joblist-box__iteminfo iteminfo'})     # 参数:名称,属性(化成字典)

    for i in range(len(html_list)):     # soup.find_all()得到的列表元素是特殊类型
        html_list[i] = str(html_list[i])

    return html_list


# 根据上面的HTML标签列表,把每个职位信息的有效数据提取出来,保存csv文件:

def get_csv(html_list):

    city, specific_position, company_name, company_size, company_type, salary, education, ability, experience = ([] for i in range(9))  # 多变量一次赋值

    for i in html_list:

        if re.search(
                '<li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li>',
                i):
            s = re.search(
                '<li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li>',
                i).group(1)
            city.append(s)
            s = re.search(
                '<li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li>',
                i).group(2)
            experience.append(s)
            s = re.search(
                '<li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li> <li class="iteminfo__line2__jobdesc__demand__item">(.*?)</li>',
                i).group(3)
            education.append(s)
        else:
            city.append(' ')
            experience.append(' ')
            education.append(' ')


        if re.search('<span class="iteminfo__line1__jobname__name" title="(.*?)">', i):
            s = re.search('<span class="iteminfo__line1__jobname__name" title="(.*?)">', i).group(1)
            specific_position.append(s)
        else:
            specific_position.append(' ')

        if re.search('<span class="iteminfo__line1__compname__name" title="(.*?)">', i):
            s = re.search('<span class="iteminfo__line1__compname__name" title="(.*?)">', i).group(1)
            company_name.append(s)
        else:
            company_name.append(' ')

        if re.search(
                '<span class="iteminfo__line2__compdesc__item">(.*?) </span> <span class="iteminfo__line2__compdesc__item">(.*?) </span>',
                i):
            s = re.search(
                '<span class="iteminfo__line2__compdesc__item">(.*?) </span> <span class="iteminfo__line2__compdesc__item">(.*?) </span>',
                i).group(1)
            company_type.append(s)
            s = re.search(
                '<span class="iteminfo__line2__compdesc__item">(.*?) </span> <span class="iteminfo__line2__compdesc__item">(.*?) </span>',
                i).group(2)
            company_size.append(s)
        else:
            company_type.append(' ')
            company_size.append(' ')

        if re.search('<p class="iteminfo__line2__jobdesc__salary">([\s\S]*?)<', i):
            s = re.search('<p class="iteminfo__line2__jobdesc__salary">([\s\S]*?)<', i).group(1)
            s = s.strip()
            salary.append(s)
        else:
            salary.append(' ')

        s = str()
        l = re.findall('<div class="iteminfo__line3__welfare__item">.*?</div>', i)
        for i in l:
            s = s + re.search('<div class="iteminfo__line3__welfare__item">(.*?)</div>', i).group(1) + ' '
        ability.append(s)

    table = list(zip(city, specific_position, company_name, company_size, company_type, salary, education, ability, experience))
    df = pd.DataFrame(table, columns=['city', 'specific_position', 'company_name', 'company_size', 'company_type', 'salary',
                                          'education', 'ability', 'experience'])
    return df



if __name__ == '__main__':

    url = 'https://sou.zhaopin.com/'
    positions = ['java开发','前端开发','算法','数据挖掘','数据开发','数据分析','硬件开发','运维','产品经理']
    for i in positions:
        html_list = get_html_list(url, i)
        df0 = get_csv(html_list)
        position = [i]*(df0.shape[0])
        df_position = pd.DataFrame(position, columns=['position'])
        df = pd.concat([df_position, df0], axis=1)
        file_name = i + '.csv'
        df.to_csv(file_name)



结果:


结果文件csv下载链接


3.对数据结果进行可视化


流程概要:


先对数据结果进行清洗,salary属性下的字段都是类似于“8千-1.5万”这种无法进行后续统计和处理的字符串,我们需要将其全部修改为数值结果从而方便后续处理,此处我使用pandasre(正则表达式)把每个字段的薪资统一处理成了范围的中间值;对公司规模也采取类似处理,将描述性文字处理为数值型;最后将’薪资’和’公司规模’列的空值替换为各自列的均值。由于时间关系,还有其他空值、异常值等数据清洗并没有再继续处理。最后,使用matplotlibseaborn进行数据可视化,共得到4个结果图


代码:(附注释)

# version : python3.8
# -*- coding : utf-8 -*-
"""
Created on : 2021/10/24 3:42 下午
@Author  : liudong
@Software: PyCharm
"""


import pandas as pd
import re
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
plt.rcParams['font.sans-serif'] = ['Heiti TC']    # 指定默认字体:解决plot不能显示中文问题
plt.rcParams['axes.unicode_minus'] = False           # 解决保存图像是负号'-'显示为方块的问题


def data_clean():

    # 读取数据
    ls = ['java开发', '前端开发', '算法', '数据挖掘', '数据开发', '数据分析', '硬件开发', '运维', '产品经理']
    df = pd.read_csv('java开发.csv', index_col=0)
    for i in ls:
        if i != 'java开发':
            df0 = pd.read_csv(i + '.csv', index_col=0)
            df = pd.concat([df, df0], axis=0, ignore_index=True)

    # 清洗"薪资"列的数据,将文字描述处理成数值型
    for i in range(df.shape[0]):
        salary = df.loc[i, 'salary']
        if re.search(r'.*千-.*千', salary):
            a = eval(re.search('(.*)千-(.*)千', salary).group(1))*1000
            b = eval(re.search('(.*)千-(.*)千', salary).group(2))*1000
            num = (a+b)/2
        elif re.search(r'.*千-.*万', salary):
            a = eval(re.search('(.*)千-(.*)万', salary).group(1))*1000
            b = eval(re.search('(.*)千-(.*)万', salary).group(2))*10000
            num = (a+b)/2
        elif re.search(r'.*万-.*万', salary):
            a = eval(re.search('(.*)万-(.*)万', salary).group(1))*10000
            b = eval(re.search('(.*)万-(.*)万', salary).group(2))*10000
            num = (a+b)/2
        else:
            num = np.nan
        df.loc[i, 'salary'] = num

    # 清洗"公司规模"列的数据,将文字描述处理成数值型
    for i in range(df.shape[0]):
        s = df.loc[i, 'company_size']
        if re.search(r'(\d+)-(\d+)', s):
            a = eval(re.search(r'(\d+)-(\d+)', s).group(1))
            b = eval(re.search(r'(\d+)-(\d+)', s).group(2))
            n = round((a+b)/2,0)
        elif re.search(r'^(\d+)人', s):
            n = eval(re.search(r'^(\d+)人', s).group(1))
        else:
            n = np.nan
        df.loc[i, 'company_size'] = n

    # 将'薪资'和'公司规模'列的空值替换为各自列的均值
    df['salary'] = df['salary'].fillna(df['salary'].mean())
    df['company_size'] = df['company_size'].fillna(df['company_size'].mean())

    df.to_csv('df.csv')
    return df


def visualization(df):

    # 图1:总体数据的薪资分布情况
    salarys = df['salary']/1000
    mean = round(salarys.mean(),1)
    plt.figure(figsize=(8,6),dpi=200)
    sns.distplot(salarys, hist = True, kde = True, kde_kws={"color":"r", "linewidth":1.5, 'linestyle':'-'})
    plt.axvline(mean, color='r', linestyle=":")
    plt.text(mean, 0.05, '平均月薪: %.1f千'%(mean), color='k', horizontalalignment = 'center', fontsize = 15)
    plt.xlim(0,60)
    plt.xlabel('月薪(单位:千)')
    plt.title('互联网行业的薪资分布')
    plt.savefig('./互联网行业的薪资分布.jpg')
    plt.show()

    # 图2:9大职位的平均薪资条形图
    salarys = []
    positions = list(set(list(df['position'])))     # 将职位列单独拉出来、去重、转化列表
    for i in positions:
        t = df.loc[df['position']==i, 'salary']
        salarys.append(round(t.mean()/1000, 1))
    data = pd.DataFrame(list(zip(positions, salarys)), columns = ['positions', 'salarys'])
    data = data.sort_values('salarys', ascending=False)
    fig = plt.figure(figsize=[8, 6], dpi=200)
    ax = fig.add_subplot()
    ax.bar(data['positions'], data['salarys'], alpha=0.8, color='dodgerblue')
    for i in zip(data['positions'], data['salarys'], data['salarys']):
        ax.text(i[0], i[1], i[2], horizontalalignment = 'center')
    ax.set_ylim(8, 30)
    ax.set_title('互联网热门职位的平均月薪(单位:千)')
    plt.savefig('./互联网热门职位的平均月薪.jpg')
    plt.show()

    # 图3:公司规模的分布
    data = df['company_size']
    plt.figure(figsize=(8,6),dpi=200)
    sns.distplot(data, rug=True, hist = False, kde = True, kde_kws={"color":"dodgerblue", "linewidth":2, 'linestyle':'-'})
    plt.xlim(0, 12000)
    plt.xlabel('公司规模(单位:人)')
    plt.title('互联网行业的公司规模分布')
    plt.savefig('./互联网行业的公司规模分布.jpg')
    plt.show()

    # 图4:学历要求情况的饼图
    data = pd.DataFrame(df['education'].value_counts())
    x = data.index
    y = list(data.iloc[:, 0])
    fig = plt.figure(figsize=(8,6), dpi=200)
    ax = fig.add_subplot()
    explode = (0, 0, 0, 0, 0.2, 0.4, 0.6)
    ax.pie(y, labels=x, autopct='%.1f%%', explode=explode )
    ax.legend()
    ax.set_title('互联网行业对学历要求的占比')
    plt.savefig('./互联网行业对学历要求的占比.jpg')
    plt.show()

    # 图5:算法岗的技能要求词频图
    ability = df.loc[df['position']=='算法', 'ability']
    l = list(ability)
    for i in range(len(l)):
        l[i] = str(l[i])
    words = ''.join(l)
    cloud = WordCloud(
        font_path='/System/Library/Fonts/STHeiti Light.ttc',    # 设置字体文件获取路径,默认字体不支持中文
        background_color='white',    # 设置背景颜色  默认是black
        max_words=20,    # 词云显示的最大词语数量
        random_state = 3,  # 设置随机生成状态,即多少种配色方案
        collocations = False,    # 是否包括词语之间的搭配,默认True,可能会产生语意重复的词语
        width=1200, height=900      # 设置大小,默认图片比较小,模糊
    ).generate(words)
    plt.figure(figsize=(8,6), dpi=200)
    plt.imshow(cloud)       # 该方法用来在figure对象上绘制传入图像数据参数的图像,cloud对象支持该函数
    plt.title('算法职位的技能要求关键词频统计')
    plt.axis('off')     # 设置词云图中无坐标轴
    plt.savefig("./算法职位的技能要求关键词频统计.jpg")
    plt.show()


if __name__ == "__main__":

    df = data_clean()
    visualization(df)



结果:


1). 互联网热门职位总体数据的薪资分布情况:(直方图+核密度分布函数)

尽管不能代表全部,不过这9大热门职位的数千条数据足以作为参考。从图中可以看出,互联网行业的薪资分布大致符合左偏态分布,薪资分布的密集区间大约在12k-22k之间,而平均薪资18.5k在所有行业的招聘市场中处于偏高的位置,一些曾经热门的行业比如机械制造、土木工程、生物医药,如今和互联网相比都大势已去了。



2). 互联网9个热门职位的平均薪资条形图:(已降序处理)

可以看出,大部分职位之间的的薪资差距不是很大,然而算法岗可谓是一枝独秀,27.4k的平均月薪即便在普遍高薪的互联网行业内部也是鹤立鸡群的,究其主要原因,算法岗的能力要求、工作难度、工作强度都是这些职位里最高的,同时市场对这类人才的需求要大于供给,这些都是造成高薪的原因。



3). 互联网招聘中对学历要求的占比:

可以看出在互联网行业学历要求中,本科占比最大,为64.9%,其次是大专,而其余学历加起来只占不到10%,因此,互联网行业对于学历要求相对较低,反观如今国内研究生报考人数与日俱增,如果你不打算读研,那你可以尝试来互联网行业。



4). 算法岗技能要求关键词频统计:

由于时间关系,此处就以薪资最高的算法岗为例作词频图。从图中可以看出,算法岗主要需求技术是深度学习、Python、机器学习、C语言、图像识别等,因此熟练掌握这些技能能够增加求职中的核心竞争力。



有问题欢迎留言交流。
最后,如果你对Python、数据分析、机器学习等内容感兴趣,欢迎关注我的博客。

  • 6
    点赞
  • 130
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的西西弗斯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值