爬取51job信管专业相关岗位的情况进行可视化分析。
采用工具:python、tableau(可视化)、DBeaver(数据库管理软件)
数据爬取过程
考虑到requests库进行数据的请求容易被平台反扒发现,从而封锁ip导致数据不能正常爬取。因此采用selenium模拟浏览器,进行数据的采集。总共需要采集的岗位有30终类型,先通过selenium采集每种类型岗位需要采集的总页数。然后利用每种岗位的总页数信息,进行岗位数据的采集。通过模拟浏览器访问51job网页,获取页面的HTML文本,然后采用BeautifulSoup库进行需要采集数据点的的数据的获取,最终将获取到的数据写入数据库中进行存储。
import requests from bs4 import BeautifulSoup import pymysql import random from selenium import webdriver from selenium.webdriver import ChromeOptions import re import time import requests
if __name__ == '__main__': #主函数 job=["产品经理","产品助理","交互设计","前端开发","软件设计","IOS开发","业务分析","安卓开发","PHP开发","业务咨询","需求分析","流程设计" ,"售后经理","售前经理","技术支持","ERP实施","实施工程师","IT项目经理","IT项目助理","信息咨询","数据挖掘","数据运营","数据分析","网络营销", "物流与供应链","渠道管理","电商运营","客户关系管理","新媒体运营","产品运营"] #总共30个职位的列表 #https://www.pexels.com/ option = ChromeOptions() UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36 Edg/94.0.992.31" option.add_argument(f'user-agent={UA}') option.add_experimental_option('useAutomationExtension', False) option.add_experimental_option('excludeSwitches', ['enable-automation']) web = webdriver.Chrome(chrome_options=option) # chrome_options=chrome_opt,,options=option web.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """ }) web.implicitly_wait(3) url='https://search.51job.com/list/000000,000000,0000,00,9,99,%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86,2,2.html?' web.get(url) time.sleep(6) page_list=[] for j in job: for i in range(1, 1 + 1): #url = "https://search.51job.com/list/000000,000000,0000,00,9,99," + j + ",2," + str(i) + ".html?" url="https://search.51job.com/list/000000,000000,0000,00,9,99,{},2,{}.html?".format(j, i) web.get(url) html = web.page_source soup = BeautifulSoup(html, "lxml") text = soup.find_all("script", type="text/javascript")[3].string # 观察原始代码发现我们需要的数据在 engine_jds 后 page_te=eval(str(text).split("=", 1)[1])["total_page"] page_list.append(page_te) print(page_te)
#得到的page_te列表将用于之后的数据爬取时对应每个职位的爬取页数。
#定义 spider()函数,用于获取每个 url 的 html def spider(url): headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36 Edg/94.0.992.31"} try: rep = requests.get(url, headers=headers) rep.raise_for_status() rep.encoding = rep.apparent_encoding txt = rep.text return txt except: print("解析失败")
#定义 jiexi()函数,用于解析得到的 html def jiexi(html, info,name): soup = BeautifulSoup(html, "lxml") text = soup.find_all("script", type="text/javascript")[3].string #观察原始代码发现我们需要的数据在 engine_jds 后 data = eval(str(text).split("=", 1)[1])["engine_jds"] for d in data: try: job_name = d["job_name"].replace("\\", "") # 岗位名称 except: job_name = " " try: company_name = d["company_name"].replace("\\", "") # 公司名称 except: company_name = " " try: providesalary_text = d["providesalary_text"].replace("\\", "") # 薪资 except: providesalary_text = " " try: workarea_text = d["workarea_text"].replace("\\", "") #工作地点 except: workarea_text = " " try: updatedate = d["updatedate"].replace("\\", "") #更新时间 except: updatedate = " " try: jobwelf = d["jobwelf"].replace("\\", "") # 工作待遇 except: jobwelf = " " try: companyind_text = d["companyind_text"].replace("\\", "") # 公司类型 except: companyind_text = " " try: companysize_text = d["companysize_text"].replace("\\", "") # 公司规模 except: companysize_text = " " try: at = d["attribute_text"] # 工作要求 s = '' for i in range(0, len(at)): s = s + at[i] + ',' attribute_text = s[:-1] except: attribute_text = " "
#将每一条岗位数据爬取下的内容以及传入参数 name 作为一个列表,依此加入到 info 列表中 info.append( [ name,job_name, updatedate, company_name, companyind_text, companysize_text, workarea_text, providesalary_text, attribute_text, jobwelf])
#将数据存到 MySQL 中名为“51job”的数据库中 def save(info): #将数据保存到数据库表中对应的列 for data in info : present_job = data[0] # 当前爬取岗位 job_name = data[1] #岗位 updatedate = data[2] #更新时间 company_name = data[3] # 公司名称 companyind_text = data[4] #公司类型 companysize_text = data[5] #公司规模 workarea_text = data[6] #工作地点 providesalary_text = data[7] #薪资 attribute_text = data[8] #工作要求 jobwelf = data[9] #工作待遇 # 创建 sql 语句 sql = "insert into jobs(当前爬取岗位,岗位,更新时间,公司名称,公司类型,公司规模,工作地点,薪资,工作要求,工作待遇) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" # 执行 sql 语句 cursor.execute(sql, [present_job, job_name, updatedate, company_name, companyind_text, companysize_text, workarea_text, providesalary_text, attribute_text, jobwelf]) db.commit() # 提交数据
if __name__ == '__main__': #主函数 job=["产品经理","产品助理","交互设计","前端开发","软件设计","IOS开发","业务分析","安卓开发","PHP开发","业务咨询","需求分析","流程设计" ,"售后经理","售前经理","技术支持","ERP实施","实施工程师","IT项目经理","IT项目助理","信息咨询","数据挖掘","数据运营","数据分析","网络营销", "物流与供应链","渠道管理","电商运营","客户关系管理","新媒体运营","产品运营"] #利用1.2获得的每个岗位对应的总页码数。 page_list=['1141', '62', '169', '619', '356', '61', '229', '64', '56', '356', '1379', '147', '62', '29', '2000', '173', '184', '10', '2', '396', '221', '115', '2000', '381', '5', '295', '1233', '280', '699', '352'] #https://www.pexels.com/ option = ChromeOptions() UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36 Edg/94.0.992.31" option.add_argument(f'user-agent={UA}') option.add_experimental_option('useAutomationExtension', False) option.add_experimental_option('excludeSwitches', ['enable-automation']) web = webdriver.Chrome(chrome_options=option) # chrome_options=chrome_opt,,options=option web.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """ }) web.implicitly_wait(10) url='https://search.51job.com/list/000000,000000,0000,00,9,99,%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86,2,2.html?' web.get(url) time.sleep(6) le=len(job) #连接数据库 db = pymysql.connect( # 连接数据库host="127.0.0.1", #MySQL 服务器名 user="root", # 用户名 password="12345678", # 密码 database="python上机", # 操作的数据库名称charset="utf8" ) cursor = db.cursor() for j in range(23,le): for i in range(1,int(page_list[j])):#页面 info = [] # url = "https://search.51job.com/list/000000,000000,0000,00,9,99," + j + ",2," + str(i) + ".html?" url = "https://search.51job.com/list/000000,000000,0000,00,9,99,{},2,{}.html?".format(job[j], i) web.get(url) ht = web.page_source soup = BeautifulSoup(ht, "lxml") jiexi(ht, info,job[j]) print('岗位{}:{}/{}'.format(j,i,page_list[j])) time.sleep(2) save(info) time.sleep(3) cursor.close() # 关闭连接 db.close()
#引入 pymysql 包 import pymysql #连接 MySQL 数据库 db = pymysql.connect( host="127.0.0.1", user="root", password="12345678", database="python上机", charset="utf8" )
def pipei(): cursor = db.cursor() # 获取操作游标 cursor.execute("select * from jobs") # 从 jobs 表中查询所有内容并保存 results = cursor.fetchall() # 接受全部的返回结果 after_pipei = [] # 建立一个空列表,用来存储匹配后数据 for each_result in results: if each_result[-1] == '物流与供应链': if '物流' in each_result[0] or '供应链' in each_result[0]: after_pipei.append(each_result) elif each_result[-1] == '新媒体运营' or each_result[-1] == '电商运营': if '运营' in each_result[0]: after_pipei.append(each_result) # 由于在以关键词“电商运营”或“新媒体运营”搜索的岗位信息中包含大量具体电商或新媒体平台名称的岗位名称,如“拼多多运营”“抖音运营”等,因此在这两类岗位名称匹配时我们认为只要岗位名称中包含“运营”就算匹配成功。 elif each_result[-1] == '客户关系管理': if'客户关系' in each_result[0]: after_pipei.append(each_result) elif each_result[-1] == '安卓开发': if '安卓' in each_result[0] or 'Android' in each_result[0]: after_pipei.append(each_result) # 由于在很多公司的招聘岗位中“安卓”会以“Android”英文形式出现,因此,在以“安卓开发”为关键词进行搜索时,我们认为只要包含“安卓”或“Android”开发就算匹配成功。 elif each_result[-1][:-2] in each_result[0] and each_result[-1][-2:]: after_pipei.append(each_result) # 剩余岗位需要两个关键词都存在岗位名称中,例如包含“数据”或“分析”在以“数据分析” 为关键词搜索的岗位名称种,我们就认为匹配成功。 cursor.close() # 关闭游标 return after_pipei # 返回匹配后的列
def split_city(data): after_split_city = [] #建立一个空列表,用来存储匹配后数据 for each_date in data: each_date_list = list(each_date) each_date_list[5] = each_date_list[5].split('-')[0] #将数据表中工作地点列以'-'进行切割,选取第一个元素替换 after_split_city.append(each_date_list) return after_split_city #返回筛除后的数据
def salary(data): after_salary=[] #建立一个空列表,用来存储匹配后数据 for each_data in data: each_data=list(each_data) if each_data[6] != '' and each_data[6][-1] != '时' and each_data[6][-3] != '下' and each_data[6][-4:-2] != '以下' and each_data[6][-3] != '上': # 筛除缺失值,以小时计费,给出的薪资表达为在“……以下”及“……以上”等难以计算数据的工作岗位 # 统一量纲(单位:千/月) if each_data[6][-1] == '年': each_data[6] = str(round((float(each_data[6].split('万')[0].split('-')[0]) + float(each_data[6].split('万')[0].split('-')[1])) * 5/12,1)) + '千 / 月' elif each_data[6][-1] == '天': each_data[6] = str(round((float(each_data[6].split('元')[0]) * 30/1000),1)) +'千 / 月' elif each_data[6][-3] == '万': each_data[6] = str(round((float(each_data[6].split('万')[0].split('-')[0]) + float(each_data[6].split('万')[0].split('-')[1])) * 5,1)) + '千/月' else: each_data[6] = str(round((float(each_data[6].split('千')[0].split('-')[0]) + float(each_data[6].split('千')[0].split('-')[1]) /2),1 )) + '千 / 月' after_salary.append(each_data) return after_salary
# 返回平均工资后的数据 def job_attribute_text(data): for each_data in data: if len(each_data[7].split(',')) == 3: if ' 经验' in each_data[7].split(',')[1] or ' 在校生' in each_data[7].split(',')[1]: each_data[7] = each_data[7].split(',')[1] + ',' # 以“,”切割后的列表长度为 3,若不包含“经验”元素,则保留“,学历”形式内容 else: each_data[7] = ',' + each_data[7].split(',')[1] # 以“,”切割后的列表长度为 4,选取中间两个元素,保留“经验,学历”形式内容 elif len(each_data[7].split(',')) == 4: each_data[7] = each_data[7].split(',')[1] + ',' + each_data[7].split(',')[2] else: each_data[7] = '' return data
#将清洗后的数据保存到数据库中 after_clean 表中,代码和保存爬取数据时类似 def save(data): cursor = db.cursor() for each_data in data: job_name = each_data[0] updatedate = each_data[1] company_name = each_data[2] companyind_text = each_data[3] companysize_text = each_data[4] workarea_text = each_data[5] providesalary_text = each_data[6] attribute_text = each_data[7] jobwelf = each_data[8] present_job = each_data[9] sql = "insert into after_clean(当前爬取岗位, 岗位,更新时间,公司名称 ,公司类型,公司规模,工作地点,薪资,工作要求,工作待遇) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" cursor.execute(sql, [present_job, job_name, updatedate,company_name, companyind_text, companysize_text, workarea_text, providesalary_text, attribute_text, jobwelf]) db.commit() cursor.close() db.close()
if __name__ == "__main__": data = pipei() data1 = split_city(data) data2 = salary(data1) data3 = job_attribute_text(data2) #将清洗后的数据存储到数据库的另一个表格中 save(data3)
清洗后的数据:薪资、工作要求、工作地点等进行了统一格式
采用工具:tableau+python
绘制界面:
图形:
分析:
对于职位的类型的占比可以看出,产品运营类岗位占据了最大的份额,说明信管专业的学生在这类岗位中需求最多,往后的求职过程中可关注此类岗位的职位。此外,技术管理类、技术开发类、技术支持类岗位的占比也在平均值以上,此类岗位也可以多加关注。
绘制界面:
绘制图形:
分析:
从学历要求的树状图可以看出,市场上的大部分与信管相关职位的需求是大专和本科均占据47%左右,对于硕士和博士等更高学历的要求分别占据0.0754066%、1.86667%,说明信管专业相关职业的学历门槛并不会太高。
绘制界面:
绘制图形:
分析:
各岗位类型公司规模数量特征条形图可以看出,对于产品运营类职位,公司的规模规模主要为500人以下的公司。数据运营类、技术管理类、数据管理类公司大厂10000人以上的大厂占比较高,如果你想去大厂,那么可以选择这类职位发展。
绘制界面:
绘制图形:
分析:
通过对信管所有相关岗位的热力图分析可知,信管专业的工作地点在:上海、北京、成都、杭州、武汉、南京、深圳、广州、重庆、等地较多,其中在上海、广州、深圳3地的最多。此外,郑州、西安、长沙、合肥等地的职位也相对较多,所以如果想避免竞争的激烈,可以考虑这些地方。
绘图界面:
绘制图形:
分析:
从学历的薪资水平箱线图可以看出:随着学历的上升,薪资的平均水平逐步提升,说明学历对于薪资是具有一定的影响的,学历越高,薪资的平均水平越高。此外,对于大专、本科、硕士类学历的专业,我们可以看到许多在箱线图以外的点,这些点表明这些职位的薪资大大超出平均水平,说明在某些职位,对于学历的要求并不是很重要,重要的是个人的其他方面的能力,比如说专业的机能等方面。为此,无论我们在那个阶段,要多加关注自己的职业技能,只要我们的学历和能力不是虚涨的,有实实在在的能力和产出做支撑,就不会有:大学生毕业了找不到工作这种焦虑。
绘制界面:
绘制图形:
分析:
通过职位薪资的桑基图可以看出:月薪,产品运营类职位的薪资大部分在10-15K,其余集中在5-10k;技术管理类与技术开发类,部分达到15-20k,其余集中在10-15K;技术支持类大约二分之一达到20-25k;对于25k以上的高薪职位主要集中在业务咨询、数据运营、数据管理、技术支持类职业。其中月薪在40k及以上,基本分布在业务咨询、数据运营类职位,所以有超高薪职位目标的同学,可以考虑往这类职业发展。
代码:
#设计词频统计函数 def wordcount(txt): #转化为列表 # 统计词频的字典 word_freq = dict() # 装载停用词,此处需将资料中给出的hit_stopwords.txt 文件放到本代码所在路径下 with open(r"D:\Users\yunmeng\PycharmProjects\小项目\大数据和上机二_数据可视化课程\相关文件\stopwords.txt", "r", encoding='utf-8') as f1: # 读取我们的待处理本文 txt1 = f1.readlines() stoplist = [] for line in txt1: stoplist.append(line.strip('\n')) # 切分、停用词过滤、统计词频 for w in list(jieba.cut(txt)): if len(w) > 1 and w not in stoplist: if w not in word_freq: word_freq[w] = 1 else: word_freq[w] = word_freq[w] + 1 return word_freq
#连接数据库 db = pymysql.connect( host="127.0.0.1", user="root", password="12345678", database="python上机", charset="utf8" ) cursor = db.cursor() cursor.execute("SELECT `工作待遇` FROM `after_clean`") results = cursor.fetchall() txt = '' for each_result in results: txt = txt + each_result[0] word_dict=wordcount(txt) da = pd.DataFrame({'word': word_dict.keys(), 'count': word_dict.values()}) #将词频统计的结果导出 da.to_csv(r'D:\Users\RK\PycharmProjects\小项目\大数据和上机二_数据可视化课程\代码文件\word_count.csv') #将导出的词频文件导入到tableau进行词云图的绘制
绘制图形:
分析:
通过岗位工作待遇热词词云图可以看到,工作待遇之中,企业最常提到的是:绩效奖金、年终奖、专业培训、餐饮、交通、弹性、体检、通勤、医疗保险、期权等。说明了,我们求职时,考虑岗位时不要只单单看给的薪资是多少,其中涉及的对于绩效奖金、交通、医疗、餐饮、期权等与我们每日生活相关的衣、食、住、行和个人工作,个人晋升等多方面的条件都需要我们进行综合的考虑加以衡量,最终找出适合自己期望的职位。
绘制图形:
分析:
从不同类型岗位更新数量的折线图可以看出,各类型职位从10月份开始,均有扩大招聘数量的趋势,并且在11-12月增长速度最快。产品运营、技术管理、技术开发、技术支持等类的职位需求增长相对较多,其中产品运营类增长最多。因此,有求职意向的同学,求职时要多加注意10月开始的这个岗位需求增长的浪潮,最为注意的是11月-12月期间。在此期间准备充分,争取拿到心仪offers。
求职目标:高薪、学历本科、大厂
对数据进行筛选
筛选条件:
①岗位类型:根据3.6 职位薪资Sankey图其中月薪在40k及以上,基本分布在业务咨询、数据运营类职位,所以对这类岗位类型进行筛选
②学历要求:本科、在校生or应届生
③月薪类别:40k+
绘制图形:
在40k以上的职位,根据薪资的方差,如果寻求平稳,那么可以选择数据运营类的职位,包括数据运营、数据分析岗位;如果有追求更高薪资水平的想法,可以尝试数据管理类职位,包括信息咨询与数据挖掘岗位。
此外:对筛选出的数据运营与数据管理类岗位进行导出到:结合自身对信管职位的分析筛选出的目标岗位.csv文件
数据结果如下:
对其中的公司名称进行词云图展现
可以看到:目标职业对应的主要公司是:腾讯和字节跳动。
如果你的求职目标是:高薪、学历本科、大厂。如果寻求平稳,那么可以选择数据运营类的职位,包括数据运营、数据分析岗位;如果有追求更高薪资水平的想法,可以尝试数据管理类职位,包括信息咨询与数据挖掘岗位,求职对应的主要公司是:腾讯和字节跳动。