入门小远学爬虫(二)(六)简单GET型网页爬虫实战——“前程无忧”爬虫岗位信息的爬取之简单“数据清洗”
前言
这是本系列第一个实战项目的第六课,有关前五课相关的内容请访问小远的主页。(上一课的链接)
上一节我们已经利用xlsxwriter库将前程无忧爬虫岗位中前十页的工作的详细信息全部存入Excel文件,效果如图:
但是仔细看,数据还并不完美,主要表现在以下几个方面:
1、有的公司的位置精确到了市,而有的精确到了区,这样画图的时候不太方便(关于画图小远会在之后的文章推出,敬请期待)。我们需要获取工作地点的经纬度坐标才好。
2、工资那一栏,有的单位是“万/月”、有的是“千/月”、还有的是“元/天”,这样只看数值的话就很不好比较。
3、有的公司没有学历要求,这就使得他们学历那一栏为空,不好看
4、对后几栏做词频统计,为之后画饼状图打好铺垫
5、……
处理这些问题的操作,叫做数据清洗
一、关于数据清洗
数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。与问卷审核不同,录入后的数据清理一般是由计算机而不是人工完成。——《百度百科》
数据清洗原理示意图
篇幅有限,更重要的是博主技术有限,本节只会使用最最简单的数据清洗方法。
二、分别分析需求
1、用工作地点确定经纬度:
将所有的工作地点打印出来,有
['上海-徐汇区', '宁波-鄞州区', '长沙-岳麓区', '合肥-高新区', '昆明', '上海-闵行区',
'广州-天河区', '北京-朝阳区', '武汉-江汉区', '上海-闵行区', '武汉', '苏州', '北京-海淀区',
……
'苏州-高新区', '无锡-惠山区', '重庆-渝北区', '深圳-南山区', '广州']
由于在比例尺不大的中国地图上,同一市级行政单位的各区级行政单位相距不远,再加之有些公司地点并未精确到区,所以我们只要能够剥离出城市名然后再进行查找比对就行了。这里小远分享一个资源,点击链接免费下载,这里面收录中国绝大多数地级市、县级市和县(包括自治县、旗、自治旗、特区和林区)的经纬度坐标信息,近2200余条。
我们的处理思路如下:
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)
运行结果如图
显然这个思路没有错,继续下一个:
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)
比如:
关于异常处理属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')))
运行结果如图
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)
运行结果如图
当然,真实数据当然不止这么一点,那么,重头戏来了
三、修改完善主程序
# -*- 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)
运行结果截图
.xlsx文件截图
下一课就到画图了,有点难,博主会更得比较慢,尽量让大家看得懂。
如果觉得博主写的还不错的,欢迎点赞、评论、加关注,大家的访问就是博主更新文章不竭的源动力!
本文地址:https://blog.csdn.net/I_am_Tony_Stark/article/details/110822550