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官方文檔。
在對數據進行存儲之前,要清楚整個過程數據的流向,大致如下:
- 在item.py中定義要爬取的欄位
- 在spiders中爬取所需欄位
- 啟用一個item_pipeline組件(在settings.py的ITEM_PIPELINES配置中),數據被傳輸到pipeline(因為涉及到多個spider,使用該方法需要來回切換,所以參考了如下的方法為每一個spider設置自己的pipeline)
- 通過peewee建立與資料庫的連接(peewee連接資料庫)
- 將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)