ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

爬取51job职位信息--进行专业市场需求可视化分析(python、tableau、DBeaver)

2022-01-17 13:00:24  阅读:219  来源: 互联网

标签:-- text 51job DBeaver job split each data 岗位


爬取51job信管专业相关岗位的情况进行可视化分析。
采用工具:python、tableau(可视化)、DBeaver(数据库管理软件)

文章目录

一.数据爬取

数据爬取过程
考虑到requests库进行数据的请求容易被平台反扒发现,从而封锁ip导致数据不能正常爬取。因此采用selenium模拟浏览器,进行数据的采集。总共需要采集的岗位有30终类型,先通过selenium采集每种类型岗位需要采集的总页数。然后利用每种岗位的总页数信息,进行岗位数据的采集。通过模拟浏览器访问51job网页,获取页面的HTML文本,然后采用BeautifulSoup库进行需要采集数据点的的数据的获取,最终将获取到的数据写入数据库中进行存储。

1.1导入相关的库

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	

1.2对每个岗位搜索的到的总页数进行爬取

请添加图片描述

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列表将用于之后的数据爬取时对应每个职位的爬取页数。

1.3进行爬取数据相关函数的设计

#定义 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()  # 提交数据
	

1.4进行数据的爬取

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()

1.5数据库爬取到的数据展示

在这里插入图片描述

二.数据清洗

2.1清洗相关函数的设计

#引入 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()

2.2进行数据清洗

if __name__ == "__main__":
    data = pipei()
    data1 = split_city(data)
    data2 = salary(data1)
    data3 = job_attribute_text(data2)
#将清洗后的数据存储到数据库的另一个表格中
    save(data3)

2.3清洗后数据展示

清洗后的数据:薪资、工作要求、工作地点等进行了统一格式
在这里插入图片描述

三.数据可视化

采用工具:tableau+python

3.1 柱状图

绘制界面:
在这里插入图片描述

图形:
在这里插入图片描述

分析:
对于职位的类型的占比可以看出,产品运营类岗位占据了最大的份额,说明信管专业的学生在这类岗位中需求最多,往后的求职过程中可关注此类岗位的职位。此外,技术管理类、技术开发类、技术支持类岗位的占比也在平均值以上,此类岗位也可以多加关注。

3.2 树状图

绘制界面:
在这里插入图片描述

绘制图形:

在这里插入图片描述

分析:
从学历要求的树状图可以看出,市场上的大部分与信管相关职位的需求是大专和本科均占据47%左右,对于硕士和博士等更高学历的要求分别占据0.0754066%、1.86667%,说明信管专业相关职业的学历门槛并不会太高。

3.3各岗位类型公司规模数量特征条形图

绘制界面:
在这里插入图片描述

绘制图形:

在这里插入图片描述

分析:
各岗位类型公司规模数量特征条形图可以看出,对于产品运营类职位,公司的规模规模主要为500人以下的公司。数据运营类、技术管理类、数据管理类公司大厂10000人以上的大厂占比较高,如果你想去大厂,那么可以选择这类职位发展。

3.4 热力图

绘制界面:
在这里插入图片描述

绘制图形:
在这里插入图片描述

分析:
通过对信管所有相关岗位的热力图分析可知,信管专业的工作地点在:上海、北京、成都、杭州、武汉、南京、深圳、广州、重庆、等地较多,其中在上海、广州、深圳3地的最多。此外,郑州、西安、长沙、合肥等地的职位也相对较多,所以如果想避免竞争的激烈,可以考虑这些地方。

3.5 箱线图

绘图界面:
在这里插入图片描述

绘制图形:
在这里插入图片描述

