赞
踩
最近在学习Python,做了一个爬虫程序练练手,前程无忧这个网站页面布局还是挺简单的,适合我这种新手。使用requests+bs4爬取
不多说了,先来看看页面布局吧。
这是前程无忧上的职位列表,看上去还是很清楚的
然后再来看看页面布局,使用Google浏览器打开前程无忧网页,然后按下F12
每一个class为el的div就代表一个招聘信息
然后再来看看div里面是怎么布局的,我们需要获取第二列公司名称以及第四列的薪资,其他的暂时不管。
公司名称在el这个div下面的class为t2的span标签下面。
薪资在t4的span标签下面。
这样获取的逻辑就很简单了。
以上是爬取一页的数据,我们当然不能只能爬取一页这么简单了,往下一页爬取有两种方法:
第一种方法就是获取到下一页的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
主要是对工资进行处理,研究一下发现工资的写法主要包括下面几种
我们单独写一个方法来处理工资的这种表述,统一化成 以月为单位 的形式
比如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("编码错误, 该数据无法写到文件中, 直接忽略该数据")
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。