使用Python爬取前程无忧上南京地区Python职位以及对应工资

2023-11-09

获取原始数据

最近在学习Python,做了一个爬虫程序练练手,前程无忧这个网站页面布局还是挺简单的,适合我这种新手。使用requests+bs4爬取
不多说了,先来看看页面布局吧。

这是前程无忧上的职位列表,看上去还是很清楚的
这是前程无忧上的职位列表,看上去还是很清楚的

然后再来看看页面布局,使用Google浏览器打开前程无忧网页,然后按下F12

每一个class为el的div就代表一个招聘信息
每一个class为el的div就代表一个招聘信息

然后再来看看div里面是怎么布局的,我们需要获取第二列公司名称以及第四列的薪资,其他的暂时不管。

公司名称在el这个div下面的class为t2的span标签下面。


薪资在t4的span标签下面。
这样获取的逻辑就很简单了。

  • 首先使用BeautifulSoup解析request获取的html
  • 找到所有class=el的div标签的集合
  • 在每个el标签下找到t2和t4标签,将它们的内容读取保存在字典中
  • 比如:‘南京万瑞建设工程有限公司’:‘12-20万/年’

以上是爬取一页的数据,我们当然不能只能爬取一页这么简单了,往下一页爬取有两种方法:
第一种方法就是获取到下一页的url,然后通过递归调用,不断地爬取
下一页的标签是这样的:

下一页在class等于bk的li标签下的a标签里面,首先要获取下一页的标签next_page,然后通过

next_url = next_page['href']

就可以获取到url,然后重复上述爬取一页的步骤,就可以不断的往下爬取了。

第二种方法我们来研究一下url
https://search.51job.com/list/070200,000000,0000,00,9,99,Python,2,1.html
这是第一页的url,注意最后的Python,2,1
https://search.51job.com/list/070200,000000,0000,00,9,99,Python,2,2.html
这是第二页,区别就是2,1变成了2,2
后面第三页,第四页就不看了,都是一样,只有数字的变化,这样就可以采用for循环了。

urlpri = 'https://search.51job.com/list/070200,000000,0000,00,9,99,Python,2,{page}.html'
for i in range(1,page+1) :#从1开始,并且page要加1,不然爬取不到最后一页
	url = urlpri.format(**{'page':i})

下面来看这部分代码:

def salary_python(page):
    """
    获取前程无忧上前page页的Python职位对应的公司名称以及薪水
    :param page: 页数
    :return:返回公司名称与薪水对应的字典
    """
    urlpri = 'https://search.51job.com/list/070200,000000,0000,00,9,99,Python,2,{page}.html'
    info = {}
    for i in range(1,page+1):
        url = urlpri.format(**{'page':i})
        res = requests.get(url)
        if res.status_code == 200:
            soup = BeautifulSoup(res.content, 'lxml')
            jobList = soup.find_all('div', class_='el')
            for job in jobList:
                companyName = ''
                salary = ''
                companyTag = job.find('span', class_='t2')
                if companyTag:
                    companyTag1 = companyTag.find('a')
                    if companyTag1:
                        companyName = companyTag1.string
                salaryTag = job.find('span', class_='t4')
                if salaryTag:
                    salary = salaryTag.string
                if companyName and salary: #有些职位没有写薪资,遇到这样的我们就跳过,这地方需要过滤一下
                    info[companyName] = salary
    return info

这是运行结果
看一下运行结果,是可以获取到对应的公司名称以及开出的薪资的,我的pycharm不知道出了啥问题,debug的时候想点开看一下,结果一点开debug就卡死,有知道原因的大神可以来解答一下。

数据简单处理

上面我们获取到的是原始数据,只有这个数据是看不出什么的,所以我们简单处理一下,这里用到Python的pandas模块,需要先安装一下

pip install pandas

主要是对工资进行处理,研究一下发现工资的写法主要包括下面几种

  • xx(-xx)万/月
  • xx(-xx)万/年
  • xx(-xx)千/月
  • xx(-xx)百/天
    或者没有万,百,千这种单位,直接是数字加上时间(月,年,天)
    剩下一些少见的单位,像什么万/天、百/月,就不考虑了。

我们单独写一个方法来处理工资的这种表述,统一化成 以月为单位 的形式
比如12万/年,将其转化为10000
对于工资在某一范围的,就取个中间值吧
比如0.6–1.2万/月,转化为9000

