学Python也将近大半年了,最终也是为了能够找到一个待遇好点的,薪资高一点的工作,使得自己以后不愁吃穿,过点体面的生活。那想应聘Python岗位应该如何选择,其有何要求,薪资怎么样,这些都是我们会去关注的问题。
我们在这里选择对前程无忧上关于Python的工作岗位进行抓取。本来是想去抓上万条数据的,可是点击Python关键字搜索后的25页左右,发现招聘岗位并无python关键词,只是在点击进去的工作要求里面提出了需要会python而已,所以,我们在这里只抓取了前25页的内容。
由于对工作的抓取需要进入一个新的URL,对于每一个页面,我们需要在增加50个请求(大概数了一下,好像一页大概有50条岗位需求),我们这里一共抓取24个岗位页面,也就是总共需要发出1200次请求,就算按0.5s处理一个来算,也需要600s,显然这需要的时间太长,对于性子急的人来说可不是什么好事儿。所以这个时候我们可以考虑使用异步的方法来进行数据的抓取。
代码如下:
import requests
from bs4 import BeautifulSoup
from pyquery import PyQuery as pq
import pandas as pd
from threading import Thread
import threading
import aiohttp
import asyncio
START_URL = (
'https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,{}.html?'
'lang=c&stype=1&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99'
'&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0'
'&address=&line=&specialarea=00&from=&welfare='
)
HEADERS = {
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36'
'(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
}
sem = threading.Semaphore(500) # 最多同时启用多少个线程
class JobSpider():
async def download_html(self, url):
"""
异步下载网页
:param session: 异步打开请求
:param url: 职位页面
"""
async with aiohttp.ClientSession() as session:
async with session.get(url, headers = HEADERS) as response:
return await response.text(encoding = 'gbk')
async def job_spider(self, main_html):
"""
爬取工作大体信息
:param session: 异步打开请求
:param url: 职位页面
"""
doc = pq(main_html)
results = doc('div.dw_table > div.el')
for result in results.items():
href = result.find('a').attr('href') # 岗位详情
if href == None: # 去除第一条数据(表头)
continue
else: # 对每个岗位详情页使用线程来进行抓取
th = Thread(target = self.main, args = (href, result, )) # 创建线程
th.start() # 启动线程
def main(self, href, result):
sem.acquire() # 准备创建启用一个线程,可用线程数量就会减1
response = requests.get(href, headers = HEADERS)
job_information_html = pq(response.text)
try:
item = pd.DataFrame({
'post_name': result.find('a').attr('title'), # 岗位名称,
'company_name': result.find('span.t2').text(), # 公司名称
'location': result.find('span.t3').text(), # 工作地点
'salary': result.find('span.t4').text(), # 薪资水平
'experience': job_information_html('p.msg.ltype').attr('title').split(' | ')[1], # 工作经验要求
'education': job_information_html('p.msg.ltype').attr('title').split(' | ')[2], # 教育要求
'job_info': job_information_html('div.bmsg.job_msg.inbox').text().split('\n职能类别: ')[0].replace('\n', ' '), # 职位信息
'job_class': job_information_html('div.mt10 > p.fp').text().split('职能类别: ')[1], # 工作类别
}, index = [0]) # index = [0] 不能省!,省掉的话会报错
self.write_to_csv(item)
except:
pass
sem.release() # 释放线程,可用线程数加1
def write_to_csv(self, item):
item.to_csv('datas/Python职位.csv', mode = 'a', encoding = 'utf_8_sig', index = False, header = None) # 数据存储
async def run(self, url):
"""
爬虫入口
"""
main_html = await self.download_html(url)
await self.job_spider(main_html)
if __name__ == '__main__':
jobspider = JobSpider()
url_tasks = [START_URL.format(page) for page in range(1, 26)] # 构建url列表
loop = asyncio.get_event_loop() # 利用asyncio模块解析异步IO处理
tasks = [asyncio.ensure_future(jobspider.run(url)) for url in url_tasks] # 获取任务列表
tasks = asyncio.gather(*tasks)
loop.run_until_complete(tasks)
在这里,将异步和线程结合起来,对于24个岗位页面的请求是异步的,然后对每个岗位请求里面的40个岗位介绍通过创建一个新的线程来实现我们所需要的数据的抓取和保存。同时对于发生异常的数据直接将其抛弃。该段代码的数据抓取仅用时36.5s,一共抓取了1195条数据。
目前为止,我们对于数据的收集已经完成了,接下来就需要对得到的数据来进行分析了。对于数据的分析最常用的就是pandas
,其方便快捷,能帮助我们快速的完成数据分析。pandas
中对于本地文件的读取很方便,只需通过read_csv
即可(我们这里保存的是csv文件)。
names = ['post_name', 'company_name', 'city', 'salary', 'experience', 'education', 'job_info', 'job_class']
datas = pd.read_csv('Python职位.csv', names = names, encoding = 'utf-8')
由于我们在保存数据时未设置列标题,所以需要通过names来规定读取结果的列名列表。
首先让我们看一下全国对于Python岗位的需求,我们选择绘制全国各地招聘数量的热力图和对Top10地区的招聘数量的柱状图,
代码如下:
def city_counter():
"""
全国工作分布热力图, 全国招聘职位top10地区
"""
datas_copy = datas
datas_copy['city'] = datas_copy['city'].apply(lambda x :x.split('-')[0])
datas_copy = datas_copy[~datas_copy['city'].str.contains('异地招聘')] # 去除含有'异地招聘'的地区所在行
grouped_city = datas_copy.groupby(datas['city'])
grouped_city_count = grouped_city['city'].agg(['count']) # 对城市数量进行统计
grouped_city_count.reset_index(inplace = True) # 若不进行此操作,将会报错
grouped_city_count['city'][grouped_city_count['city'] == '澄迈'] = '澄迈县' # pyecharts中'澄迈'为'澄迈县'
city_data = [(grouped_city_count['city'][i], grouped_city_count['count'][i]) for i in range(grouped_city_count.shape[0])]
# 全国工作分布热力图
style = Style(title_color = '#fff', title_pos = 'center', width = 1200, height = 600, background_color = '#404a59')
geo = Geo('Python职位招聘地理位置', **style.init_style)
attr, value = geo.cast(city_data)
geo.add('', attr, value, type = 'heatmap', visual_range = [0, 20], visual_text_color = '#fff',
symbol_size = 20, is_visualmap = True, is_roam = False, is_piecewise = True, visual_splite_number = 4)
geo.render('Python岗位全国热力图.html')
# 全国招聘职位top10地区
bar = Bar('Python岗位全国分布柱状图')
city_top10 = sorted(city_data, key = lambda x: x[1], reverse = True)[0:10]
attr, value = bar.cast(city_top10)
bar.add('', attr, value, is_splitline_show = False, is_label_show = True,
xaxis_rotate = 30)
bar.render('Python岗位分布柱状图.html')
需指出的是在抓到的数据中的城市是精确到各个区的,在这里我们只需使用城市名,通过使用apply
和lambda
结合可以快速的对city列的数据进行修改,同时在抓取的数据里面会有’异地招聘’的信息,在城市数量统计里面我们需要将其过滤掉,pandas
中使用~
即为取反操作。
有些时候会看到有些数据显示为NaN,这个时候就需要我们对其进行处理,一般都是将其过滤掉,我们可先观察一下是否含有NaN值,以上述代码为例,可执行一下操作:
print(datas_copy.index[datas_copy['location'].isna()])
上述代码获取某列有NaN值的索引,如果不加datas_copy.index[]的话返回的是每个元素的布尔值,若想直接去掉NaN,使用~
即可
datas_copy = datas_copy[~datas_copy['location'].isna()]
上述代码绘制的图如下:
通过热力图发现,对于Python岗位的需求主要集中在江浙沪一带(亲,包邮哦),还有广东等地方,在柱状图中发现在收集到的1195条数据中,上海的招聘量已经达到235了(魔都就是厉害),而深圳只有162(看来深圳还是看重老牌的语言呀)。所以,会python的你想去多尝试点与python相关的工作可以去大上海看看,总有一种适合你。
你可能会问,这些岗位对学位有要求吗,我们需要提高一下学历嘛(学历这个东西不管哪行哪业,都是高点吃香呀)?同样,Python也可以告诉我们。我们只需要对刚刚得到的1195条数据中的学历要求进行统计即可。
代码如下:
def education_require():
"""
学历要求饼图
"""
datas_copy = datas
datas_copy = datas_copy[~datas_copy['education'].str.contains('人')] # 去掉'招多少人'的值
grouped_eductaion = datas_copy.groupby(datas_copy['education'])
grouped_eductaion_count = grouped_eductaion['education'].agg(['count'])
grouped_eductaion_count.reset_index(inplace = True)
attr = ['中专', '大专', '本科', '硕士', '高中']
value = [(grouped_eductaion_count['count'][i]) for i in range(len(grouped_eductaion_count))]
pie = Pie('学历要求饼图', title_pos = 'center')
pie.add('', attr, value, valuecenter = [60, 60], is_random = True, radius = [20, 65], rosetype = 'area',
is_legend_show = True, legend_orient = 'vertical', legend_pos = 'left', is_label_show = True)
pie.render('学历要求饼图.html')
结果为:
通过上图可以发现,Python岗位对于学历的要求不是很高的,只要你高中毕业了,还是能找到一份好工作的(程序员这种东西,看的还是实力和经验嘛)。不过还在学习的同学可要继续好好学习哟。
对于Python工作岗位,要求会Python是毋庸置疑的,你要是连Python都不会的话,应该也就不会找和Python相关的工作了吧。当然,岗位招聘的时候,不可能只需要你会Python,只是Python是主要的而已,大多数公司还会要求你具有一些其他的专业技能,比如操作系统、数据库等。那接下来就让我们看看,Python岗位还需要会哪些专业的语言技能吧,这里我们仅选出Top10来分析,毕竟一辈子就那么长,不可能把所有要求的都学一遍的(人生苦短,我用python
)。
代码如下:
def job_requirement():
datas_copy = datas
text = ''.join(datas_copy['job_info'])
requirements = [word for word in jieba.cut_for_search(text)]
requirements = stopwords(requirements) # 设置停用词
requirements_top = re.findall(r'[a-zA-Z]+', str(requirements).lower())
requirements_top = Counter(requirements_top)
requirements_top.pop('and') # 删除'and'
requirements_top10 = requirements_top.most_common(10) # 统计最多的10个
bar = Bar('Python技能要求')
attr, value = bar.cast(requirements_top10)
bar.add('', attr, value, is_splitline_show = False, is_label_show = True, xaxis_rotate = 30)
bar.render('Python技能要求.html')
由于每个专业技能都是包含在职位要求里的,所以需要通过jieba
来进行分词,并设置停用词,继而匹配出我们所需要的专业技能词汇。在对各个专业技能进行统计时,我们使用了collections
中的Counter
,它返回一个以元素为 key 、元素个数为 value 的Counter对象集合,同时使用most_common()
方法可以提取出我们想要的前多少个键值对。
除python外,大多数公司都需要会操作Linux系统(Linux还是大佬呀,看来博主得去学学了),毕竟Linux可比Win强多了。对于数据库的要求,mysql排在第一位,其次时redis,mongodb。对于准备从事网络编程的同学的话,django看来是不可避免的了。对于想从事Python开发的你,还在等什么,赶快多学几个技能吧。
当然,各大公司除了需要你掌握专业性技能,还对你有些其他的要求。这里为了更好的体现其他的工作要求,我们选用词云并根据词频来表示。
从词云中可以看出,大多数的公司还是需要你熟悉某些东西(就是上面的专业技能),能够开发一些东西,会对数据进行一些处理,当然了,有一点,在各行各业都不能少的,那就是经验!!!所以还是趁着年轻,多攒点经验吧。
对于Python岗位的选择,大家最看重的还是薪资水平了,你说要是一个公司给你2k一个月,你会去干吗?所以,对于Python岗位的薪资分析很有必要。由于抓到的数据大部分都是’A-B千/月’等,不利于分析,所以在这里我们将数字给提取出来,对于每个岗位都取A与B的中值来进行分析,并计算每个城市的平均薪资。
代码如下:
def deal_salary(salary_data):
"""
对工资数据进行修改
:param salary_data: 工资数据
:return: 月工资中值
"""
if re.match('.*万/月', salary_data):
return float((float(re.findall(r'(.*?)万/月', salary_data)[0].split('-')[0]) * 10000 +
float(re.findall(r'(.*?)万/月', salary_data)[0].split('-')[1]) * 10000) / 2)
if re.match('.*万/年', salary_data):
return float((float(re.findall(r'(.*?)万/年', salary_data)[0].split('-')[0]) * 10000 / 12 +
float(re.findall(r'(.*?)万/年', salary_data)[0].split('-')[1]) * 10000 / 12) / 2)
if re.match('.*千/月', salary_data):
return float((float(re.findall(r'(.*?)千/月', salary_data)[0].split('-')[0]) * 1000 +
float(re.findall(r'(.*?)千/月', salary_data)[0].split('-')[1]) * 1000) / 2)
if re.match('.*元/天', salary_data):
return float(float(re.findall(r'(.*?)元/天', salary_data)[0]) * 20)
if re.match('.*千以下/月', salary_data):
return float(float(re.findall(r'(.*?)千以下/月', salary_data)[0]) * 1000)
def city_salary():
"""
每个城市的平均工资折线图
"""
datas_copy = datas
datas_copy['city'] = datas_copy['city'].apply(lambda x :x.split('-')[0])
datas_copy = datas_copy[~datas_copy['city'].str.contains('异地招聘')] # 去除含有'异地招聘'的地区所在行
datas_copy = datas_copy[~datas_copy['salary'].isna()] # 去掉含有nan的数据行
datas_copy['salary'] = datas_copy['salary'].apply(lambda x :deal_salary(x)) # 对数据中的'salary'列中的数据进行处理
datas_copy = datas_copy[~datas_copy['salary'].isna()] # 去掉含有nan的数据行
grouped_city_salary = datas_copy['salary'].groupby(datas_copy['city'])
salary_month = grouped_city_salary.agg(['mean'])
salary_month.reset_index(inplace = True)
# 方式1
bar = Bar('各城市平均工资', width = 1380, height = 300, title_pos = 'center')
bar.add('', salary_month['city'], salary_month['mean'], is_label_show = False,
mark_point = ['max', 'min'], mark_line = ['average'], xaxis_rotate = 45)
bar.render('各城市平均工资.html')
# 方式2(滑动效果)
# bar = Bar('各城市平均工资')
# bar.add('', salary_month['city'], salary_month['mean'], is_datazoom_show = True, datazoom_type = 'both',
# datazoom_range = [10, 25], mark_point = ['max', 'min'], mark_line = ['average'], xaxis_rotate = 30)
# bar.render('各城市平均工资.html')
在这里我们使用了两种方式来绘图,第一种是静态的,就是所有数据都全部显示出来;第二种是动态的,就是我们可以通过滑动坐标轴下方的区域来选择显示的结果。两种方式显示的结果如下:
(请原谅我不会截动图,效果在这里展示不出来)
需注意的是,在第二个图里的平均值,是所显示出来的地区的平均值,并不是所有崎岖的。从上图中,我们可以知道Python岗位薪资占据前三位的是上海、北京、深圳,北京居然平均薪资都将近1.7w/月(估计得有1w才能来养活自己),看来以后想挣大钱,还是得往大城市跑呀(博主看到自家南通都能达到1.2w,欣慰呀)。全国的平均薪资达到了9k,看来程序员的班没有白加呀,在努点力,就可以月入过万了。所以,从事Python行业,工资这方面看来是不用担心的,应该都可以1w+。
最后,博主为了玩一下pyecharts
中的极坐标系图,大概看了一下工资最高的top20的岗位(都是月入3w+的)的名称,也没有对数据进行清理等操作,就直接拿过来用了,得到了下面这张图
看了图里的岗位名称,才知道,人家工资高是有理由的呀,这岗位都是工程师级别的呀,还带着’高级’两个字,还不是一般的工程师,看来没个8年多的经验是混不到这种水平啦。
分析完Python岗位数据,本人发现还有许多要求未能达到,看来得加把劲了。
你,准备好用Python了吗?
好好学习,天天向上。