分析:
从学历的薪资水平箱线图可以看出:随着学历的上升,薪资的平均水平逐步提升,说明学历对于薪资是具有一定的影响的,学历越高,薪资的平均水平越高。此外,对于大专、本科、硕士类学历的专业,我们可以看到许多在箱线图以外的点,这些点表明这些职位的薪资大大超出平均水平,说明在某些职位,对于学历的要求并不是很重要,重要的是个人的其他方面的能力,比如说专业的机能等方面。为此,无论我们在那个阶段,要多加关注自己的职业技能,只要我们的学历和能力不是虚涨的,有实实在在的能力和产出做支撑,就不会有:大学生毕业了找不到工作这种焦虑。

3.6 职位薪资Sankey图

绘制界面:
在这里插入图片描述

绘制图形:

在这里插入图片描述

分析:
通过职位薪资的桑基图可以看出:月薪,产品运营类职位的薪资大部分在10-15K,其余集中在5-10k;技术管理类与技术开发类,部分达到15-20k,其余集中在10-15K;技术支持类大约二分之一达到20-25k;对于25k以上的高薪职位主要集中在业务咨询、数据运营、数据管理、技术支持类职业。其中月薪在40k及以上,基本分布在业务咨询、数据运营类职位,所以有超高薪职位目标的同学,可以考虑往这类职业发展。

3.7 岗位工作待遇热词词云图

代码:

#设计词频统计函数
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进行词云图的绘制

绘制图形:

请添加图片描述

分析:
通过岗位工作待遇热词词云图可以看到,工作待遇之中,企业最常提到的是:绩效奖金、年终奖、专业培训、餐饮、交通、弹性、体检、通勤、医疗保险、期权等。说明了,我们求职时,考虑岗位时不要只单单看给的薪资是多少,其中涉及的对于绩效奖金、交通、医疗、餐饮、期权等与我们每日生活相关的衣、食、住、行和个人工作,个人晋升等多方面的条件都需要我们进行综合的考虑加以衡量,最终找出适合自己期望的职位。

3.8 不同类型岗位更新数量的折线图

绘制图形:
请添加图片描述

分析:
从不同类型岗位更新数量的折线图可以看出,各类型职位从10月份开始,均有扩大招聘数量的趋势,并且在11-12月增长速度最快。产品运营、技术管理、技术开发、技术支持等类的职位需求增长相对较多,其中产品运营类增长最多。因此,有求职意向的同学,求职时要多加注意10月开始的这个岗位需求增长的浪潮,最为注意的是11月-12月期间。在此期间准备充分,争取拿到心仪offers。

四.结合自身对信管职位的分析

4.1简介

求职目标:高薪、学历本科、大厂
对数据进行筛选
筛选条件:
①岗位类型:根据3.6 职位薪资Sankey图其中月薪在40k及以上,基本分布在业务咨询、数据运营类职位,所以对这类岗位类型进行筛选
②学历要求:本科、在校生or应届生
③月薪类别:40k+

绘制图形:
请添加图片描述

4.2分析

在40k以上的职位,根据薪资的方差,如果寻求平稳,那么可以选择数据运营类的职位,包括数据运营、数据分析岗位;如果有追求更高薪资水平的想法,可以尝试数据管理类职位,包括信息咨询与数据挖掘岗位。
此外:对筛选出的数据运营与数据管理类岗位进行导出到:结合自身对信管职位的分析筛选出的目标岗位.csv文件

数据结果如下:
在这里插入图片描述

对其中的公司名称进行词云图展现
在这里插入图片描述

可以看到:目标职业对应的主要公司是:腾讯和字节跳动。

4.3总结

如果你的求职目标是:高薪、学历本科、大厂。如果寻求平稳,那么可以选择数据运营类的职位,包括数据运营、数据分析岗位;如果有追求更高薪资水平的想法,可以尝试数据管理类职位,包括信息咨询与数据挖掘岗位,求职对应的主要公司是:腾讯和字节跳动。

请添加图片描述

标签:--,text,51job,DBeaver,job,split,each,data,岗位
来源: https://blog.csdn.net/m0_52985451/article/details/122537237

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有