下面来看代码:

def handle_salary(s):
    """
    将str类型的薪水表述,转化为flaot类型,并全部转化为 元/月为单位
    :param s: 字符串类型数据
    :return: float类型薪水
    """
    if r'万' in s and r'月' in s:
        matchwMouth1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchwMouth2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchwMouth1:
            sMin = float(matchwMouth1.group(1))
            sMax = float(matchwMouth1.group(2))
            salary = (sMax+sMin)/2.0
            return round(salary*10000, 2)
        elif matchwMouth2:
            salary = float(matchwMouth2.group(1))
            return round(salary*10000, 2)
        else:
            return s
    elif r'万' in s and r'年' in s:
        matchwYear1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchwYear2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchwYear1:
            sMin = float(matchwYear1.group(1))
            sMax = float(matchwYear1.group(2))
            salary = (sMax + sMin) / (2.0*12)
            return round(salary*10000, 2)
        elif matchwYear2:
            salary = float(matchwYear2.group(1))/12.0
            return round(salary*10000, 2)
        else:
            return s
    elif r'千' in s and r'月' in s:
        matchqMouth1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchqMouth2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchqMouth1:
            sMin = float(matchqMouth1.group(1))
            sMax = float(matchqMouth1.group(2))
            salary = (sMax + sMin) / 2.0
            return round(salary*1000, 2)
        elif matchqMouth2:
            salary = float(matchqMouth2.group(1))
            return round(salary*1000, 2)
        else:
            return s
    elif r'百' in s and r'天' in s:
        matchbDay1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchbDay2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchbDay1:
            sMin = float(matchbDay1.group(1))
            sMax = float(matchbDay1.group(2))
            salary = (sMax + sMin) / 2.0
            return round(salary*3000, 2)
        elif matchbDay2:
            salary = float(matchbDay2.group(1))
            return round(salary*3000, 2)
        else:
            return s
    elif r'天' in s:
        matchbDay1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchbDay2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchbDay1:
            sMin = float(matchbDay1.group(1))
            sMax = float(matchbDay1.group(2))
            salary = (sMax + sMin) / 2.0
            return round(salary*30, 2)
        elif matchbDay2:
            salary = float(matchbDay2.group(1))
            return round(salary*30, 2)
        else:
            return s
    elif r'月' in s:
           matchMouth1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
           matchMouth2 = re.match(r'(\d+\.*\d*)', s, re.I)
           if matchMouth1:
               sMin = float(matchMouth1.group(1))
               sMax = float(matchMouth1.group(2))
               salary = (sMax + sMin) / 2.0
               return round(salary, 2)
           elif matchMouth2:
               salary = float(matchMouth2.group(1))
               return round(salary, 2)
           else:
               return s
    elif r'年' in s:
        matchYear1 = re.match(r'(\d+\.*\d*)\-(\d+\.*\d*)', s, re.I)
        matchYear2 = re.match(r'(\d+\.*\d*)', s, re.I)
        if matchYear1:
            sMin = float(matchYear1.group(1))
            sMax = float(matchYear1.group(2))
            salary = (sMax + sMin) / (2.0 * 12)
            return round(salary, 2)
        elif matchYear2:
            salary = float(matchYear2.group(1)) / 12.0
            return round(salary, 2)
        else:
            return s
    else:
        return s  #工资的表示形式如果不在上述几种形式范围内,就返回它的原有值,这个到后面还要在处理

这里的输出都是正确的
调试一下,输出都是对的。

接下来用一个for循环,把全部数据处理一下。
奉上代码:

def cleanData(dict):
    """
    清理获取的数据,薪水全部转化为float,单位为 元/月
    :param dict: 获取的原始数据
    :return: 返回处理过的数据
    """
    nameList = list(dict.keys())
    salary = list(dict.values())
    salaryList = []
    for item in salary:
        salaryItem = handle_salary(item)
        salaryList.append(salaryItem)

    dataDict = {}
    for i in range(len(nameList)):
        dataDict[nameList[i]] = salaryList[i]

    dataDictNew = {}
    for key,value in dataDict.items():
        if isinstance(value,float):
            dataDictNew[key] = value
    data = {'name': list(dataDictNew.keys()), 'salary': list(dataDictNew.values())}
    df = pd.DataFrame(data)
    return df

