Python | 資料分析實戰Ⅰ

寒食君發表於2019-03-04

Linux之父Linus

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

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

leoxin在知識星球發起一項活動:目標是爬取拉鉤網的招聘資訊以及鏈家的房產資訊,然後對資料進行清洗和儲存,並分析其資料下的價值,最後用視覺化的形式表現出來。 我覺得難度適中,循序漸進,對於不同身份角色的學習人群都大有裨益。

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

爬蟲

構思/準備部分

首先開啟拉鉤網,我初步選擇的是Java-上海

Python | 資料分析實戰Ⅰ

Step1

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

在Preview預覽中,我們可以看到,大部分都是一些公共檢視框架和公共JS程式碼,沒有核心資料。

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

doc

Step2

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

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

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

那麼該如何偽造請求?

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

發現有好多條。

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

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

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

first:true
pn:1
kd:Java
複製程式碼

xhr
headers

實現/程式碼部分

Step1

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

class Proxy():
    def __init__(self):
        self.MAX=5 #最大嗅探次數
        self.headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
            "Referer":"https://www.lagou.com/jobs/list_Java?px=default&city=%E4%B8%8A%E6%B5%B7",
            "X-Anit-Forge-Code":"0",
            "X-Anit-Forge-Token":"None",
            "X-Requested-With":"XMLHttpRequest"
        }

    def getPage(self,url,data):
        FAILTIME=0 #訪問失敗次數
        try:
            result=requests.post(url,headers=self.headers,data=data)
            result.encoding = "utf-8"
            return result
        except:
            FAILTIME+=1
            if FAILTIME==self.MAX:
                print("訪問錯誤")
                return ''

複製程式碼

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

adWord:9
appShow:0
approve:1
businessZones:["唐鎮", "唐鎮", "曹路", "曹路"]
city:"上海"
companyFullName:"招商銀行股份有限公司信用卡中心"
companyId:6796
companyLabelList:["金融科技銀行", "技術創新驅動", "奮鬥獨立改變", "一年兩次調薪"]
companyLogo:"i/image2/M00/25/D7/CgoB5lodmL2AJHxrAABieRjcJjU514.png"
companyShortName:"招商銀行信用卡中心"
companySize:"2000人以上"
createTime:"2018-03-09 09:14:30"
deliver:0
district:"浦東新區"
education:"本科"
explain:null
financeStage:"上市公司"
firstType:"開發/測試/運維類"
formatCreateTime:"09:14釋出"
gradeDescription:null
hitags:null
imState:"today"
industryField:"移動網際網路,金融"
industryLables:[]
isSchoolJob:0
jobNature:"全職"
lastLogin:1520581074000
latitude:"31.247248"
linestaion:null
longitude:"121.673868"
pcShow:0
plus:null
positionAdvantage:"五險一金,職位晉升,各類補貼"
positionId:2762378
positionLables:["專案管理", "j2ee", "架構"]
positionName:"Java技術經理"
promotionScoreExplain:null
publisherId:73936
resumeProcessDay:1
resumeProcessRate:100
salary:"30k-50k"
score:0
secondType:"管理崗"
stationname:null
subwayline:null
workYear:"5-10年"

複製程式碼

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

class Job:
    def __init__(self):
        self.datalist=[]

    def getJob(self,url,data):
        p=Proxy()
        result=p.getPage(url,data)
        result.encoding = "utf-8"
        result_dict=result.json()
        try:
            job_info = result_dict['content']['positionResult']['result']
            for info in job_info:
                print(info)
            return job_info
        except:
            print("發生解析錯誤")
       
複製程式碼

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

Step2

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

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

可以看到控制檯顯示:

Python | 資料分析實戰Ⅰ

總結

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

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

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

掃一掃,關注公眾號

相關文章