8月份已经正式离职,这两个月主要在做新书校对工作。9月份陆续投了几份简历,参加了两次半面试,第一次是家做办公自动化的公司,开的薪水和招聘信息严重不符,感觉实在是在浪费时间,你说你给不了那么多为什还往上发布?第二次是家做业务系统的中型公司,结果面试我的技术总监直接被我按在地上摩擦,估计没戏了。还有半次,是个研究所,电话和微信简单沟通了一下,结果感觉自己有点被摩擦的意思,不愧挂着研究俩字。后两家公司的薪水区间几乎相同,但人员的技术水平却相差很大,这让我有些好奇,忍不住想分析一下苏州类似岗位的薪资水平。

我在51job上搜索了一下苏州,计算机软件,互联网/电子商务,计算机服务,近一月的java相关职位,一共33页。

第1页的URL是:

https://search.51job.com/list/070300,000000,0000,00,3,99,java,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=

第2页是:

https://search.51job.com/list/070300,000000,0000,00,3,99,java,2,2.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=

不同之处在 java2,1.html 和 java2,2.html 看来那个不同的数字就是翻页信息。

在浏览器F12一下,页面dom布局大概是这样:

于是爬取了一下全部数据:

 from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
import csv
from itertools import chain
import threading def get_jobs(url):
'''
根据url爬取职位信息
:param url:
:return: 职位列表,每个元素是一个四元组(职位名, 薪资,发布时间,详情页面url)
'''
try:
html = urlopen(url)
except HTTPError as e:
print('Page was not found')
return [] jobs = []
try:
bsObj = BeautifulSoup(html.read())
jobs_div = bsObj.find('div', {'id': 'resultList'}).findAll('div', {'class':'el'})
for div in jobs_div[1:]:
span_list = div.findAll('span')
job_name = span_list[0].a.get_text().strip() # 职位名称
job_url = span_list[0].a.attrs['href'].strip() # 职位详情url
job_comp = span_list[1].a.get_text().strip() #公司名称
job_salary = span_list[3].get_text().strip() # 薪资
job_date = span_list[4].get_text().strip() # 日期
jobs.append((job_comp, job_name, job_salary, job_date, job_url))
except AttributeError as e:
print(e)
return []
return jobs def crawl():
'''
分页苏州市近一月内的java相关职位
:return: 职位列表,每个元素是一个四元组(职位名, 薪资,发布时间,详情页面url)
'''
# 查询条件:java;苏州;计算机软件、计算机服务(系统、数据服务、维修)、互联网/电子商务;近一月
url = 'https://search.51job.com/list/070300,000000,0000,01%252C38%252C32,9,99,java,2,{0}.html?' \
'lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99' \
'&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1' \
'&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='
all_jobs = [] def _crawl(page_start, page_end):
'''
分页爬取数据
:param page_start: 起始页
:param page_end: 终止页
:return:
'''
print('crawl {0}~{1} start...'.format(page_start, page_end))
for i in range(page_start, page_end):
# 翻页url
page_url = url.format(str(i))
jobs = get_jobs(page_url)
if len(jobs) == 0:
break
all_jobs.append(jobs)
print('crawl {0}~{1} over'.format(page_start, page_end)) # 线程列表
thread_list = []
start_nums = list(range(0, 45, 5))
end_nums = list(range(5, 50, 5))
# 每5页一个线程, 最多50页
for i in range(len(start_nums)):
t = threading.Thread(target=_crawl, args=(start_nums[i], end_nums[i]))
thread_list.append(t) print('开始爬取数据...')
for t in thread_list:
t.start()
for t in thread_list:
t.join()
print('爬取结束') return all_jobs def save_data(all_jobs):
'''
将职位信息保存到joblist.csv
:param all_jobs: 二维列表,每个元素是一页的职位信息
'''
print('正在保存数据...')
with open('joblist.csv', 'w', encoding='utf-8', newline='') as fp:
w = csv.writer(fp)
# 将二维列表转换成一维
t = list(chain(*all_jobs))
w.writerows(t)
print('保存结束,共{}条数据'.format(len(t))) if __name__ == '__main__':
# 爬取数据
all_jobs = crawl()
# 保存数据
save_data(all_jobs)

为了爬的快点,开了多个线程,最后把数据保存在joblist.csv中。近一个月共有967个java相关职位:

打开csv,发现里面的数据并不太好:

……