这里有几点要说明

    dataDict = {}
    for i in range(len(nameList)):
        dataDict[nameList[i]] = salaryList[i]

对数据的处理其实到这儿就已经结束了

 dataDictNew = {}
    for key,value in dataDict.items():
        if isinstance(value,float):
            dataDictNew[key] = value

这部分代码是为了过滤掉上面说的handle_salary这个方法没能处理掉的数据,经过处理过的工资,数据类型应该都是float,如果不是float,就将这条数据删除。

    data = {'name': list(dataDictNew.keys()), 'salary': list(dataDictNew.values())}
    df = pd.DataFrame(data)
    return df

这是将数据保存为pandas的dataframe形式,关于这种数据结构,可以参考这篇博客
https://www.cnblogs.com/IvyWong/p/9203981.html

保存数据

经过处理过的数据,将它保存在一个csv文件里,方便以后查看,pandas模块有直接处理csv文件的方法,非常方便。

def saveDataCsv(df):
    """
    保存数据到csv文件中
    :param df: dataframe类型数据
    :return:
    """
    filename = 'python_51job_salary.csv'
    try:
        df.to_csv(filename, encoding='gbk')
    except UnicodeEncodeError:
        print("编码错误, 该数据无法写到文件中, 直接忽略该数据")

这是保存的csv文件

简单分析

dataframe有个describe()方法,可以简单分析一下数据,我们来看一下

print(dfSalary.describe())  #dfsalary是处理过的数据,保存为dataframe格式

输出:

             salary
count    146.000000       #样本数量
mean   13519.748836     #工资平均值
std     7021.955639         #工资标准差
min     3750.000000        #最小值
25%     9000.000000		#从小到大排列,第25%的那个数
50%    12500.000000      #中位数
75%    15750.000000      #同25%
max    45000.000000       #最大值

还可以利用pandas模块画一个折线图

dfSalary.plot()
plt.show()  #绘制折现图表


工资看上去一万到两万的比较多。
下面这个方法用于统计工资高于某个值所占的比例:

def over(df, num):
    """
    计算高于num的工资所占比例
    :param df: 原始数据,dataframe类型
    :param num: 高于工资
    :return: 比例
    """
    salaryList = df.salary.tolist()
    overList = [x for x in salaryList if x>num]
    over = len(overList)*1.0/len(salaryList)
    return over
print(over(dfSalary,10000))    #工资高于10000所占比例  输出:0.7123287671232876

各位可以看下自己大概在啥水平哈。

