数据分析师薪酬?拉勾网职位爬取+分析

我是封面

——2018.06.01——
最近几天看到了很多文章写拉勾网职位爬取,那些基本是基于requests + json,但是貌似所给url打不开,还有可能被封ip的风险。
本文主要用selenium + BautifulSoup + xpath工具,爬取不同城市的数据分析师薪酬,闲话不多说,上菜。

  • 爬虫 selenium + BautifulSoup + xpath
  • 储存数据 MySQL
  • 数据清理
  • 薪酬和职位需求分析 seaborn + pyecharts
    ——
    编译环境:Python 3.5
    IDE:jupyter notebook

1.网页爬取

第一步,导入所需的模块:

import re
import time
import random
#爬虫工具
from selenium import webdriver 
from selenium.webdriver.chrome.options import Options
#解析工具
from bs4 import BeautifulSoup
from lxml import etree

第二步,设置浏览器参数:

#设置无头的chrome浏览器参数
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
#运行浏览器
driver = webdriver.Chrome(chrome_options=chrome_options)
#备注::selenium 3.0+版本已不支持PhantomJS,所以这里使用无头的chrome brower

第三步,找到合适url开始爬虫,这里以长沙为例:

url = "https://www.lagou.com/jobs/list_数据分析师?px=default&city={}#filterBox".format('长沙')
driver.get(url)
time.sleep(3) #设置等待时间,强烈建议

第四步,解析网页

# BeautifulSoup解析获取职位的福利和标签
s = BeautifulSoup(driver.page_source,'lxml')
labels=[]
treatment=[]
for i in s.findAll('div','list_item_bot'):
    labels.append(i.text.split('“')[0].strip().replace('\n',','))
    treatment.append(i.text.split('“')[1].strip().replace('”',''))
    
# xpath 工具获取公司名称、链接、id等其他数据
selector=etree.HTML(driver.page_source)
company_link=selector.xpath('//div[@class="company_name"]/a/@href')
company_name=selector.xpath('//div[@class="company_name"]/a/text()')
job_name =selector.xpath('//a[@class="position_link"]/h3/text()')
job_link = selector.xpath('//a[@class="position_link"]/@href')
address =selector.xpath('//span[@class="add"]/em/text()')
temp_1 =selector.xpath('//div[@class="p_bot"]/div[@class="li_b_l"]/text()')
salary = selector.xpath('//span[@class="money"]/text()')
temp_2 = selector.xpath('//div[@class="industry"]/text()')
treatment = selector.xpath('//div[@class="li_b_r"]/text()')
    
#利用自定义的分隔函数,得到所需的值
def spt(temp):
    list1,list2 = [],[]
    for i in temp:
        if i.strip():
            list1.append(i.split('/')[0].strip())
            list2.append(i.split('/')[1].strip())
    return list1,list2
experience,education = spt(temp_1)
industry,scale = spt(temp_2)
    
#从链接中获取职位ID和所属公司的ID
companyID = list(map(lambda x:re.findall(r'\d+',x)[0],company_link))
jobID = list(map(lambda x:re.findall(r'\d+',x)[0],job_link))

2.将数据存入MySQL数据库中

在Python链接之前,已在MySQL数据库,创建数据库lagou,数据表job,company

#导入模块 pymysql
import pymysql  

db = pymysql.connect("localhost", "root", "123456", "lagou",use_unicode=True, charset="utf8")
cursor = db.cursor()

#定义一个插入到数据库的函数
def insert(x_zip,table):    
    for m in x_zip:
        sql = '''INSERT INTO {} values {}'''.format(table,m)
        try:
            cursor = db.cursor()
            cursor.execute(sql)
            db.commit()
        except Exception as error:
            db.rollback()
            print(error)
#将数据插入到数据库中
companys = zip(companyID,company_name,company_link,industry,scale)
insert(companys,"company")
jobs = zip(jobID,job_name,address,experience,salary,treatment,job_link,companyID,education,labels,['长沙']*len(jobID))
insert(jobs,"job")

——
翻页问题:
拉勾上的职位内容基于js动态加载,所以点击下一页后,地址仍不生变化。但是仔细观察elements,最后一页会存在标签:<span …… class = 'pager_next pager_next_disabled' ,我们可以利用这个标签来定位。

if s.findAll('span','pager_next pager_next_disabled'):
    break
