欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

程序员文章站 2022-06-26 17:14:02
文章目录前言一、关于数据清洗二、分别分析需求1、用工作地点确定经纬度:2、工资待遇统一单位:3、补全空格:4、对后几列做词频统计三、修改完善主程序前言这是本系列第一个实战项目的第六课,有关前五课相关的内容请访问小远的主页。(上一课的链接)上一节我们已经利用xlsxwriter库将前程无忧爬虫岗位中前十页的工作的详细信息全部存入Excel文件,效果如图:但是仔细看,数据还并不完美,主要表现在以下几个方面:1、有的公司的位置精确到了市,而有的精确到了区,这样画图的时候不太方便(关于画图小远会在之后...


前言

这是本系列第一个实战项目的第六课,有关前五课相关的内容请访问小远的主页。(上一课的链接

上一节我们已经利用xlsxwriter库将前程无忧爬虫岗位中前十页的工作的详细信息全部存入Excel文件,效果如图:
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”
但是仔细看,数据还并不完美,主要表现在以下几个方面:
1、有的公司的位置精确到了市,而有的精确到了区,这样画图的时候不太方便(关于画图小远会在之后的文章推出,敬请期待)。我们需要获取工作地点的经纬度坐标才好。
2、工资那一栏,有的单位是“万/月”、有的是“千/月”、还有的是“元/天”,这样只看数值的话就很不好比较。
3、有的公司没有学历要求,这就使得他们学历那一栏为空,不好看
4、对后几栏做词频统计,为之后画饼状图打好铺垫
5、……

处理这些问题的操作,叫做数据清洗


一、关于数据清洗

数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。与问卷审核不同,录入后的数据清理一般是由计算机而不是人工完成。——《百度百科》

数据清洗原理示意图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”
篇幅有限,更重要的是博主技术有限,本节只会使用最最简单的数据清洗方法。

二、分别分析需求

1、用工作地点确定经纬度:

将所有的工作地点打印出来,有

['上海-徐汇区', '宁波-鄞州区', '长沙-岳麓区', '合肥-高新区', '昆明', '上海-闵行区',
 '广州-天河区', '北京-朝阳区', '武汉-江汉区', '上海-闵行区', '武汉', '苏州', '北京-海淀区', 
 ……
 '苏州-高新区', '无锡-惠山区', '重庆-渝北区', '深圳-南山区', '广州']

由于在比例尺不大的中国地图上,同一市级行政单位的各区级行政单位相距不远,再加之有些公司地点并未精确到区,所以我们只要能够剥离出城市名然后再进行查找比对就行了。这里小远分享一个资源,点击链接免费下载,这里面收录中国绝大多数地级市、县级市和县(包括自治县、旗、自治旗、特区和林区)的经纬度坐标信息,近2200余条
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

我们的处理思路如下:
step1:将文件放在目录下,检测有无“-”字符
step2:如无,直接在json文件中检测;如有,取“-”之前的字符串(即为城市名)
step3:将所有经纬度以元组列表形式存储

那么我们可以用一个临时.py文件来测试思路,最后再加到主程序中

dic = json.load(open('城市经纬度.json', 'rb'))
LngLat = []
for city in cities:
    LngLat.append(tuple(dic[city.split('-')[0] if '-' in city else city]))
print(LngLat)

运行结果如图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

显然这个思路没有错,继续下一个:

2、工资待遇统一单位:

工资待遇一栏如下:

['1.2-1.6万/月', '0.8-1万/月', '0.8-1万/月', '5-7千/月', '0.8-1万/月', '1.5-2.5万/月',
 '0.8-1.2万/月', '1.5-2万/月', '1-1.5万/月', '6-8万/月', '1-1.5万/月', '0.5-1万/月', 
 ……
 '2-2.5万/月', '20-30万/年', '2-3千/月', '4-8千/月', '1-1.5万/月']

先分析一下这里工资数据的结构:
【A】-【B】(C)/(D)
A:最低工资
B:最高工资
C:货币单位
D:时间单位
那么统一单位就交给数学知识了,这里我需要将其统一成(千/月)并求一下平均工资

这里仍然需要用到正则表达式,关于正则的知识,可以参考小远的博文“入门小远学爬虫(二)(四)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之正则概念以及Python中re库的简单应用
(有个别如下情形会使得异常,需特殊处理,具体见代码)
【A】(C)/(D)
比如:
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”
关于异常处理属Python基本语法,在此不赘述

直接上代码

for wage in wages:
    try:
        temp = re.findall(r'(.*)-(.*)([万/千/元])/([年/月/天/日])', wage)[0]
        wMin = eval(temp[0])
        wMax = eval(temp[1])
    except IndexError:
        temp = re.findall(r'(.+)(-?)([万/千/元])/([年/月/天/日])', wage)[0]
        wMin = eval(temp[0])
        wMax = wMin
    if temp[2] == '万':
        wMin, wMax = wMin * 10, wMax * 10
    elif temp[2] == '元':
        wMin, wMax = wMin / 1000, wMax / 1000
    if temp[3] == '年':
        wMin, wMax = wMin / 12, wMax / 12
    elif temp[3] == '天' or temp[3] == '日':
        wMin, wMax = wMin * 30, wMax * 30

    print("转化前{},{}-{}千/月,平均{}千/月".format(wage, format(wMin, '0.1f'), format(wMax, '0.1f'), format(0.5*(wMin+wMax), '0.1f')))

运行结果如图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

3、补全空格:

这一部分比较简单,直接在正式程序里写算了(第三大点,就在下面)

4、对后几列做词频统计

对于词频统计的方法,当然是用字典了
为了方便,小远直接假设一个数据,比如

['A', 'B', 'C', 'D', 'E', 'F', 'A', 'F', 'C', 'D']

现在我要对这个列表进行遍历并做词频统计,那么
上代码:

a = ['A', 'B', 'C', 'D', 'E', 'F', 'A', 'F', 'C', 'D']
dic = {}
for word in a:
    dic[word] = 1 if word not in dic else dic[word] + 1
print(dic)

运行结果如图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

当然,真实数据当然不止这么一点,那么,重头戏来了

三、修改完善主程序

# -*- coding: utf-8 -*-
# @Time: 2020/11/29 14:21
# @Author: 胡志远
# @Software: PyCharm

# 导入re包
import re
# 导入requests包
import requests
# 导入lxml中的html模块
from lxml import html
# 导入xlsxwriter包
import xlsxwriter
# 需要用到时间
import datetime

import json

# 请求头
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Connection": "keep-alive",
    "Cookie": "guid=7e8a970a750a4e74ce237e74ba72856b; partner=blog_csdn_net",
    "Host": "jobs.51job.com",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"
}
Information = list()
for i in range(1, 11):
    # 网页链接
    url = "https://jobs.51job.com/pachongkaifa/p{}/".format(i)
    # 有请求头写法
    res = requests.get(url=url, headers=headers)
    res.encoding = "gbk"
    # 生成Element对象
    etree = html.etree
    html_elements = etree.HTML(res.text)
    # 根据Xpath路径获取每一个工作的对象
    Jobs_element = html_elements.xpath('//div[@class="detlist gbox"]/div')
    # 正则
    regex = '.*href="(.*)">(.*)</a.*href=.*">(.*)</a.*me">(.*)</span.*on">(.*)</span.*me">(.*)</span.*:(.*)<s.*:(.*)<s.*:(.*)<s.*:(.*)</p.*title='
    for aJob in Jobs_element:
        Information += re.findall(regex, etree.tostring(aJob, encoding="utf-8").decode(), re.S)
    # 将元组列表改为列表列表
    Information = [list(tup) for tup in Information]


# 对数据进行处理

def wage(w):
    try:
        temp = re.findall(r'(.*)-(.*)([万/千/元])/([年/月/天/日])', w)[0]
        wMin = eval(temp[0])
        wMax = eval(temp[1])
    except IndexError:
        temp = re.findall(r'(.+)(-?)([万/千/元])/([年/月/天/日])', w)[0]
        wMin = eval(temp[0])
        wMax = wMin
    if temp[2] == '万':
        wMin, wMax = wMin * 10, wMax * 10
    elif temp[2] == '元':
        wMin, wMax = wMin / 1000, wMax / 1000
    if temp[3] == '年':
        wMin, wMax = wMin / 12, wMax / 12
    elif temp[3] == '天' or temp[3] == '日':
        wMin, wMax = wMin * 30, wMax * 30
    return "{}-{}千/月".format(format(wMin, '0.1f'), format(wMax, '0.1f')), "{}千/月".format(0.5 * (wMin + wMax), '0.1f')


j = open('城市经纬度.json', 'rb')
city = json.load(j)
# 创建四个空字典
Education, Experience, Type, Count = {}, {}, {}, {}
for item in Information:
    # 将经纬度坐标加在后面
    item.append(str(tuple(city[item[3].split('-')[0] if '-' in item[3] else item[3]])))
    # 将工资统一格式化并将平均工资加在后面
    wg, aveWg = wage(item[4])
    item[4] = wg
    item.append(aveWg)
    # 若学历信息为空改为无学历要求
    item[6] = item[6] if item[6] != "" else "无学历要求"
    # 对学历要求、需要资历、公司类型、招聘人数进行词频统计
    Education[item[6]] = 1 if item[6] not in Education else Education[item[6]] + 1
    Experience[item[7]] = 1 if item[7] not in Experience else Experience[item[7]] + 1
    Type[item[8]] = 1 if item[8] not in Type else Type[item[8]] + 1
    Count[item[9]] = 1 if item[9] not in Count else Count[item[9]] + 1
j.close()

wb = xlsxwriter.Workbook('前程无忧招聘信息.xlsx')
ws = wb.add_worksheet('爬虫岗位')
# 先打印一个时间
ws.write(0, 0, "数据截止时间", wb.add_format({'bold': True}))  # 加粗
ws.write(0, 1, datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S'))
# 再写上列名
columnNames = ("网址", "岗位名称", "所属公司", "工作地点", "工资待遇", "招聘信息发布时间", "学历要求", "需要资历", "公司类型", "招聘人数", "工作地点经纬度", "平均工资")
for columnName in columnNames:
    ws.write(1, columnNames.index(columnName), columnName, wb.add_format({'bold': True}))
# 查看总列表
for item in Information:
    for each in item:
        ws.write(Information.index(item) + 2, item.index(each), each)
wb.close()

# 暂时先将四个字典打印出来,之后还有用的
print(Education)
print(Experience)
print(Type)
print(Count)

运行结果截图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”
.xlsx文件截图
入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”

下一课就到画图了,有点难,博主会更得比较慢,尽量让大家看得懂。


如果觉得博主写的还不错的,欢迎点赞、评论、加关注,大家的访问就是博主更新文章不竭的源动力!

本文地址:https://blog.csdn.net/I_am_Tony_Stark/article/details/110822550