獲取原始數據
最近在學習Python,做了一個爬蟲程序練練手,前程無憂這個網站頁面布局還是挺簡單的,適合我這種新手。使用requests+bs4爬取
不多說了,先來看看頁面布局吧。
然后再來看看頁面布局,使用Google瀏覽器打開前程無憂網頁,然后按下F12
然后再來看看div里面是怎么布局的,我們需要獲取第二列公司名稱以及第四列的薪資,其他的暫時不管。
公司名稱在el這個div下面的class為t2的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("編碼錯誤, 該數據無法寫到文件中, 直接忽略該數據")
簡單分析
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
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
