BOSS直聘行業信息爬取與分析(一)

從11月開始,斷斷續續幾個月,BOSS直聘的行業信息爬取數據量已經初具規模,目前程序穩步運行,日增量在六七萬左右,最終將達到兩三百萬,數據爬取的工作也算是告一段落,下面對項目進行一個簡單的小結。

作為一個即將進入職場並認準大數據方向的研究生而言,沒有互聯網公司的實習經歷,只能通過項目經驗來為自己加分。初期並沒有想過能有如此大的規模,只是想通過爬取一些東西來鞏固python的學習,積累一些爬蟲的經驗,隨著項目一步步迭代,數據的爬取工作進行的十分順利,期間學習了大量的機器學習演算法,正好也能運用到爬取的數據上,對行業信息進行分析,也能幫助自己提前了解行業形式,一舉多得。

一、爬取內容的選擇

我們的目的是爬取該網站所有的招聘信息,由於招聘信息的網址組合方式為(主網址+城市url+行業url)這樣一種形式,所以事先要將城市和對應的url行業和對應的url信息分別爬取出來,最後採用排列組合的方式將網址挨個進行爬取。

  • 所有城市與對應區的信息

目前該網站支持的城市只有以下十幾個,其對應的區也一起爬取備用,在後面分析階段可能會細化到某些區。

  • 所有行業分類

該網站的行業信息涉及面非常廣,我們需要爬取某個行業及對應的一級分類、二級分類。

  • 招聘信息

這是BOSS直聘網站一條行業信息記錄,包括崗位名稱、薪資 、公司、城市、經驗、學歷、所屬行業、融資情況、公司規模、關鍵字、聯繫人名字、聯繫人職務、聯繫人圖片、發布時間這14個屬性值。

二、爬蟲框架的選取

scrapy作為一個爬蟲主流框架,上手非常容易,本人通過爬取豆瓣電影TOP250這個小例子對scrapy框架的使用有了一定的認識與理解,scrapy的安裝與項目創建可以參考scrapy入門教程。下面是BOSS直聘的一個scrapy項目,給出了相關文件的簡單介紹:

  • spiders:放置spider代碼的目錄,裡面可以有多個spider
  • items.py:是種簡單的容器,保存了爬取到的數據,提供了類似於字典的API以及用於聲明可用欄位的簡單語法
  • middlewares.py:主要是管理User-Agent和IP這一塊內容
  • pipelines.py:管道文件,爬取到的數據會傳輸到這裡
  • settings.py:設置文件

與最初創建的項目相比多了start和model兩個文件夾,其中的start文件夾通過cmdline設置了每個spider的啟動代碼,這樣就不需要每次都用scrapy crawl命令來啟動,更加方便;model文件夾中的mysql_db文件成功建立scrapy與資料庫的連接,部分代碼如下

三、爬蟲主體

通過(一)中我們知道該項目涉及到三個spider文件的編寫,在下文我們只選擇其中的招聘信息來進行介紹,另外兩個的方法類似。

(1)爬取內容的item定義

確定好在網頁中爬取的內容之後,需要在item.py文件中進行定義:

(2)spider文件編寫

spider文件的代碼編寫是最最重要和核心的部分,在進行數據提取前需要對網頁的構成、html以及xpath知識有一定的了解,相關內容可以在w3school網站上進行學習,推薦一個谷歌瀏覽器插件,XPath Helper,可以在網頁面及時發現xpath是否書寫正確,是一大神器。考慮到網頁的相似性,初期只爬取一頁的內容,然後在此基礎上進行翻頁爬取,具體方法後文會講到,spider文件部分代碼如下:

(3)異常情況處理

期間遇到很多異常情況,需要單獨處理,也是因為這些接觸到了python的捕獲異常以及異常處理機制,異常信息的獲取對於程序的調試非常重要,尤其是在代碼量比較大的情況下,可以有助於快速定位有錯誤程序語句的位置。在此給大家提個醒,有些網站的反爬手段做的十分巧妙,往往就在一個細小的標籤上,所以對html及xpath一定要有很清晰的認識與辨別力(Amazon的反爬就非常出名,有興趣的可以了解下,畢竟知己知彼才能百戰百勝嘛),為了大家少踩坑,部分羅列如下:

  • 存在某些招聘信息的融資情況(Financing)缺失,而CompanySize、Financing、 Industry這三個元素的信息在一個標籤里,故導致爬取信息出現紊亂

