“Talk is cheap,Show me the code.”翻譯為中文是“廢話少說,放碼過來。”我覺得可謂信達雅。
在程式設計之路上,實踐的重要性無可比擬。這也是很多同學感覺學了很多,但還是不會寫程式碼的原因;也是很多有意轉行的人士,自學了大半年,仍不見起色的緣故。
leoxin在知識星球發起一項活動:目標是爬取拉鉤網的招聘資訊以及鏈家的房產資訊,然後對資料進行清洗和儲存,並分析其資料下的價值,最後用視覺化的形式表現出來。
我覺得難度適中,循序漸進,對於不同身份角色的學習人群都大有裨益。
下面我來寫一寫在第一階段的一些學習操作總結和感受。
爬蟲
構思/準備部分
首先開啟拉鉤網,我初步選擇的是Java-上海
Step1
開啟瀏覽器開發者工具,觀察Network部分的內容,首先點進Doc部分,看看伺服器給我們返回了哪些文字內容。
在Preview預覽中,我們可以看到,大部分都是一些公共檢視框架和公共JS程式碼,沒有核心資料。
那麼這時我們就應該猜測,網站可能是首先渲染一個公共框架,然後再通過Ajax傳送請求去獲得資料,再在頁面上顯示獲取的資料。
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
複製程式碼
實現/程式碼部分
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)
複製程式碼
可以看到控制檯顯示:
總結
到這裡,一個簡單的爬蟲已經編寫完畢了,資料以json格式返回,似乎已經大功告成。而事實是,對於為後面的資料分析做準備工作還差得遠,對於爬取海量資料,下面有幾點思考。
- 拉鉤網對於同一ip的大量請求行為肯定會進行封禁,所以需要準備代理池。
- 為了實現高自動化,需要對一系列可能出現的異常情況進行處理,斷點處理,確保程式不掛。
- 為了提高效率,加入多執行緒。
- 資料持久化,在持久化之前需要先進行清洗。
- ……
針對以上問題,需要進一步學習。