江苏未至科技股份有限公司,实施工程师(苏州),4-8千/月,09-25,https://jobs.51job.com/suzhou-gxq/105762963.html?s=01&t=0
江苏未至科技股份有限公司,交付工程师,6-7千/月,09-25,https://jobs.51job.com/suzhou-gxq/104253078.html?s=01&t=0
易程创新科技有限公司苏州分公司,高级软件工程师,1-2万/月,09-25,https://jobs.51job.com/suzhou-gxq/100292396.html?s=01&t=0
江苏未至科技股份有限公司,项目经理,0.8-1.6万/月,09-25,https://jobs.51job.com/suzhou-gxq/85646230.html?s=01&t=0
达内时代教育集团,咨询顾问底薪4-7K+五险一金,1-1.5万/月,09-25,https://jobs.51job.com/suzhou-gsq/113505714.html?s=01&t=0
达内时代教育集团,搜索顾问底薪4-7K-上市企业,1-1.5万/月,09-25,https://jobs.51job.com/suzhou-gsq/113505583.html?s=01&t=0
苏州工业园区测绘地理信息有限公司...,Web前端开发工程师,6-15万/年,09-25,https://jobs.51job.com/suzhou-gyyq/86942466.html?s=01&t=0
江苏云坤信息科技有限公司,项目经理,1-1.8万/月,09-25,https://jobs.51job.com/suzhou-gyyq/112994728.html?s=01&t=0
江苏云坤信息科技有限公司,前端开发工程师,0.8-1.5万/月,09-25,https://jobs.51job.com/suzhou-gyyq/70761080.html?s=01&t=0
苏州智享云信息科技有限公司,系统架构师,1.6-2.5万/月,09-25,https://jobs.51job.com/suzhou/108411172.html?s=01&t=0
英诺赛科(苏州)半导体有限公司,MES 工程师,0.6-1万/月,09-25,https://jobs.51job.com/suzhou-wjq/115142646.html?s=01&t=0
苏州麦芒软件科技有限公司,软件测试助理工程师,4-6千/月,09-25,https://jobs.51job.com/suzhou/115511688.html?s=01&t=0
三门峡崤云信息服务股份有限公司,大数据挖掘工程师,0.8-2万/月,09-25,https://jobs.51job.com/sanmenxia/110394655.html?s=01&t=0
苏州春慷咨询管理有限公司,软件实施工程师,0.3-1万/月,09-25,https://jobs.51job.com/suzhou-gsq/115617730.html?s=01&t=0
苏州佑捷科技有限公司,高级开发工程师,1.5-2万/月,09-25,https://jobs.51job.com/suzhou-gxq/114727492.html?s=01&t=0
苏州佑捷科技有限公司,Android开发工程师,1-2.5万/月,09-25,https://jobs.51job.com/suzhou-gxq/114726758.html?s=01&t=0
瑞泰信息技术有限公司,.NET开发工程师(实习生),6.5-8.5千/月,09-25,https://jobs.51job.com/suzhou/108055469.html?s=01&t=0
北京直真科技股份有限公司,前端开发工程师(苏州),1.1-1.7万/月,09-25,https://jobs.51job.com/suzhou/113018219.html?s=01&t=0

……

职位包含测试、项目经理、售前、Android,还有一部分.net也混进来了,所以分析前需要过滤掉这些数据。'测试', '.Net', '运维', '嵌入式','前端',这些职位都不要,’总监', '主管', '技术', '研发', '开发', '经理', 'java', 'JAVA', 'Java', '工程师’ ,这些需要保留。

薪资的单位也不统一,有万/年,万/月,千/月, 统一转换成万/年,没写薪资的也不要。

 import csv
from decimal import Decimal
import pandas as pd
import numpy as np def load_datas():
'''
从joblist.csv中装载数据
:return: 数据集 datas
'''
datas = []
with open('joblist.csv', encoding='utf-8') as fp:
r = csv.reader(fp)
for row in r:
datas.append(row)
return datas def clear(datas):
'''
数据清洗
规则:
1.没有标明薪资的,直接去掉;
2.万/月和千/月转换成万/年
:param datas: 原始数据
:return: 清洗后的数据
'''
result = []
for d in datas:
# 清洗后的数据
new_d = []
new_d.append(d[0]) # 公司
job = filter_job(d[1]) # 公司
# 去掉公司不符合的数据
if job == '':
continue
new_d.append(d[1]) # 职位
salary_start, salary_end = salary_trans(d[2])
# 去掉没写薪资的数据
if salary_start == '':
continue
else:
new_d.append(salary_start)
new_d.append(salary_end)
new_d.append(d[3]) # 发布日期
new_d.append(d[4]) # 详细页面URL
result.append(new_d)
return result def filter_job(job):
'''
过滤职位名称
:param job: 职位
:return: 如果被过滤掉,返回''
'''
# 黑名单
black = ['测试', '.Net', '运维', '嵌入式', '前端']
# job在黑名单中
if [job.find(x, 0, len(job)) for x in black].count(-1) < len(black):
return ''
# job在白名单
white = ['总监', '主管', '技术', '研发', '开发', '经理', 'java', 'JAVA', 'Java', '工程师']
if [job.find(x, 0, len(job)) for x in white].count(-1) > 0:
return job
return '' def salary_trans(salary):
'''
对薪资进行转换
:param salary: 薪资
:return: 二元组(起始年薪(万/年), 终止年薪(万/年))
'''
start, end = '', '' # 起始年薪, 终止年薪
# 将所有薪资单位转换成 万/年
if salary.endswith('万/年'):
s = salary.replace('万/年', '').split('-')
start, end = s[0], s[1]
elif salary.endswith('万/月'):
s = salary.replace('万/月', '').split('-')
start = (Decimal(s[0]) * 12).normalize()
end = (Decimal(s[1]) * 12).normalize()
elif salary.endswith('千/月'):
s = salary.replace('千/月', '').split('-')
start = (Decimal(s[0]) * 12 / 10).normalize()
end = (Decimal(s[1]) * 12 / 10).normalize()
return str(start), str(end) if __name__ == '__main__':
# 读取并清洗数据
datas = np.array(clear(load_datas()))
print(len(datas)) 