解決辦法:將Financing Industry這兩個單獨考慮,採用if判斷

  • 在翻頁的時候由於第二頁中個別招聘信息的PubTime標籤丟失,移植到其他標籤里,導致程序終止,無法爬去所有的

解決辦法:利用python的異常處理機制將PubTime異常的設置為其他可標識的東西,之後統一處理數據

  • KeyWord包含0到3個信息不等,要將這幾個用一個數組表示,如果空的話就輸出None

解決辦法:當KeyWord為空時,對應span里保存的是PubTime;當KeyWord不為空時,對應span里保存的是KeyWord值,此時PubTime保存在.//div[@class="job-time"]/span/text(),順帶解決了第二個問題

  • 在用第三個方法處理KeyWord時,關鍵字缺失的情況沒法按預期顯示「關鍵字未知」,原因是if語句沒有進入,「發布」是字元型,後面的文本信息是列表型,不能直接用in

解決辦法:將列表型轉化為字元型 ,.join(list)

  • 對於PubTime這一欄,有的是「發佈於某月某日」,有的是「發佈於今天」,有的是「發佈於昨天」

解決辦法:為了統一,利用時間日期函數將形式變為「2017-9-12」這種固定的格式,加入年份是考慮到跨年爬取帶來的誤區

(4)遞歸爬取

前面提到的翻頁爬取其實是一種遞歸爬取,涉及到scrapy spider的parse方法,理解起來比較難,可以返回兩種值:Item或者Request,通過Request可以實現遞歸抓取,主要有以下幾種情況:

  • 如果要抓取的數據在當前頁,可以直接解析返回item;

def parse(self, response):n item = MyItem()n item[a] = aaan item[b] = bbbn yield item n

  • 如果要抓取的數據在當前頁指向的頁面,則返回Request並指定parse_item作為callback;

def parse(self, response):n item = MyItem()n url = 當前頁指向的頁面n yield Request(url, callback=self.parse_item)nndef parse_item(self, response):n item[c] = cccn item[d] = dddn yield itemn

  • 如果要抓取的數據當前頁有一部分,指向的頁面有一部分(比如博客或論壇,當前頁有標題、摘要和url,詳情頁面有完整內容)這種情況需要用Request的meta參數把當前頁面解析到的數據傳到parse_item,後者繼續解析item剩下的數據

def parse(self, response):n item = MyItem()n item[a] = aaan item[b] = bbbn url = 當前頁指向的頁面n yield Request(url, meta={item: item} , callback=self.parse_item)nndef parse_item(self, response):n item = response.meta[item]n item[c] = cccn item[d] = dddn yield itemn

  • 抓完當前頁再抓其它頁面(比如下一頁),本項目就需要使用這種方法來進行遞歸爬取,可以返回Request,callback為parse。

