拉勾爬蟲實戰(zhàn)

0 引言

??一次簡單的 Python 爬蟲練習(xí):輸入 目標(biāo)城市目標(biāo)職位爆惧,從 拉勾網(wǎng) 爬取相關(guān)的職位列表數(shù)據(jù)(受拉勾網(wǎng)的展示機制限制冬竟,只能爬取 30 頁共 450 條記錄),并對數(shù)據(jù)進行清洗糕韧,最后進行簡單的描述統(tǒng)計和回歸分析寺滚。
# 主要參考了 閑庭信步 的拉勾爬蟲代碼。

1 分析網(wǎng)頁

??首先氨淌,使用 chrome 打開拉勾網(wǎng)泊愧,選擇城市為廣州,搜索產(chǎn)品經(jīng)理職位盛正,會進入下圖的職位列表頁:

拉勾職位列表頁

??接下來使用開發(fā)者工具進行發(fā)現(xiàn)删咱,會發(fā)現(xiàn)頁面上的職位信息其實是存在 positionAjax.json 上的,查看 positionAjax.jsonHeaders豪筝,獲取爬蟲所需要的信息:

使用開發(fā)者工具分析網(wǎng)頁
# Request URL
url = 'https://www.lagou.com/jobs/positionAjax.json?city=%E5%B9%BF%E5%B7%9E&needAddtionalResult=false'

# Request Headers
my_headers = {  
  'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',  
  'Host':'www.lagou.com',  
  'Referer':'https://www.lagou.com/jobs/list_%E6%95%B0%E6%8D%AE%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86?city=%E5%B9%BF%E5%B7%9E&cl=false&fromSearch=true&labelWords=&suginput=',  
  'X-Anit-Forge-Code':'0',  
  'X-Anit-Forge-Token': 'None',  
  'X-Requested-With':'XMLHttpRequest'  
}

# Form Data
my_data = {  
  'first': 'true',  
  'pn': 1,  
  'kd': '產(chǎn)品經(jīng)理'
} 

??有了這些信息痰滋,我們就可以使用 POST 請求抓取網(wǎng)頁了。但為了讓程序可以更加方便续崖,不需要每次都復(fù)制粘貼那么多信息敲街,我們應(yīng)該在這些信息中找出不變的和可由程序自行填充的,最好能達到只需要輸入目標(biāo)城市和職位袜刷,就可以自動爬取的效果聪富。

2 爬取網(wǎng)頁

2.1 導(dǎo)入相關(guān)包

??由于這次還會進行簡單的描述統(tǒng)計和回歸分析,因此需要導(dǎo)入較多包:

# 數(shù)據(jù)處理及導(dǎo)入導(dǎo)出
import pandas as pd  
# 爬蟲
import requests  
import math  
import time
import sys, urllib
# 數(shù)據(jù)可視化
import seaborn as sns
import matplotlib.pyplot as plt
# 統(tǒng)計建模
import statsmodels.api as sm
# 詞云
from wordcloud import WordCloud  
from imageio import imread
import jieba

2.2 構(gòu)建爬蟲函數(shù)