else:
    submitBtn = driver.find_element_by_class_name("pager_next")
    driver.execute_script("arguments[0].scrollIntoView()", submitBtn) # js滚动加载,缺少此步,action无法生效
    submitBtn.click()
    time.sleep(random.randint(3,20)) #爬虫礼仪,减少访问服务器的频率

以上就是拉勾数据分析师职位数据爬取和储存,这里为了显得整洁,分别存储在job,company两个表中。为了便于阅读,完整代码放在文末链接中,有需要可以自己去fork。

3.数据清理

首先,导入数据:

import pandas as pd
from sqlalchemy import create_engine
import pymysql

#创建engine
engine = create_engine('mysql://root:123456@localhost:3306/lagou?charset=utf8') 

#从数据库读取数据
#由于python 3 不支持MySQLdb,所以这里将文件中的MySQLdb,改为pymysql便可运行
job = pd.read_sql('job',engine)
company = pd.read_sql('company',engine)

第二步,数据基本情况查看

job.head()  #前几条数据查看
image
job.shape   #查看形状
image
sum(job.duplicated())   #查看重复情况
image
job.info() #查看数据类型,空值存在情况
image

从以上的基本情况上,可以看到以下问题:

  1. 职位名称中含有实习等因素,为了方便统计,这里去除实习职位的影响。
  2. experience、salary都是一个区间,后续探讨experience、salary 和education 的关系需要将具体数值提取出来,转换为float型数据
  3. 含有重复职位数据,这里需要剔除。

第三步,清洗数据

#完全复制原dataframe中不重复的数据,方便后续的数据修改
job_df = job.drop_duplicates().copy()
#去除实习数据
job_df.drop(job_df[job_df.name.str.contains('实习')].index,inplace=True)
#新建工资水平high\low
job_df['lowS'] = job_df['salary'].apply(lambda x: x.lower().split('-')[0].replace('k','')).astype(int)
job_df['highS'] = job_df['salary'].apply(lambda x: x.lower().split('-')[1].replace('k','')).astype(int)
#为了更符合实际,取工资的区间前25%
job_df['avgS'] = job_df['lowS']+(job_df['highS']-job_df['lowS'])/4 
#新建一个工作年限,处理经验问题
#若为不限/应届毕业生,值为0;
#若为1年以下或10年以上,值分别为1,10;
#若为1到3年之类的,值取二者均值
job_df['workyear'] = job_df['experience'].str.findall(r'\d+')
def avg_year(i):
    m = 0
    if len(i) == 0:
        m = 0
    elif len(i) == 1:
        m = int(i[0])
    else:
        m = sum(list(map(int,i)))/2
    return m
job_df['workyear'] = job_df['workyear'].apply(avg_year)

4.薪酬和职位分析

from pyecharts import Geo
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

matplotlib.rc('font', **{'family' : 'SimHei'}) #解决中文乱码问题,必选
plt.style.use("ggplot")
#这里若不加plt.style.use("ggplot"),中文仍然乱码
  • 薪酬整体分布状况
plt.figure(figsize=(10,5))
sns.distplot(job_df['avgS'],color="r",kde=False)
plt.title('整体薪酬分布状况');
image

薪酬集中在12000附近,整体上呈偏右分布。

  • 不同城市之间的薪水差异?
plt.figure(figsize=(14,5))
sns.boxplot(x='city',y='avgS',data=job_df,color="r")
plt.title('不同城市之间的平均薪水差异');
image

放在中国地图上:

data_x = job_df.groupby('city')['avgS'].mean().reset_index()
geot = Geo("全国数据分析师平均薪酬分布", "数据来源:拉勾", title_color="#fff",
          title_pos="center", width=1200,
          height=600, background_color='#404a59')
attr= data_x['city'].values.tolist()
value = data_x['avgS'].values.tolist()
geot.add("",attr,value,type="heatmap", is_visualmap=True, visual_range=[0, 300],visual_text_color='#fff')
geot.render()
image

在地域分布的薪酬方面,北京、杭州和深圳的中位数薪酬牢牢的占据第一梯队高于15k,其次就是上海、成都、武汉等城市,中位数薪酬也在11k左右,最低的是天津,这有可能是于需求上人才都转移到北京去了。

  • 学历对薪酬的影响?
plt.figure(figsize=(14,5))
sns.boxplot(x='education',y='avgS',data=job_df,color="r")
plt.title('不同学历之间的薪水差异');
image

学历越高,获得高薪的机会也就越高,这说明读书发家还是有机会的。

  • 工作经验对薪酬的影响?
