猎聘网数据分析职位的抓取与清洗
2017-05-11 本文已影响0人
海墨星人
liepin.jpg
首先自我批评一下,上周出差没有更新博客,本周继续
上周突发奇想,想看看当前数据分析岗位的薪水情况。所以抓取了猎聘网20170427关于数据分析的所有100页内容。由于本人能力有限,整理笔记如下,以备后来翻阅。
数据抓取
数据抓取中遇到的困难
1. 本人爬虫功底有限,看了几页就照葫芦画瓢,代码能力不好
2. 猎聘网的安全防护阻止我100次循环连续抓取内容,所以只能连续20几页的抓取
3. 本打算获取技能要求的内容,但由于是动态加载,再加上时间有限,就没有过多的研究。所以在本章中没有实现抓取技能要求的内容。
爬虫代码
# -*- coding:utf-8 -*-
#__author__ = 'ecaoyng'
import urllib.request
import re
import csv
import time
import codecs
class LiePinCrawler:
def __init__(self):
self.user_agent = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36'
self.headers = {'User-Agent': self.user_agent}
self.jobs=[]
self.csv='C:/Users/ecaoyng/Desktop/work space/job.csv'
# 获取搜索结果的某一页
def getPage(self, pageIndex):
try:
url = 'https://www.liepin.com/zhaopin/?pubTime=&ckid=757c5403caae67a7&fromSearchBtn=2&compkind=&isAnalysis=&init=-1&searchType=1&dqs=&industryType=&jobKind=&sortFlag=15&industries=&salary=&compscale=&key=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&clean_condition=&headckid=757c5403caae67a7&curPage='
url=url + str(pageIndex)
# print(url)
request = urllib.request.Request(url,headers = self.headers)
response = urllib.request.urlopen(request)
pageCode = response.read().decode('utf-8')
print (pageCode)
print('='*60)
return pageCode
except urllib.request.URLError as e:
if hasattr(e, "reason"):
print(u"Errors during connect to LiePin:", e.reason)
return None
#获取职位详细描述
def getDesPage(self, contentURL):
contentURL='https://www.liepin.com/job/198467615.shtml?imscid=R000000075&ckid=4704ed8e99af8c70&headckid=4704ed8e99af8c70&pageNo=0&pageIdx1&totalIdx=1&sup=1'
print('contentURL is %s' % contentURL)
request = urllib.request.Request(contentURL, headers=self.headers)
response = urllib.request.urlopen(request)
desPage = response.read().decode('utf-8')
print(desPage)
return desPage
#加载一页并过滤需要的内容
def getItems(self,pageIndex):
pageCode=self.getPage(pageIndex)
if not pageCode:
print('页面加载失败...')
return None
pattern = re.compile(
'<div class="job-info">.*?<h3 title.*?>.*?<a href="(.*?)".*?>(.*?)</a>.*?</h3>.*?<span class="text-warning">(.*?)</span>.*?<a href.*?>(.*?)</a>.*?<span class.*?>(.*?)</span>.*?<span>(.*?)</span>.*?<a title=.*?>(.*?)</a>.*?<a class.*?>(.*?)</a>.*?<p class=.*?>(.*?)</p>',
re.S)
items = re.findall(pattern, pageCode)
for item in items:
# contentPage=self.getDesPage(item[0].strip())
# print(contentPage)
break
temp=re.sub('\s+','',item[8].strip())
temp=re.sub('</span>','',temp)
words=re.split(r'<span>',temp)
self.jobs.append([item[0].strip(),item[1].strip(),item[2].strip(),item[3].strip(),item[4].strip(),item[5].strip(),item[6].strip(),item[7].strip(),words])
print(self.jobs)
return self.jobs
#每次读取一页内容并写入文件
def writeCSV(self):
# for i in self.jobs:
# print(i)
with open(self.csv, 'a+',encoding='utf-8') as csvFile:
# csvFile.write(codecs.BOM_UTF8)
csvwriter = csv.writer(csvFile, dialect=("excel"))
# csvwriter.writerow(['Jobs','Salary','City','Edu','Years','Company','Industry','Key words'])
for row in self.jobs:
print(row)
csvwriter.writerow(row)
self.jobs=[]
if __name__== '__main__':
liepin=LiePinCrawler()
for i in range(0,20): # 可以修改此处,每次抓取指定页的内容
time.sleep(5)
liepin.getItems(i)
#由于猎聘封号,所以只能读一页写一页
liepin.writeCSV()
抓取的文件
由于不知该如何上传,所以无法展示供大家下载
抓取的文件放到excel中显示
由于抓取的文件是uft-8格式,放在excel中显示的中文乱码,但是在utraledit和notepad中可以正常打开。这是excel的问题,可以不用理睬,但如果非想用excel打开的话,解决方法如下.
1. 将excel后缀名改为txt
2. 打开Excel-> Data->Get Data From Text
3. 打开txt,根据需求选择并next
数据清洗
数据分析中占用时间最多的就是数据清洗。
原始数据的特点:
1. 在年薪payment中会出现面议,保密等字样
2. 在年薪payment中出现13-20万这样的区间,不利于分析
3. 会有重复数据
5. 在place中有一行数据为空
4. 在地域中会出现类似北京、上海、深圳 这样的数据
针对如上的问题,解决思路如下:
1. 将保密替换为面议方便处理
2. 将一个13-20万这样的字段,拆分成三个income_min, income_max并计算income_avg
3. 丢弃掉重复数据
4. 在替他条件都整理好的情况下,将一条北京、上海、深圳这样的row,复制成三条数据并删除原有的行
下面来看下原始数据的情况
import pandas as pd
srcFile='C:\\Users\\ecaoyng\\Desktop\\work space\\job.csv'
#加入header=None是为了不把第一行数据方做header
liepin=pd.read_csv(srcFile,header=None)
liepin.columns=['jobs','payment','place','qualifications','experience','company','area','walfare']
# liepin.columns=['职位','年薪','工作地点','学历','工作年限','公司','行业','公司福利']
liepin.head()
jobs | payment | place | qualifications | experience | company | area | walfare | |
---|---|---|---|---|---|---|---|---|
0 | 数据分析主管(用户研究) | 13-20万 | 上海 | 本科或以上 | 3年工作经验 | 资邦咨询 | 基金/证券/期货/投资 | ['', '发展空间大', '技能培训', '岗位晋升', '五险一金', '绩效奖金', ... |
1 | 数据分析岗(客服运营中心) | 面议 | 上海 | 本科或以上 | 1年工作经验 | 深圳平安综合金融服务有限公司 | 保险 | ['<spanclass="text-warning">12-18万<ahref="http... |
2 | 数据分析经理 | 面议 | 上海 | 本科或以上 | 3年工作经验 | 善林(上海)金融 | 基金/证券/期货/投资 | ['', '年底双薪', '绩效奖金', '带薪年假', '管理规范', '技能培训', '... |
3 | 数据分析 | 5-7万 | 北京 | 本科或以上 | 经验不限 | 北京众信优联科技有限公司 | 互联网/移动互联网/电子商务 | ['', '带薪年假', '午餐补助', '五险一金'] |
4 | 数据分析研究员 | 面议 | 北京 | 本科或以上 | 1年工作经验 | 360 | 互联网/移动互联网/电子商务 | ['', '午餐补助', '绩效奖金', '五险一金', '节日礼物', '免费班车', '... |
liepin.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3939 entries, 0 to 3938
Data columns (total 8 columns):
jobs 3939 non-null object
payment 3939 non-null object
place 3938 non-null object
qualifications 3939 non-null object
experience 3939 non-null object
company 3939 non-null object
area 3939 non-null object
walfare 3939 non-null object
dtypes: object(8)
memory usage: 246.3+ KB
#丢弃重复数据
liepin.drop_duplicates()
发现1646行的place为空,经查实应该是成都,填充上
#发现异常值在1646行
liepin.iloc[1646,2:3]='成都'
liepin.iloc[1646,:]
发现place中有类似上海-浦东区这样的内容,同意将其过滤为上海并添加一个城市字段
# 将city标准化
city=[]
for x in liepin.place:
print(x)
if '-' in x:
x=x[0:x.index('-')]
city.append(x)
liepin['City']=city
liepin.head(3)
jobs | payment | place | qualifications | experience | company | area | walfare | City | |
---|---|---|---|---|---|---|---|---|---|
0 | 数据分析主管(用户研究) | 13-20万 | 上海 | 本科或以上 | 3年工作经验 | 资邦咨询 | 基金/证券/期货/投资 | ['', '发展空间大', '技能培训', '岗位晋升', '五险一金', '绩效奖金', . | 上海 |
1 | 数据分析岗(客服运营中心) | 面议 | 上海 | 本科或以上 | 1年工作经验 | 深圳平安综合金融服务有限公司 | 保险 | ['<spanclass="text-warning">12-18万<ahref="http... | 上海 |
2 | 数据分析经理 | 面议 | 上海 | 本科或以上 | 3年工作经验 | 善林(上海)金融 | 基金/证券/期货/投资 | ['', '年底双薪', '绩效奖金', '带薪年假', '管理规范', '技能培训', '... | 上海 |
按照之前所讲会出现多地址的问题
liepin.iloc[923,:]
Out[322]:
jobs 数据分析P6 p7 p8 p9
payment 40-70万
place 上海,北京,浙江省
qualifications 本科或以上
experience 3年工作经验
company 国内最大互联网企业
area 互联网/移动互联网/电子商务
walfare ['<iclass="icons24icons24-honesty"></i><em>该职位...
City 上海,北京,浙江省
Name: 923, dtype: object
另外,本打算丢弃掉面议的row,但无奈太多,不能丢弃
liepin[liepin.payment=='面议'].info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 907 entries, 1 to 3938
Data columns (total 9 columns):
jobs 907 non-null object
payment 907 non-null object
place 907 non-null object
qualifications 907 non-null object
experience 907 non-null object
company 907 non-null object
area 907 non-null object
walfare 907 non-null object
City 907 non-null object
dtypes: object(9)
memory usage: 70.9+ KB
为了对薪资进行处理,根据年薪字段,增加三个薪水字段,分别为最低年薪,最高年薪,平均年薪
#新建三个地段来描述薪资情况
import re
income_avg=[]
income_min=[]
income_max=[]
for i in liepin.payment:
if i.strip()== '面议':
income_avg.append('面议')
income_min.append('面议')
income_max.append('面议')
elif(i.strip()=='保密'):
# 数据中有一行的薪水是保密,将其改为面议
income_avg.append('面议')
income_min.append('面议')
income_max.append('面议')
else:
pattern=r'(\d+)-(\d+).*'
regex=re.compile(pattern)
m=regex.match(i)
income_min.append(m.group(1))
income_max.append(m.group(2))
int_min=int(m.group(1))
int_max=int(m.group(2))
income_avg.append(format((int_min+int_max)/2,'.2f'))
liepin['income_min']=income_min
liepin['income_max']=income_max
liepin['income_avg']=income_avg
增加字段后的表结构如下
jobs | payment | place | qualifications | experience | company | area | walfare | City | income_min | income_max | income_avg | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 数据分析主管(用户研究) | 13-20万 | 上海 | 本科或以上 | 3年工作经验 | 资邦咨询 | 基金/证券/期货/投资 | ['', '发展空间大', '技能培训', '岗位晋升', '五险一金', '绩效奖金', ... | 上海 | 13 | 20 | 16.50 |
1 | 数据分析岗(客服运营中心) | 面议 | 上海 | 本科或以上 | 1年工作经验 | 深圳平安综合金融服务有限公司 | 保险 | ['<spanclass="text-warning">12-18万<ahref="http... | 上海 | 面议 | 面议 | 面议 |
2 | 数据分析经理 | 面议 | 上海 | 本科或以上 | 3年工作经验 | 善林(上海)金融 | 基金/证券/期货/投资 | ['', '年底双薪', '绩效奖金', '带薪年假', '管理规范', '技能培训', '... | 上海 | 面议 | 面议 | 面议 |
现在表的整体信息是:
liepin.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3939 entries, 0 to 3938
Data columns (total 12 columns):
jobs 3939 non-null object
payment 3939 non-null object
place 3939 non-null object
qualifications 3939 non-null object
experience 3939 non-null object
company 3939 non-null object
area 3939 non-null object
walfare 3939 non-null object
City 3939 non-null object
income_min 3939 non-null object
income_max 3939 non-null object
income_avg 3939 non-null object
dtypes: object(12)
memory usage: 369.4+ KB
对多地址行进行复制
# 显示多地址行
liepin[liepin.place.str.contains(',')].head(2)
jobs | payment | place | qualifications | experience | company | area | walfare | City | income_min | income_max | income_avg | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
827 | 数据分析工程师 | 20-40万 | 北京,上海,深圳 | 本科或以上 | 3年工作经验 | 全国知名的风电企业 | 能源(电力/水利) | ['<iclass="icons24icons24-honesty"></i><em>该职位... | 北京,上海,深圳 | 20 | 40 | 30.00 |
828 | 数据分析专员 | 10-15万 | 北京,上海,深圳 | 本科或以上 | 经验不限 | 全国知名的风电企业 | 能源(电力/水利) | ['<iclass="icons24icons24-honesty"></i><em>该职位... | 北京,上海,深圳 | 10 | 15 | 12.50 |
#一共159条数据,无法忽略
liepin[liepin.place.str.contains(',')].info()
#获取一行dataframe一行数据
liepin.iloc[[826]]
# 将一行数据拆分成三行
for i in liepin[liepin.place.str.contains(',')].index:
for city in str(liepin.iloc[i].place).split(','):
row=pd.DataFrame([dict(jobs=liepin.iloc[i].jobs,
payment=liepin.iloc[i].payment,
place=city,
qualifications=liepin.iloc[i].qualifications,
experience=liepin.iloc[i].experience,
company=liepin.iloc[i].company,
area=liepin.iloc[i].area,
walfare=liepin.iloc[i].walfare,
City=city,
income_min=liepin.iloc[i].income_min,
income_max=liepin.iloc[i].income_max,
income_avg=liepin.iloc[i].income_avg,
)])
liepin=liepin.append(row,ignore_index=True)
liepin.tail()
City | area | company | experience | income_avg | income_max | income_min | jobs | payment | place | qualifications | walfare | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
4364 | 武汉 | 百货/批发/零售 | 国内某知名百货公司 | 5年工作经验 | 22.50 | 30 | 15 | 数据分析经理 | 15-30万 | 武汉 | 本科或以上 | ['<iclass="icons24icons24-honesty"></i><em>该职位... |
4365 | 东莞 | 中介服务 | 任仕达公司 | 12年工作经验 | 75.00 | 90 | 60 | 制造大数据分析架构师 (56688) | 60-90万 | 东莞 | 硕士或以上 | ['<iclass="icons24icons24-honesty"></i><em>该职位... |
4366 | 深圳 | 中介服务 | 任仕达公司 | 12年工作经验 | 75.00 | 90 | 60 | 制造大数据分析架构师 (56688) | 60-90万 | 深圳 | 硕士或以上 | ['<iclass="icons24icons24-honesty"></i><em>该职位... |
#丢弃重复数据
liepin.drop_duplicates()
# 查看多地址行,一共159行
liepin[liepin.place.str.contains(',')].index.size
#丢弃掉多地址字段
for i in liepin[liepin.place.str.contains(',')].index:
liepin.drop(i,inplace=True)
liepin.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4208 entries, 0 to 4366
Data columns (total 12 columns):
City 4208 non-null object
area 4208 non-null object
company 4208 non-null object
experience 4208 non-null object
income_avg 4208 non-null object
income_max 4208 non-null object
income_min 4208 non-null object
jobs 4208 non-null object
payment 4208 non-null object
place 4208 non-null object
qualifications 4208 non-null object
walfare 4208 non-null object
dtypes: object(12)
memory usage: 427.4+ KB
#check发现已经没有多地址的行
liepin[liepin.place.str.contains(',')].index.size # 0
#将其写入csv文件
targetDir='C:\\Users\\ecaoyng\\Downloads\\jobs_clean.csv'
liepin.to_csv(targetDir)
#数据清洗完成