next_url = response.xpath(//*[@id="main"]/div[3]/div[2]/div[2]/a[@ka="page-next"]/@href).extract()nif next_url:n next_url = https://www.zhipin.com + next_url[0]n yield Request(next_url)n

參考鏈接:scrapy遞歸抓取網頁數據

不斷的抓取下一個鏈接如何實現,items如何保存?

四、數據入庫

在spiders中爬取所需欄位後,如何保存數據成為亟待解決的問題?將數據保存在文件中是一個方法,但是數據量龐大時該方法並不是最佳選擇,通常會選擇保存在資料庫中,接下來將介紹一個python的ORM框架——Peewee,這屬於一款輕量級的框架,能有效建立scrapy與資料庫的連接,詳細內容可查看peewee官方文檔。

在對數據進行存儲之前,要清楚整個過程數據的流向,大致如下:

  1. 在item.py中定義要爬取的欄位
  2. 在spiders中爬取所需欄位
  3. 啟用一個item_pipeline組件(在settings.py的ITEM_PIPELINES配置中),數據被傳輸到pipeline(因為涉及到多個spider,使用該方法需要來回切換,所以參考了如下的方法為每一個spider設置自己的pipeline
  4. 通過peewee建立與資料庫的連接(peewee連接資料庫
  5. 將pipeline里的數據傳輸到資料庫

本文使用的是MySql資料庫,在入庫之前首先要設計好數據表,這裡需要將城市信息和行業信息分別存儲在兩個表中,在進行招聘信息網址組合時再從資料庫里分別提取,期間會用到在資料庫里查找、修改等操作的知識,可以參考資料庫增刪查改操作。

(1)表結構設計

  • 城市信息表

在設計城市信息表時,因為涉及到城市和對應區兩種類型數據,為了將兩者放在一個表中,特添加father_id和type兩個欄位來將兩者進行區分

  • 行業信息表

  • 招聘信息表

(2)去重

後期程序會一直運行,將存在大量重複的記錄,為了保證資料庫中數據的唯一性和有效性,在入庫的時候加入去重這個步驟:

  • 城市和區通過name和url可以唯一標識
  • 行業通過name和url可唯一標識
  • 職位信息因為涉及到的屬性較多,在去重時稍微複雜一點,通過post_name,salary,company,city,experience,education,contact_name,publish_time這些屬性生成一個唯一的MD5值,即可將一條記錄唯一標識,其中屬性的選擇根據個人需求進調整。

資料庫部分數據情況如下,招聘信息表太長分割成圖三和圖四:

圖一 城市信息表

圖二 行業信息表

圖三 招聘信息表

圖四 招聘信息表

五、scrapy防止IP被封

在進行大量爬取工作時,網站會識別出是爬蟲程序,然後封鎖IP,這種情況本人在初期遇到過很多次,當時採取了下面兩種scrapy自帶措施:

  • 設置下載等待時間/頻率

setting.py文件中的DOWNLOAD_DELAY設置在3以上

  • 禁止cookies

setting.py文件中的COOKIES_ENABLES = FALSE

結果表明效果並不明顯,當數據量稍微大點時,還是會被封IP(少量數據可以使用上述方法),於是繼續探索了更一般的解決方法,如下:

  • 使用User-Agent池

大多數情況下,網站都會根據我們的請求頭信息來區分你是不是一個爬蟲程序,如果一旦識別出這是一個爬蟲程序,很容易就會拒絕我們的請求,因此我們需要給我們的爬蟲手動添加請求頭信息,來模擬瀏覽器的行為,但是當我們需要大量的爬取某一個網站的時候,一直使用同一個User-Agent顯然也是不夠的,因此需要scrapy設置隨機User-Agent

  • 使用IP池

在scrapy中使用IP池的方法可以參考IP池設置,這需要我們去尋找大量可用的IP, 網上有一些免費的可以使用,比如碼農很忙代理IP搜索,米撲代理等等。

需要注意的是,不管使用User-Agent池還是IP池都是通過下載器中間件(Downloader Middleware)來實現的,因此需要在setting.py文件進行設置,如下:

因為使用IP池的方法有非常好的效果,故本項目沒有再繼續使用隨機User-Agent,若有需求大家可以自行選擇。

六、斷點爬取

以上五點完成後,程序已經可以正常運行,但是由於數據量非常龐大,粗略計算了一下筆記本的爬取周期大概為二十多天(考慮過租用阿里伺服器,但是目前對於數據的需求還不太大,不至於到那一步),而且由於網站時刻會有數據更新,程序需要多周期迭代。那麼問題來了,電腦不可能一天24小時開機,一個周期二十多天不間斷,如果程序突然中斷了,就需要重頭開始,從第一個網址再進行爬取,而排在前面的網址都是之前已經爬取過的,很費時不說,可能後面的網址一次也爬取不了。遇到這個問題,很自然的想法就是程序能從上次中斷的地方繼續爬,這就涉及到斷點爬取的知識,核心思想是記錄好上次已經爬的狀態,具體實現方法多種多樣。在本項目中,採用的方法是將每次爬取網址的城市id和行業id分別保存在兩個文件中,隨著程序不但的更新,下次再啟動程序時,直接讀取文件中的id,將之後的進行組合即可。

——————————————————————————————————————

以上就是本項目數據爬取部分的流程,用了一天的時間終於整理完了,涉及到的知識點非常多,都是本人在項目中親自踩過的坑,非常有借鑒意義,由於某些原因,代碼暫時不公開,有相關的問題可以隨時私信與我交流探討!

爭取能在過年前出一份相對完整的數據分析報告,幫助自己分析大數據方向的就業行情,找到一份理想的實習,大家有這方面的需求也可以私信告訴我,免費幫大家做一個簡單的分析!

未經許可,禁止轉載!!!

推薦閱讀:

跟繁瑣的命令行說拜拜!Gerapy分散式爬蟲管理框架來襲!
簡單爬蟲的通用步驟
Python3爬蟲(3)單網頁簡單爬取文字信息
第四章:動態網頁抓取 (解析真實地址 + selenium)

TAG:python爬虫 | scrapy | boss直聘 |