新手一枚,代码还有许多不合理之处,欢迎各位大神提出意见,共同进步!!
附上github源码:
https://github.com/cchhgithub/pythonLearning/blob/master/pachong/51job.py

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用Python爬取前程无忧上南京地区Python职位以及对应工资 的相关文章

  • 稀有对象的 python 类型注释,例如 psycopg2 对象

    我了解内置类型 但是我如何指定稀有对象 例如数据库连接对象 def get connection and cursor gt tuple psycopg2 extensions cursor psycopg2 extensions conn
  • 使用 django-rest-framework 设置对象级权限

    尝试使用 django rest framework 最干净 最规范地管理 django guardian 对象级权限 我想将对象的读取权限 module view object 分配给在执行 POST 时发出请求的用户 我的基于阶级的观点
  • 多处理中的动态池大小?

    有没有办法动态调整multiprocessing Pool尺寸 我正在编写一个简单的服务器进程 它会产生工作人员来处理新任务 使用multiprocessing Process对于这种情况可能更适合 因为工作人员的数量不应该是固定的 但我需
  • 反编译Python 3.9.2的PYC文件[重复]

    这个问题在这里已经有答案了 目前 我有一个 3 9 2 版本的 python 的 PYC 文件 P S 这适用于所有 3 9 及更高版本 我正在尝试反编译 PYC 文件 但它显示错误 因为 uncompyle6 或者更确切地说 新版本 de
  • 是否可以从 Julia 调用 Python 函数并返回其结果?

    我正在使用 Python 从网络上抓取数据 我想使用这些数据在 Julia 中运行计算 是否可以在 Julia 中调用该函数并返回其结果 或者我最好直接导出到 CSV 并以这种方式加载数据 绝对地 看PyCall jl https gith
  • 从 Azure ML 实验中访问 Azure Blob 存储

    Azure ML 实验提供了通过以下方式读取 CSV 文件并将其写入 Azure Blob 存储的方法 Reader and Writer模块 但是 我需要将 JSON 文件写入 blob 存储 由于没有模块可以执行此操作 因此我尝试在Ex
  • 如何过滤 Pandas GroupBy 对象并获取 GroupBy 对象?

    当对 Pandas groupby 操作的结果执行过滤时 它返回一个数据帧 但假设我想执行进一步的分组计算 我必须再次调用 groupby 这似乎有点绕 有更惯用的方法吗 EDIT 为了说明我在说什么 我们无耻地从 Pandas 文档中窃取
  • 类型错误:需要二进制或 unicode 字符串,得到 618.0

    I ve been trying to implement this ML Linear Model into my dataset https www tensorflow org tutorials estimator linear L
  • 在Python上获取字典的前x个元素

    我是Python的新手 所以我尝试用Python获取字典的前50个元素 我有一本字典 它按值降序排列 k 0 l 0 for k in len dict d l 1 if l lt 51 print dict 举个小例子 dict d m
  • 运行 Python 单元测试,以便成功时不打印任何内容,失败时仅打印 AssertionError()

    我有一个标准单元测试格式的测试模块 class my test unittest TestCase def test 1 self tests def test 2 self tests etc 我的公司有一个专有的测试工具 它将作为命令行
  • 查找 Pandas DF 行中的最短日期并创建新列

    我有一个包含多个日期的表 有些日期将为 NaN 我需要找到最旧的日期 所以一行可能有 DATE MODIFIED WITHDRAWN DATE SOLD DATE STATUS DATE 等 因此 对于每一行 一个或多个字段中都会有一个日期
  • Ubuntu systemd 自定义服务因 python 脚本而失败

    希望获得有关 Ubuntu 中的 systemd 守护进程服务的一些帮助 我写了一个 python 脚本来禁用 Dell XPS 上的触摸屏 这更像是一个问题 而不是一个有用的功能 该脚本可以工作 但我不想一直启动它 这就是为什么我想到编写
  • 使用 Python 将连续日期分组在一起

    Given dates datetime 2014 10 11 datetime 2014 10 1 datetime 2014 10 2 datetime 2014 10 3 datetime 2014 10 5 datetime 201
  • minizinc python 安装

    我通过 anaconda 提示符在 python 上安装了 minizinc 就像其他软件包一样 pip install minizinc 该软件包表示已成功安装 我可以导入该模块 但是 我正在遵循基本示例https minizinc py
  • 如何给URL添加变量?

    我正在尝试从网站收集数据 我有一个 Excel 文件 其中包含该网站的所有不同扩展名 F i www example com example2 我有一个脚本可以成功从网站中提取 HTML 但现在我想为所有扩展自动执行此操作 然而 当我说 s
  • rpy2 无法加载外部库

    希望有人能帮忙解决这个问题 R版本 2 14 1rpy2版本 2 2 5蟒蛇版本 2 7 3 一直在尝试在 python 脚本中使用 rpy2 加载 R venneuler 包 该包以 rJava 作为依赖项 venneuler 和 rJa
  • 如何从namedtuple实例列表创建pandas DataFrame(带有索引或多索引)?

    简单的例子 from collections import namedtuple import pandas Price namedtuple Price ticker date price a Price GE 2010 01 01 30
  • pandas 中数据帧中的随机/洗牌行

    我目前正在尝试找到一种方法来按行随机化数据框中的项目 我在 pandas 中按列洗牌 排列找到了这个线程 在 pandas 中对 DataFrame 进行改组 排列 https stackoverflow com questions 157
  • 如何使用 python 定位和读取 Data Matrix 代码

    我正在尝试读取微管底部的数据矩阵条形码 我试过libdmtx http libdmtx sourceforge net 它有 python 绑定 当矩阵的点是方形时工作得相当好 但当矩阵的点是圆形时工作得更糟 如下所示 另一个复杂问题是在某
  • 定义在文本小部件中双击时选择哪些字符

    在 Windows 上 双击文本小部件中的单词也将选择连接的标点符号 有什么方法可以定义您想要选择的角色吗 tcl wordchars该变量的值是一个正则表达式 可以设置它来控制什么被视为 单词 字符 例如 通过双击 Tk 中的文本来选择单

随机推荐