Python | 爬蟲-數據分析實戰Ⅰ

「Talk is cheap,Show me the code.」翻譯為中文是「廢話少說,放碼過來。」我覺得可謂信達雅。

在編程之路上,實踐的重要性無可比擬。這也是很多同學感覺學了很多,但還是不會寫代碼的原因;也是很多有意轉行的人士,自學了大半年,仍不見起色的緣故。

leoxin在知識星球發起一項活動:目標是爬取拉鉤網的招聘信息以及鏈家的房產信息,然後對數據進行清洗和存儲,並分析其數據下的價值,最後用可視化的形式表現出來。

我覺得難度適中,循序漸進,對於不同身份角色的學習人群都大有裨益。

下面我來寫一寫在第一階段的一些學習操作總結和感受。

爬蟲

構思/準備部分

首先打開

找工作-互聯網招聘求職網-拉勾網?

www.lagou.com圖標

我初步選擇的是Java-上海

Step1

打開瀏覽器開發者工具,觀察Network部分的內容,首先點進Doc部分,看看伺服器給我們返回了哪些文本內容。

在Preview預覽中,我們可以看到,大部分都是一些公共視圖框架和公共JS代碼,沒有核心數據。

那麼這時我們就應該猜測,網站可能是首先渲染一個公共框架,然後再通過Ajax發送請求去獲得數據,再在頁面上顯示獲取的數據。

Step2

通過Step1的思考和猜測,大致確定了數據是非同步獲取的。做過一點web的應該都想得到這一點,因為即使是反爬,也要按照基本法啊!應該不會使用多麼多麼匪夷所思的黑科技(如果真的出現了,那是我太菜了的原因(っ °Д °;)っ)

這時點開XHR按鈕,XHR全稱XMLHttpRequest,有什麼作用呢?Ajax通過XMLHttpRequest對象發出HTTP請求,得到伺服器返回的數據。

通過預覽我們可以發現,我們需要的數據都出現在positionAjax請求下返回的數據中,參見content-positionResult-result中。

那麼該如何偽造請求?

點進Headers,首先發現 RequestMethod的值是 POST,確定了我們提交請求的方式。然後看看 RequestHeaders中的欄位。

