####基本环境配置
- win10
- python3.7,
- pycharm
- upyter notebook
- 明确分析目的
了解数据分析岗位的最新招聘情况, 包括地区分布, 学历要求, 经验要求, 薪资水平等.
- 数据收集
这里借助爬虫, 爬取招聘网站的招聘信息, 进而分析出相关的薪资以及招聘要求.
2.1 目标站点分析
通过对目标站点的分析, 我们需要确定目标站点的请求方式, 以及网页结构.
2.2 新建scrapy项目
- 在cmd命令行窗口中任意路径下执行以下代码, 比如在"D:\python\Tests"目录下新建zhaopin项目.
d:
cd D:\python\Tests
scrapy startproject zhaopin
- 在完成了zhaopin项目创建之后, 接下来就是在zhaopin项目文件夹中新建spider爬虫主程序
cd zhaopin
scrapy genspider zhaopinSpider zhaopin.com
这样就完成项目zhaopin的创建, 开始编写我们的程序吧.
2.3 定义items
在items.py文件中定义需要爬取的招聘信息.
import scrapy
from scrapy.item import Item, Field
'''
遇到不懂的问题?Python学习交流群:1136201545满足你的需求,资料都已经上传群文件,可以自行下载!
'''
class zhaopinItem(Item):
# define the fields for your item here like:
# name = scrapy.Field()
JobTitle = Field() #职位名称
CompanyName = Field() #公司名称
CompanyNature = Field() #公司性质
CompanySize = Field() #公司规模
IndustryField = Field() #所属行业
Salary = Field() #薪水
Workplace = Field() #工作地点
Workyear = Field() #要求工作经验
Education = Field() #要求学历
RecruitNumbers = Field() #招聘人数
ReleaseTime = Field() #发布时间
Language = Field() #要求语言
Specialty = Field() #要求专业
PositionAdvantage = Field() #职位福利
2.4 编写爬虫主程序
在zhaopinSpider.py文件中编写爬虫主程序
import scrapy
from scrapy.selector import Selector
from scrapy.spiders import CrawlSpider
from scrapy.http import Request
from zhaopin.items import zhaopinItem
class ZhaoPinSpider(scrapy.Spider):
name = "ZhaoPinSpider"
allowed_domains = ['zhaopin.com']
start_urls = ['https://xxxx.com/list/2,{0}.html?'.format(str(page)) for page in range(1, 217)]
def parse(self, response):
'''
开始第一页
:param response:
:return:
'''
yield Request(
url = response.url,
callback = self.parse_job_url,
meta={},
dont_filter= True
)
def parse_job_url(self, response):
'''
获取每页的职位详情页url
:param response:
:return:
'''
selector = Selector(response)
urls = selector.xpath('//div[@class="el"]/p/span')
for url in urls:
url = url.xpath('a/@href').extract()[0]
yield Request(
url = url,
callback = self.parse_job_info,
meta = {},
dont_filter = True
)
def parse_job_info(self, response):
'''
解析工作详情页
:param response:
:return:
'''
item = Job51Item()
selector = Selector(response)
JobTitle = selector.xpath('//div[@class="cn"]/h1/text()').extract()[0].strip().replace(' ','').replace(',',';')
CompanyName = selector.xpath('//div[@class="cn"]/p[1]/a[1]/text()').extract()[0].strip().replace(',',';')
CompanyNature = selector.xpath('//div[@class="tCompany_sidebar"]/div/div[2]/p[1]/text()').extract()[0].strip().replace(',',';')
CompanySize = selector.xpath('//div[@class="tCompany_sidebar"]/div/div[2]/p[2]/text()').extract()[0].strip().replace(',',';')
IndustryField = selector.xpath('//div[@class="tCompany_sidebar"]/div/div[2]/p[3]/text()').extract()[0].strip().replace(',',';')
Salary = selector.xpath('//div[@class="cn"]/strong/text()').extract()[0].strip().replace(',',';')
infos = selector.xpath('//div[@class="cn"]/p[2]/text()').extract()
Workplace = infos[0].strip().replace(' ','').replace(',',';')
Workyear = infos[1].strip().replace(' ','').replace(',',';')
if len(infos) == 4:
Education = ''
RecruitNumbers = infos[2].strip().replace(' ', '').replace(',',';')
ReleaseTime = infos[3].strip().replace(' ', '').replace(',',';')
else:
Education = infos[2].strip().replace(' ', '').replace(',',';')
RecruitNumbers = infos[3].strip().replace(' ', '').replace(',',';')
ReleaseTime = infos[4].strip().replace(' ', '').replace(',',';')
if len(infos) == 7:
Language, Specialty = infos[5].strip().replace(' ',''), infos[6].strip().replace(' ','').replace(',',';')
elif len(infos) == 6:
if (('英语' in infos[5]) or ('话' in infos[5])):
Language, Specialty = infos[5].strip().replace(' ','').replace(',',';'), ''
else:
Language, Specialty = '', infos[5].strip().replace(' ','').replace(',',';')
else:
Language, Specialty = '', ''
Welfare = selector.xpath('//div[@class="t1"]/span/text()').extract()
PositionAdvantage = ';'.join(Welfare).replace(',', ';')
item['JobTitle'] =JobTitle
item['CompanyName'] =CompanyName
item['CompanyNature'] =CompanyNature
item['CompanySize'] = CompanySize
item['IndustryField'] = IndustryField
item['Salary'] =Salary
item['Workplace'] = Workplace
item['Workyear'] =Workyear
item['Education'] =Education
item['RecruitNumbers'] = RecruitNumbers
item['ReleaseTime'] =ReleaseTime
item['Language'] = Language
item['Specialty'] = Specialty
item['PositionAdvantage'] = PositionAdvantage
yield item
2.5 保存到csv文件
通过pipelines项目管道保存至csv文件
class Job51Pipeline(object):
def process_item(self, item, spider):
with open(r'D:\Data\ZhaoPin.csv','a', encoding = 'gb18030') as f:
job_info = [item['JobTitle'], item['CompanyName'], item['CompanyNature'], item['CompanySize'], item['IndustryField'], item['Salary'], item['Workplace'], item['Workyear'], item['Education'], item['RecruitNumbers'], item['ReleaseTime'],item['Language'],item['Specialty'],item['PositionAdvantage'],'\n']
f.write(",".join(job_info))
return item
2.6 配置setting
设置用户代理, 下载延迟0.5s, 关闭cookie追踪, 调用pipelines
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
DOWNLOAD_DELAY = 0.5
COOKIES_ENABLED = False
ITEM_PIPELINES = {
'job51.pipelines.Job51Pipeline': 300,
}
2.7 运行程序
新建main.py文件, 并执行以下代码
from scrapy import cmdline
cmdline.execute('scrapy crawl zhaopin'.split())
这样开始了数据爬取, 最终爬取到9000多条数据, 在分析这些数据之前, 先看看数据都是什么样, 进入数据概览环节.
- 数据概览
3.1 读取数据
import pandas as pd
df = pd.read_csv(r'D:\aPython\Data\DataVisualization\shujufenxishiJob51.csv')
#由于原始数据中没有字段, 需要为其添加字段
df.columns = ['JobTitle','CompanyName','CompanyNature','CompanySize','IndustryField','Salary','Workplace','Workyear','Education','RecruitNumbers', 'ReleaseTime','Language','Specialty','PositionAdvantage']
df.info()
抛出异常: UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xbd in position 0: invalid start byte
解决办法; 用Notepad++将编码转换为utf-8 bom格式
转换之后, 再次执行
抛出异常: ValueError: Length mismatch: Expected axis has 15 elements, new values have 14 elements
解决办法: 在列表[‘JobTitle…PositionAdvantage’]后面追加’NNN’, 从而补齐15个元素.
追加之后, 再次执行, 执行结果为:
可以了解到的信息: 目前的数据维度9948行X15列, Education, Language, Specialty, PositionAdvantage有不同程度的缺失(NNN是最后添加, 仅仅是用来补齐15元素), 14个python对象(1个浮点型)
3.2 描述性统计
由于我们所需信息的数据类型都是python对象, 故使用以下代码
#注意是大写的字母o df.describe(include=['O'])
从以下信息(公司名称部分我没有截图)中可以得到:
职位名称中’数据分析师’最多, 多为民营公司, 公司规模150-500人最多, 行业领域金融/投资/证券最多, 薪资中6-8千/月最多, 大多对工作经验没有要求, 学历要求多为本科, 多数均招1人等信息.
职位名称的种类就有4758种, 他们都是我们本次分析的数据分析师岗位吗, 先来确认下:
职位名称的种类就有4758种, 他们都是我们本次分析的数据分析师岗位吗, 先来确认下:
zhaopin.JobTitle.unique()
array([‘零基础免费培训金融外汇数据分析师’, ‘数据分析师(周末双休+上班舒适)’, ‘数据分析师’, …,
‘数据分析实习(J10635)’, ‘数据分析实习(J10691)’, ‘数据分析实习(J10713)’], dtype=object)
这仅仅显示了职位名称中的一部分,而且还都符合要求, 换种思路先看20个
JobTitle = zhaopin.groupby(‘JobTitle’, as_index=False).count()
JobTitle.JobTitle.head(20)
###可视化分析
import matplotlib
import matplotlib.pyplot as plt
CompanyNature_Count = zhaopin.CompanyNature.value_counts()
#设置中文字体
font = {'family': 'SimHei'}
matplotlib.rc('font', **font)
fig = plt.figure(figsize = (8, 8))
#绘制饼图, 参数pctdistance表示饼图内部字体离中心距离, labeldistance则是label的距离, radius指饼图的半径
patches, l_text, p_text = plt.pie(CompanyNature_Count, autopct = '%.2f%%', pctdistance = 0.6, labels = CompanyNature_Count.index, labeldistance=1.1, radius = 1)
m , n= 0.02, 0.028
for t in l_text[7: 11]:
t.set_y(m)
m += 0.1
for p in p_text[7: 11]:
p.set_y(n)
n += 0.1
plt.title('数据分析岗位中各类型企业所占比例', fontsize=24)
以看出招聘中主要以民营企业, 合资企业和上市公司为主.
from pyecharts import Geo
from collections import Counter
#统计各地区出现次数, 并转换为元组的形式
data = Counter(place).most_common()
#生成地理坐标图
geo =Geo("数据分析岗位各地区需求量", title_color="#fff", title_pos="center", width=1200, height=600, background_color='#404a59')
attr, value =geo.cast(data)
#添加数据点
geo.add('', attr, value, visual_range=[0, 100],visual_text_color='#fff', symbol_size=5, is_visualmap=True, is_piecewise=True)
geo.show_config()
geo.render()
可以看出北上广深等经济相对发达的地区, 对于数据分析岗位的需求量大.