——2018.06.01——
最近幾天看到了很多文章寫拉勾網(wǎng)職位爬取幢踏,那些基本是基于requests + json,但是貌似所給url打不開许师,還有可能被封ip的風(fēng)險房蝉。
本文主要用selenium + BautifulSoup + xpath工具,爬取不同城市的數(shù)據(jù)分析師薪酬微渠,閑話不多說搭幻,上菜。
- 爬蟲 selenium + BautifulSoup + xpath
- 儲存數(shù)據(jù) MySQL
- 數(shù)據(jù)清理
- 薪酬和職位需求分析 seaborn + pyecharts
——
編譯環(huán)境:Python 3.5
IDE:jupyter notebook
1.網(wǎng)頁爬取
第一步逞盆,導(dǎo)入所需的模塊:
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
第二步檀蹋,設(shè)置瀏覽器參數(shù):
#設(shè)置無頭的chrome瀏覽器參數(shù)
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
#運(yùn)行瀏覽器
driver = webdriver.Chrome(chrome_options=chrome_options)
#備注::selenium 3.0+版本已不支持PhantomJS,所以這里使用無頭的chrome brower
第三步纳击,找到合適url開始爬蟲续扔,這里以長沙為例:
url = "https://www.lagou.com/jobs/list_數(shù)據(jù)分析師?px=default&city={}#filterBox".format('長沙')
driver.get(url)
time.sleep(3) #設(shè)置等待時間,強(qiáng)烈建議
第四步焕数,解析網(wǎng)頁
# BeautifulSoup解析獲取職位的福利和標(biāo)簽
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等其他數(shù)據(jù)
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()')
#利用自定義的分隔函數(shù)堡赔,得到所需的值
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.將數(shù)據(jù)存入MySQL數(shù)據(jù)庫中
在Python鏈接之前,已在MySQL數(shù)據(jù)庫设联,創(chuàng)建數(shù)據(jù)庫lagou,數(shù)據(jù)表job,company
#導(dǎo)入模塊 pymysql
import pymysql
db = pymysql.connect("localhost", "root", "123456", "lagou",use_unicode=True, charset="utf8")
cursor = db.cursor()
#定義一個插入到數(shù)據(jù)庫的函數(shù)
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)
#將數(shù)據(jù)插入到數(shù)據(jù)庫中
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")
——
翻頁問題:
拉勾上的職位內(nèi)容基于js動態(tài)加載善已,所以點(diǎn)擊下一頁后,地址仍不生變化离例。但是仔細(xì)觀察elements,最后一頁會存在標(biāo)簽:<span …… class = 'pager_next pager_next_disabled' 换团,我們可以利用這個標(biāo)簽來定位。
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)) #爬蟲禮儀,減少訪問服務(wù)器的頻率
以上就是拉勾數(shù)據(jù)分析師職位數(shù)據(jù)爬取和儲存耀盗,這里為了顯得整潔想虎,分別存儲在job,company兩個表中。為了便于閱讀叛拷,完整代碼放在文末鏈接中舌厨,有需要可以自己去fork。
3.數(shù)據(jù)清理
首先忿薇,導(dǎo)入數(shù)據(jù):
import pandas as pd
from sqlalchemy import create_engine
import pymysql
#創(chuàng)建engine
engine = create_engine('mysql://root:123456@localhost:3306/lagou?charset=utf8')
#從數(shù)據(jù)庫讀取數(shù)據(jù)
#由于python 3 不支持MySQLdb,所以這里將文件中的MySQLdb,改為pymysql便可運(yùn)行
job = pd.read_sql('job',engine)
company = pd.read_sql('company',engine)
第二步裙椭,數(shù)據(jù)基本情況查看
job.head() #前幾條數(shù)據(jù)查看
job.shape #查看形狀
sum(job.duplicated()) #查看重復(fù)情況
job.info() #查看數(shù)據(jù)類型躏哩,空值存在情況
從以上的基本情況上,可以看到以下問題:
- 職位名稱中含有實(shí)習(xí)等因素揉燃,為了方便統(tǒng)計扫尺,這里去除實(shí)習(xí)職位的影響。
- experience你雌、salary都是一個區(qū)間器联,后續(xù)探討experience、salary 和education 的關(guān)系需要將具體數(shù)值提取出來婿崭,轉(zhuǎn)換為float型數(shù)據(jù)
- 含有重復(fù)職位數(shù)據(jù)拨拓,這里需要剔除。
第三步氓栈,清洗數(shù)據(jù)
#完全復(fù)制原dataframe中不重復(fù)的數(shù)據(jù),方便后續(xù)的數(shù)據(jù)修改
job_df = job.drop_duplicates().copy()
#去除實(shí)習(xí)數(shù)據(jù)
job_df.drop(job_df[job_df.name.str.contains('實(shí)習(xí)')].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)
#為了更符合實(shí)際渣磷,取工資的區(qū)間前25%
job_df['avgS'] = job_df['lowS']+(job_df['highS']-job_df['lowS'])/4
#新建一個工作年限,處理經(jīng)驗(yàn)問題
#若為不限/應(yīng)屆畢業(yè)生授瘦,值為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('整體薪酬分布狀況');
薪酬集中在12000附近,整體上呈偏右分布打肝。
- 不同城市之間的薪水差異脂新?
plt.figure(figsize=(14,5))
sns.boxplot(x='city',y='avgS',data=job_df,color="r")
plt.title('不同城市之間的平均薪水差異');
放在中國地圖上:
data_x = job_df.groupby('city')['avgS'].mean().reset_index()
geot = Geo("全國數(shù)據(jù)分析師平均薪酬分布", "數(shù)據(jù)來源:拉勾", 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()
在地域分布的薪酬方面,北京粗梭、杭州和深圳的中位數(shù)薪酬牢牢的占據(jù)第一梯隊高于15k争便,其次就是上海、成都断医、武漢等城市滞乙,中位數(shù)薪酬也在11k左右,最低的是天津孩锡,這有可能是于需求上人才都轉(zhuǎn)移到北京去了酷宵。
- 學(xué)歷對薪酬的影響?
plt.figure(figsize=(14,5))
sns.boxplot(x='education',y='avgS',data=job_df,color="r")
plt.title('不同學(xué)歷之間的薪水差異');
學(xué)歷越高躬窜,獲得高薪的機(jī)會也就越高浇垦,這說明讀書發(fā)家還是有機(jī)會的。
- 工作經(jīng)驗(yàn)對薪酬的影響荣挨?
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('不同工作經(jīng)驗(yàn)之間的薪水差異');
擬合來看:
sns.regplot(x='workyear',y='avgS',data=job_df,x_jitter=.1,)
plt.title('工作年限與薪酬的擬合關(guān)系')
整體上來看男韧,隨著工作經(jīng)驗(yàn)的增加朴摊,薪酬也在不斷上升。低的工作經(jīng)驗(yàn)有也高薪的機(jī)會此虑,但可能需要更高的技能要求(如普通業(yè)務(wù)運(yùn)營數(shù)據(jù)分析師和大數(shù)據(jù)挖掘工程師)甚纲,不斷提升自我的能力才是王道。
- 數(shù)據(jù)分析師的工作福利和技能
#定義詞云制作函數(shù)
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()
#調(diào)用定義的詞云制作函數(shù)
wordclouds('labels')
wordclouds('treatment')
在職位標(biāo)簽上朦前,更多的是數(shù)據(jù)挖掘類的工作介杆,當(dāng)然像金融、大數(shù)據(jù)韭寸、業(yè)務(wù)運(yùn)營春哨、機(jī)器學(xué)習(xí)這幾塊也必不可少的。
五險一金作為基本的福利牢牢站在第一福利關(guān)鍵詞恩伺,有些公司給出了六險一金(補(bǔ)充商業(yè)險)的待遇也很不錯的赴背,帶薪年假彈性工作、周末雙休牢牢吸引著眼球吧晶渠。
最后凰荚,我們探討一下,數(shù)據(jù)分析師的需求問題:
- 數(shù)據(jù)分析師的主要分布行業(yè)褒脯?
#合并工作職位和企業(yè)
df = company_df[['id','name','industry','scale']].merge(job_df[['id','company_id','avgS']],left_on='id',right_on='company_id')
#將industy里面的關(guān)鍵詞取出來
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=('數(shù)據(jù)分析師的需求行業(yè)分布'));
移動互聯(lián)網(wǎng)需求獨(dú)占鰲頭,其次就是金融番川、數(shù)據(jù)服務(wù)類胳徽、電子商務(wù)類公司需求較大,當(dāng)然可能會有部分企業(yè)行業(yè)標(biāo)簽重合爽彤。
- 全國各地職位需求數(shù)分布問題?
#提取每個城市的職位數(shù)
data = job_df.groupby('city')['name'].count().reset_index()
#作圖scatter
geo = Geo("全國數(shù)據(jù)分析師的需求分布", "數(shù)據(jù)來源:拉勾", 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()
求職機(jī)會上缚陷,雖然說一線城市機(jī)會多适篙,但一些準(zhǔn)一線城市也在蓬勃發(fā)展中。
結(jié)論?
- 數(shù)據(jù)分析師的平均薪水集中在12k附近箫爷,整體上呈右分布
- 隨著經(jīng)驗(yàn)和學(xué)歷的增加嚷节,整體上來看數(shù)據(jù)分析師的薪酬在不斷上升中。
- 目前市場上數(shù)據(jù)分析師的人才缺口虎锚,更多的是需要掌握數(shù)據(jù)挖掘的技能硫痰。
- 數(shù)據(jù)分析師的需求方面,移動互聯(lián)網(wǎng)需求獨(dú)占鰲頭窜护,其次就是金融效斑、數(shù)據(jù)服務(wù)類、電子商務(wù)類公司需求較大
- 數(shù)據(jù)分析師的求職方面柱徙,雖然說一線城市機(jī)會多缓屠,但一些準(zhǔn)一線城市也在蓬勃發(fā)展中奇昙。
?
局限性:
- 事實(shí)上,一線城市北上廣深杭的職位可能會遠(yuǎn)多于其他城市敌完,因?yàn)槔磧H限獲取前30頁储耐,這些城市的職位或多或少大于30頁的。
- 數(shù)據(jù)分析師的技能是影響薪酬的重要因素滨溉,但本次分析并沒有體現(xiàn)出來什湘。
——
爬蟲+分析完整代碼:https://github.com/mimicolois/lagou
PS:
1.git 是非常強(qiáng)大的工具,學(xué)會使用git晦攒,從注冊一個github賬號開始
2.嫌棄國內(nèi)訪問速度闽撤,代碼托管國內(nèi)有碼云
3.git 入門指南:廖雪峰老師的Git教程