發現有好多條。

  1. Accept:application/json, text/javascript, */*; q=0.01
  2. Accept-Encoding:gzip, deflate, br
  3. Accept-Language:zh-CN,zh;q=0.9
  4. Connection:keep-alive
  5. Content-Length:23
  6. Content-Type:application/x-www-form-urlencoded; charset=UTF-8
  7. Cookie:XXX
  8. Host:www.lagou.com
  9. Origin:https://www.lagou.com
  10. Referer:https://www.lagou.com/jobs/list_Java?px=default&city=%E4%B8%8A%E6%B5%B7
  11. User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
  12. X-Anit-Forge-Code:0
  13. X-Anit-Forge-Token:None
  14. X-Requested-With:XMLHttpRequest

通過篩選,我們選取其中有用的幾條,構建自己的請求頭。(那麼怎麼知道哪些是有用的呢?首先篩除語言,編碼之類的,這些的影響一般都是比較小的;接著在剩下的欄位中進行嘗試,等以後有經驗了,自然能準確選取有價值的欄位)

由於是POST的請求方式,那麼勢必會向伺服器提交一些數據,可以看到表單信息:

  1. first:true
  2. pn:1
  3. kd:Java

實現/代碼部分

Step1

在進行上述分析後,基本已經準備得差不多了。這時可以先簡單構建一下我們的Proxy類。

  1. class Proxy():
  2. def __init__(self):
  3. self.MAX=5 #最大嗅探次數
  4. self.headers={
  5. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
  6. "Referer":"https://www.lagou.com/jobs/list_Java?px=default&city=%E4%B8%8A%E6%B5%B7",
  7. "X-Anit-Forge-Code":"0",
  8. "X-Anit-Forge-Token":"None",
  9. "X-Requested-With":"XMLHttpRequest"
  10. }
  11. def getPage(self,url,data):
  12. FAILTIME=0 #訪問失敗次數
  13. try:
  14. result=requests.post(url,headers=self.headers,data=data)
  15. result.encoding = "utf-8"
  16. return result
  17. except:
  18. FAILTIME+=1
  19. if FAILTIME==self.MAX:
  20. print("訪問錯誤")
  21. return

上文中提到,發現Ajaxposition返回的content-positionResult-result數據,數據格式是一個數組裡有15條數據,每條數據的格式是一個字典,具體如下:

  1. adWord:9
  2. appShow:0
  3. approve:1
  4. businessZones:["唐鎮", "唐鎮", "曹路", "曹路"]
  5. city:"上海"
  6. companyFullName:"招商銀行股份有限公司信用卡中心"
  7. companyId:6796
  8. companyLabelList:["金融科技銀行", "技術創新驅動", "奮鬥獨立改變", "一年兩次調薪"]
  9. companyLogo:"i/image2/M00/25/D7/CgoB5lodmL2AJHxrAABieRjcJjU514.png"
  10. companyShortName:"招商銀行信用卡中心"
  11. companySize:"2000人以上"
  12. createTime:"2018-03-09 09:14:30"
  13. deliver:0
  14. district:"浦東新區"
  15. education:"本科"
  16. explain:null
  17. financeStage:"上市公司"
  18. firstType:"開發/測試/運維類"
  19. formatCreateTime:"09:14發布"
  20. gradeDescription:null
  21. hitags:null
  22. imState:"today"
  23. industryField:"移動互聯網,金融"
  24. industryLables:[]
  25. isSchoolJob:0
  26. jobNature:"全職"
  27. lastLogin:1520581074000
  28. latitude:"31.247248"
  29. linestaion:null
  30. longitude:"121.673868"
  31. pcShow:0
  32. plus:null
  33. positionAdvantage:"五險一金,職位晉陞,各類補貼"
  34. positionId:2762378
  35. positionLables:["項目管理", "j2ee", "架構"]
  36. positionName:"Java技術經理"
  37. promotionScoreExplain:null
  38. publisherId:73936
  39. resumeProcessDay:1
  40. resumeProcessRate:100
  41. salary:"30k-50k"
  42. score:0
  43. secondType:"管理崗"
  44. stationname:null
  45. subwayline:null
  46. workYear:"5-10年"

可見返回了大量的信息,那麼我們如何去獲得這些數據?此時可以寫一個簡單的Job類:

  1. class Job:
  2. def __init__(self):
  3. self.datalist=[]
  4. def getJob(self,url,data):
  5. p=Proxy()
  6. result=p.getPage(url,data)
  7. result.encoding = "utf-8"
  8. result_dict=result.json()
  9. try:
  10. job_info = result_dict[content][positionResult][result]
  11. for info in job_info:
  12. print(info)
  13. return job_info
  14. except:
  15. print("發生解析錯誤")

使用getJob方法獲取的是所有的信息,這其實是不必要的,應該也有所選擇,否則將給自己帶來壓力,對於後續步驟也將帶來不便。

Step2

此時,一個簡單的爬蟲已經編寫得差不多了,我們可以進行測試一下。

編寫主函數

  1. if __name__ == __main__:
  2. url="https://www.lagou.com/jobs/positionAjax.json?px=default&city=%E4%B8%8A%E6%B5%B7&needAddtionalResult=false&isSchoolJob=0"
  3. job = Job()
  4. all_page_info=[]
  5. for x in range(1,31):
  6. data = {
  7. "first": "false",
  8. "pn": x,
  9. "kd": "Java"
  10. }
  11. current_page_info=job.getJob(url,data)
  12. all_page_info.extend(current_page_info)
  13. print("第%d頁已經爬取成功"%x)
  14. time.sleep(5)

可以看到控制台顯示:

總結

到這裡,一個簡單的爬蟲已經編寫完畢了,數據以json格式返回,似乎已經大功告成。而事實是,對於為後面的數據分析做準備工作還差得遠,對於爬取海量數據,下面有幾點思考。

  • 拉鉤網對於同一ip的大量請求行為肯定會進行封禁,所以需要準備代理池。
  • 為了實現高自動化,需要對一系列可能出現的異常情況進行處理,斷點處理,確保程序不掛。
  • 為了提高效率,加入多線程。
  • 數據持久化,在持久化之前需要先進行清洗。
  • ......

針對以上問題,需要進一步學習。

公眾號:「果核里的圖靈」

一條小白的探索之路


推薦閱讀:

怎樣用五十行Python代碼自造比特幣?
P 站非會員查看人氣作品
關於 "ImportError: cannot import name cbook" 的解決
Pipeline語法支持,還是flowpython

TAG:Python | python爬蟲 | 數據分析 |