??這里一共構(gòu)建了 4 個函數(shù):

  • get_json(url, num, encode_city, position):從網(wǎng)頁獲取 JSON著蟹,使用 POST 請求墩蔓,加上頭部信息
  • get_page_num(count):根據(jù)職位數(shù)計算要抓取的頁數(shù)(最多 30 頁
  • get_page_info(jobs_list):對包含網(wǎng)頁職位信息的 JSON 進行解析,返回列表
  • lagou_spider(city, position):獲取鍵盤輸入的目標(biāo)城市和職位,調(diào)用其它 3 個函數(shù)萧豆,爬取拉勾職位列表數(shù)據(jù)并導(dǎo)出為 CSV 文件奸披。
def get_json(url, num, encode_city, position):  
    '''
        從網(wǎng)頁獲取 JSON,使用 POST 請求涮雷,加上頭部信息
    '''
    # Request Headers
    my_headers = {  
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',  
        'Host':'www.lagou.com',  
        'Referer':'https://www.lagou.com/jobs/list_%E6%95%B0%E6%8D%AE%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86?city=' + encode_city + '&cl=false&fromSearch=true&labelWords=&suginput=',  
        'X-Anit-Forge-Code':'0',  
        'X-Anit-Forge-Token': 'None',  
        'X-Requested-With':'XMLHttpRequest'  
    }

    # Form Data
    my_data = {  
        'first': 'true',  
        'pn': num,  
        'kd': position
    }  
    
    res = requests.post(url, headers = my_headers, data = my_data)  
    res.raise_for_status()  
    res.encoding = 'utf-8'  
    
    # 得到包含職位信息的字典  
    page = res.json()  
    return page  
def get_page_num(count):  
    '''
        計算要抓取的頁數(shù)
    '''  
    # 每頁15個職位,向上取整  
    res = math.ceil(count/15)  
    # 拉勾網(wǎng)最多顯示30頁結(jié)果  
    if res > 30:  
        return 30  
    else:  
        return res  
def get_page_info(jobs_list):  
    ''''
        對一個網(wǎng)頁的職位信息進行解析,返回列表
    '''  
    page_info_list = []  
    
    for i in jobs_list:  
        job_info = []  
        job_info.append(i['companyFullName'])  
        job_info.append(i['companyShortName'])  
        job_info.append(i['companySize']) 
        job_info.append(i['financeStage'])  
        job_info.append(i['district'])  
        job_info.append(i['industryField'])  
        job_info.append(i['positionName'])  
        job_info.append(i['jobNature']) 
        job_info.append(i['firstType']) 
        job_info.append(i['secondType']) 
        job_info.append(i['workYear'])  
        job_info.append(i['education'])  
        job_info.append(i['salary'])  
        job_info.append(i['positionAdvantage'])  
        page_info_list.append(job_info)  
    
    return page_info_list  
def lagou_spider(city, position):  
    '''
        爬取拉勾職位列表數(shù)據(jù)
    '''
    # encode city
    encode_city = urllib.parse.quote(city)
    
    # Request URL
    url = 'https://www.lagou.com/jobs/positionAjax.json?city=' + encode_city + '&needAddtionalResult=false'  
    
    # 先設(shè)定頁數(shù)為1,獲取總的職位數(shù)  
    first_page = get_json(url,1, encode_city, position)  
    total_count = first_page['content']['positionResult']['totalCount']  
    num = get_page_num(total_count)  
    total_info = []  
    time.sleep(20)  
    print('職位總數(shù):{},頁數(shù):{}'.format(total_count,num))  

    for n in range(1,num+1):  
        # 對每個網(wǎng)頁讀取JSON, 獲取每頁數(shù)據(jù)  
        page = get_json(url, n, encode_city, position)  
        jobs_list = page['content']['positionResult']['result']  
        page_info = get_page_info(jobs_list)  
        total_info += page_info  
        print('已經(jīng)抓取第{}頁, 職位總數(shù):{}'.format(n, len(total_info)))  
        # 每次抓取完成后,暫停一會,防止被服務(wù)器拉黑  
        time.sleep(30)  
       
    #將總數(shù)據(jù)轉(zhuǎn)化為 DataFrame再輸出  
    df = pd.DataFrame(data = total_info,columns = ['公司全名', '公司簡稱', '公司規(guī)模', '融資階段', '區(qū)域', '行業(yè)', 
                                                   '職位名稱', '工作性質(zhì)', '一級類別', '二級類別', '工作經(jīng)驗', '學(xué)歷要求', '工資','職位福利'])   
    data_output = 'data\\' + city + '—' + position + '.csv'
    df.to_csv(data_output, index = False)  
    
    print('已保存為csv文件.')  

2.3 運行爬蟲

??完成爬蟲函數(shù)之后阵面,就可以開始運行爬蟲了,在鍵盤輸入工作城市和目標(biāo)職位洪鸭,爬蟲開始運行:

# 輸入工作城市和目標(biāo)職位
city = input("工作城市:\n")
position = input("目標(biāo)職位:\n")

工作城市:
廣州
目標(biāo)職位:
產(chǎn)品經(jīng)理

# 運行爬蟲
lagou_spider(city, position)

職位總數(shù):1176,頁數(shù):30
已經(jīng)抓取第1頁, 職位總數(shù):15
已經(jīng)抓取第2頁, 職位總數(shù):30
已經(jīng)抓取第3頁, 職位總數(shù):45
已經(jīng)抓取第4頁, 職位總數(shù):60
已經(jīng)抓取第5頁, 職位總數(shù):75
已經(jīng)抓取第6頁, 職位總數(shù):90
已經(jīng)抓取第7頁, 職位總數(shù):105
已經(jīng)抓取第8頁, 職位總數(shù):120
已經(jīng)抓取第9頁, 職位總數(shù):135
已經(jīng)抓取第10頁, 職位總數(shù):150
已經(jīng)抓取第11頁, 職位總數(shù):165
已經(jīng)抓取第12頁, 職位總數(shù):180
已經(jīng)抓取第13頁, 職位總數(shù):195
已經(jīng)抓取第14頁, 職位總數(shù):210
已經(jīng)抓取第15頁, 職位總數(shù):225
已經(jīng)抓取第16頁, 職位總數(shù):240
已經(jīng)抓取第17頁, 職位總數(shù):255
已經(jīng)抓取第18頁, 職位總數(shù):270
已經(jīng)抓取第19頁, 職位總數(shù):285
已經(jīng)抓取第20頁, 職位總數(shù):300
已經(jīng)抓取第21頁, 職位總數(shù):315
已經(jīng)抓取第22頁, 職位總數(shù):330
已經(jīng)抓取第23頁, 職位總數(shù):345
已經(jīng)抓取第24頁, 職位總數(shù):360
已經(jīng)抓取第25頁, 職位總數(shù):375
已經(jīng)抓取第26頁, 職位總數(shù):390
已經(jīng)抓取第27頁, 職位總數(shù):405
已經(jīng)抓取第28頁, 職位總數(shù):420
已經(jīng)抓取第29頁, 職位總數(shù):435
已經(jīng)抓取第30頁, 職位總數(shù):450
已保存為csv文件.

??為了防止被封样刷,因此爬蟲在每次完成抓取后,都會暫停一會览爵,這導(dǎo)致爬蟲需要較多時間置鼻。爬蟲運行完成后,將數(shù)據(jù)保存為 CSV 文件蜓竹,使用 Pandas 打開:

# 讀取數(shù)據(jù)  
file = open('data\\' + city + '—' + position + '.csv', 'rb')
df = pd.read_csv(file, encoding = 'utf-8')  

??查看頭 5 行的數(shù)據(jù):

df.head()
爬蟲結(jié)果頭 5 行數(shù)據(jù)

3 數(shù)據(jù)清洗

??首先剔除非全職的職位:

# 剔除實習(xí)崗位  
df.drop(df[df['工作性質(zhì)'].str.contains('實習(xí)')].index, inplace=True)  

??將工作經(jīng)驗和工資由字符串形式轉(zhuǎn)換為列表:

pattern = '\d+'  
df['工作年限'] = df['工作經(jīng)驗'].str.findall(pattern)

avg_work_year = []  
for i in df['工作年限']:  
    # 如果工作經(jīng)驗為 '不限' 或 '應(yīng)屆畢業(yè)生' ,那么匹配值為空,工作年限為 0  
    if len(i) == 0:  
        avg_work_year.append(0)  
   # 如果匹配值為一個數(shù)值,那么返回該數(shù)值  
    elif len(i) == 1:  
        avg_work_year.append(int(''.join(i)))  
   # 如果匹配值為一個區(qū)間,那么取平均值  
    else:  
        num_list = [int(j) for j in i]  
        avg_year = sum(num_list) / 2  
        avg_work_year.append(avg_year)
        
df['經(jīng)驗要求'] = avg_work_year
        
# 將字符串轉(zhuǎn)化為列表,再取區(qū)間的前25%箕母,比較貼近現(xiàn)實  
df['salary'] = df['工資'].str.findall(pattern)  

avg_salary = []  
for k in df['salary']:  
    int_list = [int(n) for n in k]  
    avg_wage = int_list[0] + (int_list[1] - int_list[0]) / 4  
    avg_salary.append(avg_wage) 

df['月工資'] = avg_salary

??保存再打開清洗后的數(shù)據(jù):

# 將清洗后的數(shù)據(jù)保存
df.to_csv('data\\' + city + '—' + position + '(已清洗)' + '.csv', index = False)

# 讀取清洗后的數(shù)據(jù)  
file = open('data\\' + city + '—' + position + '(已清洗)' + '.csv', 'rb')
df2 = pd.read_csv(file, encoding = 'utf-8')  

4 描述統(tǒng)計

# 描述統(tǒng)計  
print('工資描述:\n{}'.format(df2['月工資'].describe()))

工資描述:
count 450.000000
mean 12.885556
std 4.888166
min 1.250000
25% 9.750000
50% 12.000000
75% 16.000000
max 31.250000
Name: 月工資, dtype: float64

plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默認字體  
plt.rcParams['axes.unicode_minus'] = False # 解決保存圖像是負號'-'顯示為方塊的問題 
sns.set(font='SimHei')  # 解決 Seaborn 中文顯示問題
# 繪制頻率直方圖并保存  
sns.distplot(df2['月工資'], bins=15, kde=True, rug=True)
plt.title("工資直方圖")
plt.xlabel('工資 (千元)')   
plt.ylabel('頻數(shù)')      
plt.savefig('output\histogram.jpg')  
plt.show();
histogram.jpg
count = df2['區(qū)域'].value_counts()

# 繪制餅圖并保存  
fig = plt.figure()
ax = fig.add_subplot(111)
ax.pie(count, labels = count.keys(), labeldistance=1.4, autopct='%2.1f%%')  
plt.axis('equal')  # 使餅圖為正圓形  
plt.legend(loc='upper left', bbox_to_anchor=(-0.1, 1))  
plt.savefig('output\pie_chart.jpg')  
plt.show()  
pie_chart.jpg
# 繪制詞云,將職位福利中的字符串匯總  
text = ''  
for line in df2['職位福利']:  
    text += line  
# 使用jieba模塊將字符串分割為單詞列表      
cut_text = ' '.join(jieba.cut(text))  
color_mask = imread('img\jobs.jpg')  #設(shè)置背景圖  
cloud = WordCloud(  
    font_path = 'fonts\FZBYSK.ttf',   
    background_color = 'white',  
    mask = color_mask,  
    max_words = 1000,  
    max_font_size = 100          
)  

word_cloud = cloud.generate(cut_text)  

# 保存詞云圖片  
word_cloud.to_file('output\word_cloud.jpg')  
plt.imshow(word_cloud)  
plt.axis('off')  
plt.show()

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\gaiusyao\AppData\Local\Temp\jieba.cache
Loading model cost 0.796 seconds.
Prefix dict has been built succesfully.

word_cloud.jpg

5 實證統(tǒng)計

# 實證統(tǒng)計,將學(xué)歷不限的職位要求認定為最低學(xué)歷:大專  
df['學(xué)歷要求'] = df['學(xué)歷要求'].replace('不限', '大專')  
# 學(xué)歷分為大專\本科\碩士,將它們設(shè)定為虛擬變量  
dummy_edu = pd.get_dummies(df['學(xué)歷要求'], prefix = '學(xué)歷') 
# 構(gòu)建回歸數(shù)組  
df_with_dummy = pd.concat([df['月工資'], df['經(jīng)驗要求'], dummy_edu], axis = 1)
# 建立多元回歸模型  
y = df_with_dummy['月工資']  
X = df_with_dummy[['經(jīng)驗要求','學(xué)歷_大專','學(xué)歷_本科','學(xué)歷_碩士']]  
X = sm.add_constant(X)   
model = sm.OLS(y, X.astype(float))  
results = model.fit()  
print('回歸方程的參數(shù):\n{}\n'.format(results.params))  
print('回歸結(jié)果:\n{}'.format(results.summary()))

回歸方程的參數(shù):
const 7.302655
經(jīng)驗要求 1.192419
學(xué)歷大專 0.159035
學(xué)歷
本科 2.069740
學(xué)歷_碩士 5.073880
dtype: float64

回歸結(jié)果:
OLS Regression Results
==============================================================================
Dep. Variable: 月工資 R-squared: 0.240
Model: OLS Adj. R-squared: 0.235
Method: Least Squares F-statistic: 46.89
Date: Fri, 29 Jun 2018 Prob (F-statistic): 2.35e-26
Time: 20:57:54 Log-Likelihood: -1290.4
No. Observations: 450 AIC: 2589.
Df Residuals: 446 BIC: 2605.
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 7.3027 0.569 12.824 0.000 6.184 8.422
經(jīng)驗要求 1.1924 0.116 10.321 0.000 0.965 1.419
學(xué)歷大專 0.1590 0.567 0.281 0.779 -0.954 1.272
學(xué)歷
本科 2.0697 0.532 3.891 0.000 1.024 3.115
學(xué)歷_碩士 5.0739 1.444 3.515 0.000 2.237 7.911
==============================================================================
Omnibus: 97.185 Durbin-Watson: 2.145
Prob(Omnibus): 0.000 Jarque-Bera (JB): 195.765
Skew: 1.166 Prob(JB): 3.09e-43
Kurtosis: 5.236 Cond. No. 1.23e+16
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The smallest eigenvalue is 4.61e-29. This might indicate that there are
strong multicollinearity problems or that the design matrix is singular.

6 小結(jié)

??這是一次很簡單的爬蟲和數(shù)據(jù)分析練習(xí)储藐,最后回歸分析的結(jié)果不能令人滿意,這與產(chǎn)品經(jīng)理這一職位較注重工作經(jīng)驗而非學(xué)歷有關(guān)嘶是。
??但僅有職位的工作年限要求钙勃,并不能真正體現(xiàn)企業(yè)對于產(chǎn)品經(jīng)理的需求,企業(yè)對產(chǎn)品經(jīng)理更準確更詳細的要求應(yīng)在職位描述和工作要求這些文本數(shù)據(jù)中聂喇。其實不僅是產(chǎn)品經(jīng)理辖源,絕大部分的職位,僅憑結(jié)構(gòu)化的數(shù)據(jù)授帕,是不能很好地把握企業(yè)對于人才的真正需求同木。結(jié)構(gòu)化數(shù)據(jù)僅是水面上的冰山,水面下的文本數(shù)據(jù)則是一個更大的寶礦跛十,需要我們對其進行挖掘彤路。
??如需完善這次的練習(xí)項目,除了進一步完善爬蟲和統(tǒng)計分析以外芥映,如何進行文本挖掘洲尊,也是一個重要的方向。另外奈偏,定時爬取職位數(shù)據(jù)坞嘀,對數(shù)據(jù)集進行增刪和更新,并生成自動化報告惊来,也將是一個有趣的嘗試丽涩。

源代碼傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裁蚁,隨后出現(xiàn)的幾起案子矢渊,更是在濱河造成了極大的恐慌,老刑警劉巖枉证,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矮男,死亡現(xiàn)場離奇詭異,居然都是意外死亡室谚,警方通過查閱死者的電腦和手機毡鉴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秒赤,“玉大人猪瞬,你說我怎么就攤上這事癌蚁√” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵城瞎,是天一觀的道長崎弃。 經(jīng)常有香客問我甘晤,道長,這世上最難降的妖魔是什么饲做? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任线婚,我火速辦了婚禮,結(jié)果婚禮上盆均,老公的妹妹穿的比我還像新娘塞弊。我一直安慰自己,他們只是感情好泪姨,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布游沿。 她就那樣靜靜地躺著,像睡著了一般肮砾。 火紅的嫁衣襯著肌膚如雪诀黍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天仗处,我揣著相機與錄音眯勾,去河邊找鬼。 笑死婆誓,一個胖子當(dāng)著我的面吹牛吃环,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洋幻,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼郁轻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了文留?” 一聲冷哼從身側(cè)響起好唯,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厂庇,沒想到半個月后渠啊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡权旷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年替蛉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拄氯。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡躲查,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出译柏,到底是詐尸還是另有隱情镣煮,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布鄙麦,位于F島的核電站典唇,受9級特大地震影響镊折,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜介衔,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一恨胚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炎咖,春花似錦赃泡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绸栅,卻和暖如春级野,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粹胯。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工勺阐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矛双。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓渊抽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親议忽。 傳聞我的和親對象是個殘疾皇子懒闷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容