还剩789条。

分析开始。招聘信息上绝大多数都是以起薪资为准, 最高薪资就是做个样子,因此只分析起薪。先快速统计一下:

 def analysis(datas):
''' 数据分析 '''
df = pd.DataFrame({'comp_name': datas[:, 0],
'job_name': datas[:, 1],
'salary_start': datas[:, 2],
'salary_end': datas[:, 3],
'publish_date': datas[:, 4],
'url': datas[:, 5]})
# 全部起始薪资
salary_col = df['salary_start']
print('按起始薪资快速统计'.center(60, '-'))

一共有789条记录,其中最多的是年薪12W,共出现了162次,大多数职位也就1W的月薪。

再看起薪出现次数最多的top10。

 def analysis(datas):
''' 数据分析 '''
df = pd.DataFrame({'comp_name': datas[:, 0],
'job_name': datas[:, 1],
'salary_start': datas[:, 2],
'salary_end': datas[:, 3],
'publish_date': datas[:, 4],
'url': datas[:, 5]})
# 全部起始薪资
salary_col = df['salary_start']
print('按起始薪资快速统计'.center(60, '-'))
# 按起始薪资快速统计
print(salary_col.describe())
# 起薪出现次数最多的top10
salary_count_top_n(salary_col, 10) def salary_count_top_n(salary_col, n):
''' 起薪出现次数最多的top n '''
print(('起薪出现次数最多的top' + str(n)).center(60, '-'))
print('起薪\t数量')
# 起薪出现次数最多的top n
count_top_n = salary_col.value_counts(sort=True, ascending=False).head(n)
print(count_top_n)
count_top_n.plot(kind='bar')
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.xlabel('薪资(万/年)')
plt.ylabel('职位数量')
plt.show()

真是一看吓一跳,月薪1W以下大概有500条,约占总职位数量的65%。

再看起薪最高的top 10:

     ''' 数据分析 '''
df = pd.DataFrame({'comp_name': datas[:, 0],
'job_name': datas[:, 1],
'salary_start': datas[:, 2],
'salary_end': datas[:, 3],
'publish_date': datas[:, 4],
'url': datas[:, 5]})
……
# 起薪最高的top10
urls = salary_high_top_n(df, 10) def salary_high_top_n(df, n):
''' 起薪最高的top n '''
print(('起薪最高的top' + str(n)).center(60, '-'))
salary_grp = df.groupby('salary_start')
# 按起薪分组
salary_top_n = sorted(salary_grp, reverse=True, key=lambda x: float(x[0]))[0:n]
print('%-16s%-20s' % ('起薪', '数量'))
# 职位对应的url
urls = []
for salary, group in salary_top_n:
print('%-20s%-20d' % (salary, len(group)))
urls += group.url.values.tolist()
return urls

top 10中共64个职位,年薪24W的占了26个,约占top10的40%,24W以下的占了65%以上。月薪2W居然都是高薪了,还有前途吗?


  作者:我是8位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

  扫描二维码关注公作者众号“我是8位的”