plt.figure(figsize=(14,5))
sns.boxplot(x="experience", y="avgS", data=job_df)
sns.stripplot(x='experience',y='avgS',data=job_df,jitter=True, color ='.3')
plt.title('不同工作经验之间的薪水差异');
image

拟合来看:

sns.regplot(x='workyear',y='avgS',data=job_df,x_jitter=.1,)
plt.title('工作年限与薪酬的拟合关系')
image

整体上来看,随着工作经验的增加,薪酬也在不断上升。低的工作经验有也高薪的机会,但可能需要更高的技能要求(如普通业务运营数据分析师和大数据挖掘工程师),不断提升自我的能力才是王道。

  • 数据分析师的工作福利和技能
#定义词云制作函数
def wordclouds(s):
    text = ''
    for line in job_df[s]:
        text = text+' '+line.strip().replace(',',' ')
    color_mask = imread('qqq.png')
    wordcloud = WordCloud(
                width=1000,height=600,
                font_path = 'simhei.ttf',
                background_color = 'white',
                mask = color_mask,
                max_words = 1000,
                max_font_size = 100, 
                collocations=False
                ).generate(text)
    wordcloud.to_file('{}.png'.format(s))
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.show()

#调用定义的词云制作函数
wordclouds('labels')
wordclouds('treatment')
image
image

在职位标签上,更多的是数据挖掘类的工作,当然像金融、大数据、业务运营、机器学习这几块也必不可少的。
五险一金作为基本的福利牢牢站在第一福利关键词,有些公司给出了六险一金(补充商业险)的待遇也很不错的,带薪年假弹性工作、周末双休牢牢吸引着眼球吧。
最后,我们探讨一下,数据分析师的需求问题:

  • 数据分析师的主要分布行业?
#合并工作职位和企业
df = company_df[['id','name','industry','scale']].merge(job_df[['id','company_id','avgS']],left_on='id',right_on='company_id')

#将industy里面的关键词取出来
industry_df = pd.DataFrame(df.industry.apply(lambda x: x.replace(' ,',' ').replace(',',' ').replace('、',' ').strip().split(' ')).tolist())
industry_df_new = industry_df.stack().reset_index().rename(columns={'level_0':'df_index',0:'industry_name'})
industry_df_new.drop('level_1', axis=1, inplace=True)

t = df.merge(industry_df_new,right_on='df_index',left_index=True)
tt['industry_name'].value_counts().plot.bar(figsize =(10,4),title=('数据分析师的需求行业分布'));
image

移动互联网需求独占鳌头,其次就是金融、数据服务类、电子商务类公司需求较大,当然可能会有部分企业行业标签重合。

  • 全国各地职位需求数分布问题?
#提取每个城市的职位数
data = job_df.groupby('city')['name'].count().reset_index()

#作图scatter
geo = Geo("全国数据分析师的需求分布", "数据来源:拉勾", title_color="#fff",
          title_pos="center", width=1200,
          height=600, background_color='#404a59')
attr= data['city'].values.tolist()
value = data['name'].values.tolist()
geo.add("",attr,value,type="scatter", is_visualmap=True, visual_range=[50, 2000],visual_text_color='#fff',visual_type='size', visual_range_size=[20, 80])
geo.render()
image

求职机会上,虽然说一线城市机会多,但一些准一线城市也在蓬勃发展中。

结论​

  1. 数据分析师的平均薪水集中在12k附近,整体上呈右分布
  2. 随着经验和学历的增加,整体上来看数据分析师的薪酬在不断上升中。
  3. 目前市场上数据分析师的人才缺口,更多的是需要掌握数据挖掘的技能。
  4. 数据分析师的需求方面,移动互联网需求独占鳌头,其次就是金融、数据服务类、电子商务类公司需求较大
  5. 数据分析师的求职方面,虽然说一线城市机会多,但一些准一线城市也在蓬勃发展中。

局限性:

  1. 事实上,一线城市北上广深杭的职位可能会远多于其他城市,因为拉勾仅限获取前30页,这些城市的职位或多或少大于30页的。
  2. 数据分析师的技能是影响薪酬的重要因素,但本次分析并没有体现出来。

——
爬虫+分析完整代码:https://github.com/mimicolois/lagou

PS:
1.git 是非常强大的工具,学会使用git,从注册一个github账号开始
2.嫌弃国内访问速度,代码托管国内有码云
3.git 入门指南:廖雪峰老师的Git教程

推荐阅读更多精彩内容