苏州市java岗位的薪资状况(1)的更多相关文章

  1. 苏州市java岗位的薪资状况(2)

    上一篇已经统计出了起薪最高的top 10: 接着玩,把top 10 中所有职位的详细信息爬取下来.某一职位的详情是这样: 我们需要把工作经验.学历.职能.关键字爬取下来. from urllib.re ...

  2. 2014广州Java岗位面试汇总

    本文记录了最近一些朋友提供的面试经历,真实数据,仅供广州求职的朋友参考.为行文方便,一律用主语”我“进行.部分词语可能造成读者不良反应,敬请留意. 1  广州沣首信息科技有限公司 公司所在区域相对较偏 ...

  3. Python基础学习笔记(一)python发展史与优缺点,岗位与薪资

    相信有好多朋友们都是第一次了解python吧,可能大家也听过或接触过这个编程语言.那么到底什么是python呢?它在什么机缘巧合下诞生的呢?又为什么在短短十几年时间内就流行开来呢?就请大家带着疑问,让 ...

  4. Java岗位面试题分享:jvm+分布式+消息队列+协议(已拿offer)

    个人近期面试情况 今年二月以来,我的面试除了一个用友的,基本其他都被毙了,可以说是非常残酷的.其中有很多自己觉得还面的不错的岗位,比如百度.跟谁学.好未来等公司.说实话,打击比较大. 情况基本上是从三 ...

  5. 野村证券伦敦分部面试 - Java岗位

    第一轮 1. 笔试 30 mins 一共六道大题,前两题有4-5个小题. 第一道大题主要是考察Java Collections: a. LinkedList和ArrayList的区别 b. Set和L ...

  6. 掌握Python可以去哪些岗位?薪资如何?

    一.人工智能 Python作为人工智能的黄金语言,选择人工智能作为就业方向是理所当然的,就业前景也还不错.人工智能工程师的招聘起薪一般在20K-35K,如果是初级工程师,起薪一般12K. 二.大数据 ...

  7. 使用java检测网络连接状况

    windows中可以通过在cmd中使用ping命令来检测网络连接状况,如下: 网络连接正常时: 网络未连接时: 在java中可以通过调用ping命令来判断网络是否连接正常: package modul ...

  8. 成都传智播客java就业班(14.04.01班)就业快报(Java程序猿薪资一目了然)

    这是成都传智播客Java就业班的就业情况,很多其它详情请见成都传智播客官网:http://cd.itcast.cn?140812ls 姓名 入职公司 入职薪资(¥) 方同学 安**软件成都有限公司(J ...

  9. 9大行为导致Java程序员薪资过低, 你有几个?

    Java程序员薪水有高有低,有的人一个月可能拿30K.50K,有的人可能只有2K.3K.同样有五年工作经验的Java程序员,可能一个人每月拿20K,一个拿5K.是什么因素导致了这种差异?本文整理导致J ...

随机推荐

  1. Docker中进入容器命令行及后台运行

    Docker中我们一般会有两种执行命令的方式,一种是直接进入容器的命令行,在终端执行并查看结果,一种是在后台执行,并不会在终端查看结果. 1.进入容器命令行 su root docker run -i ...

  2. Memcached 基本语法 记录

    set 命令:命令将value数值存储在指定的key中: set key flags exptime bytes [noreply] value key:键值 key-value 结构中的 key,用 ...

  3. Linux-3.14.12内存管理笔记【构建内存管理框架(4)】

    虽说前文分析内存管理框架构建的实现,提到了find_zone_movable_pfns_for_nodes(),但这里不准备复述什么,仅针对required_movablecore和required_ ...

  4. [PHP] stream_set_blocking非阻塞模式影响fgets fread函数

    当设置socket为非阻塞时,fread或者fgets函数会立即返回结果,而不需要等待有输入,测试过程可以使用vscode的debug模式来进行当不设置这一项时,如果客户端没有输入会一直阻塞在这里等待 ...

  5. bash的基本特性

    1.命令历史 作用:查看之前使用的命令 关于命令历史的文件 每个用户家目录下面的.bash_history 在关机的时候,会自动写入一次(history -r 将内存中的命令历史写入文件) 关于命令历 ...

  6. Golang防止多个进程重复执行

    创建锁文件 lockFile := "./lock.pid" lock, err := os.Create(lockFile) if err != nil { log.Fatal( ...

  7. angular路由事件

    Angular 4检测路由变化,可以使用router.events来监听: 支持的事件类型: NavigationStart:导航开始 NavigationEnd:导航结束 NavigationCan ...

  8. nacos+springboot的多环境使用方法

    这里通过namespace的方法来实现,其他的没成功. 添加依赖 <dependency> <groupId>com.alibaba.boot</groupId> ...

  9. django--调用百度AI接口实现人脸注册登录

    面部识别----考勤打卡.注册登录.面部支付等等...感觉很高大上,又很方便,下面用python中的框架--django完成一个注册登录的功能,调用百度AI的接口,面部识别在网上也有好多教程,可以自己 ...

  10. linux 主机通过虚拟机(win10)上网

    公司内网必须安装安全软件(exe)才可以上网,但是我的系统是deepin,用deepin-wine无法安软该exe,于是用vmware安装了win10虚拟机,通过虚拟机上网 先简单介绍下vmware以 ...