R語言爬蟲實戰——網易雲課堂數據分析課程板塊數據爬取
R語言的爬蟲生態雖然與Python相比要弱小很多,but,如果你真的想要用R干一些有趣的事情,那麼R語言目前所具有的的網路爬取工具也能給你帶來很多方便。
今天借著中秋節的興緻,用網易雲課堂
全部課程>編程開發>人工智慧與大數據>數據分析
模塊的課程作為實戰對象,來給大家演練一下如何使用R語言httr包實現非同步載入和POST 表單提交以及cookies登入。
直接使用json或者其他格式的表單返回值,避免苦逼的的書寫大量正則表達式以及讓人眼花繚亂的 CSS表達式、Xath路徑表達式。這應該是每一個爬蟲練習者都應該謹記的事情。
沒錯,非同步載入的網頁大多通過返回json字元串的形式來獲取數據,它的難點在於請求的提交以及表單體構建、json字元串處理和最煩人的null空值剔除與替換。
本文使用到的技術是哈德利.威科姆大神的又一新作——網路數據爬取利器:httr。
library("httr") library("dplyr") library("jsonlite")library("curl")library("magrittr")library("rlist")library("pipeR")library("plyr")
網易雲課堂的網頁使用POST請求提交的非同步載入,在不久前我曾用Python演示過一次,今天換成R重塑一遍流程,你也可以參照這個代碼自己照葫蘆畫瓢。
網易雲課堂Excel課程爬蟲思路
首先我們需要做的事情是確認它的網頁構架:
打開F12鍵,定位到XHR,尋找以.josn結尾的請求文件。當你在它的右側打開對應Preview菜單,可以看到它的json數據源並且,有大量很整齊的課程信息的時候,差不多就找對了。
第二步:獲取請求信息:
定位到Headers,主要關注四大模塊:
General裡面的Request URL、Request Method、Status Code
Response Headers裡面的Content-Type
Request Headers 裡面的 Accept、Content-Type、Cookie、Referer、User-Agent以及最後Request Paylond裡面的所有參數表General裡面的url和post方法即是即決定訪問的資源對象和使用的技術手段。
Response Headers裡面的Content-Type決定著你獲得的數據以什麼樣的編碼格式返回。Request Headers 裡面的 Accept、Content-Type、Cookie、Referer、User-Agent等是你客戶端的瀏覽器信息,其中Cookie是你瀏覽器登錄後緩存在本地的登錄狀態信息,使用Cookie登入可以避免爬蟲程序被頻繁拒絕。(雖然網易雲課堂的課程信息沒有強制要求登錄才能查看)。Request Paylond信息最為關鍵,是POST提交請求必備的定位信息,因為瀏覽器的課程頁有很多頁信息,但是實際上他們訪問同一個地址(就是General裡面的url),而真正起到切換頁面的就是這個Request Paylond裡面的表單信息。以下是我從Chrome後台獲取的所有信息:
請求方式:POST
url<-"http://study.163.com/p/search/studycourse.json"
請求頭:
Accept:application/jsonAccept-Encoding:gzip, deflateAccept-Language:zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4Connection:keep-aliveContent-Length:148Content-Type:application/jsonCookie:請鍵入你自己的Cookies(我的賬號里還有很多付費課程呢,不能隨便賣~_~)edu-script-token:538144961c6343e88841c686a6ffb7b2Host:study.163.comOrigin:http://study.163.comReferer:http://study.163.com/category/dataanalysisUser-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36
請求頭參數:(JSON)
pageIndex 1pageSize 50relativeOffset 0frontCategoryId 400000000709002searchTimeType -1orderType 0priceType -1activityId 0
前五頁和最後一頁的Request Paylond信息(可以看到其中前四個參數最為關鍵,剩餘的可以不要)
{"pageIndex":1,"pageSize":50,"relativeOffset":0,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}{"pageIndex":2,"pageSize":50,"relativeOffset":50,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}{"pageIndex":3,"pageSize":50,"relativeOffset":100,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}{"pageIndex":4,"pageSize":50,"relativeOffset":150,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}{"pageIndex":5,"pageSize":50,"relativeOffset":200,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}……{"pageIndex":12,"pageSize":50,"relativeOffset":550,"frontCategoryId":"400000000709002","searchTimeType":-1,"orderType":0,"priceType":-1,"activityId":0}
第四步:構造請求提交信息:
Cookie="請鍵入個人的網易雲課堂Cookies"
構造瀏覽器報頭信息:
#構造瀏覽器報頭信息:headers <- c("Accept"="application/json", "Content-Type"="application/json", "User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", "edu-script-token"= "3e44b18e2cea46b6890d1cf92c1ad606", "Referer"="http://study.163.com/category/dataanalysis", "Connection"="keep-alive", "Cookie"=Cookie )構造請求頭參數信息:(這裡只接受list)payload<-list( "pageIndex"=1, "pageSize"=50, "relativeOffset"=0, "frontCategoryId"="400000000709002" )
構造url:
url<-"http://study.163.com/p/search/studycourse.json"
第五步:單步執行:
r <- POST(url,add_headers(.headers =headers),body =payload, encode="json",verbose())
從返回信息上我們可以看到提交成功,畢竟已經成功返回有效內容。
myresult1 <-r %>% content() %>%`[[`(3) %>% `[[`(2) %>% toJSON() %>% fromJSON(simplifyDataFrame=TRUE)
可以看到我們想要的內容存放在r %>% content()返回值的第三個list(result)內的第二個list中,長度為50,寬度為27,剛好就是我們在後台看到的課程信息。如果你不記得`[[`(3)的用法,記得看前幾篇的推送,它與extract函數相同,用於提取指定list對象。
如何使用管道操作符優雅的書寫R語言代碼
這是所有課程信息欄位名稱,我們無需要這麼多,僅挑選其中必要到的即可。
[1] "productId" "courseId" "productName" "productType" "startTime" "endTime" [7] "description" "provider" "score" "scoreLevel" "learnerCount" "imgUrl"[13] "bigImgUrl" "lectorName" "originalPrice" "discountPrice" "discountRate" "forumTagLector"[19] "tagLectorTime" "schoolShortName" "tagIap" "gmtModified" "displayType" "courseCardProps"[25] "published" "activityIds" "isPromStatus" usefulname<-c("productId","courseId","productName","lectorName","provider","score","scoreLevel","learnerCount","originalPrice","discountPrice","discountRate","description")myresult1<-myresult1 %>% select(usefulname)
第六步:書寫完整的循環獲取全部課程數據:
myfullresult<-list()for (i in 1:12){ payload[["pageIndex"]]=i payload[["relativeOffset"]]=payload[["relativeOffset"]] %>% `+`(50*(i-1)) web <- POST(url,add_headers(.headers =headers),body =payload,encode="json",verbose()) myresult<-web %>% content() %>% `[[`(3) %>% `[[`(2) myfullresult<-c(myfullresult,myresult)}
以上獲取的是個巨大的列表,我們需要將其轉換為數據框,並提取出我們需要的列。
mydata<-do.call(rbind,myfullresult) %>% as.data.frame() %>% select(usefulname)
還有一個問題,因為mydata整體是數據框,但是單個變數仍然是lsit(原因是原始信息中出現大量的NULL值),我們需要將所有NULL替換為NA,方可對mydata的個列進行向量化。
替換NULL值
for (j in 1:length(mydata)){ for (i in 1:nrow(mydata)){ if(is.null(mydata[i,j][[1]])){ mydata[i,j][[1]]=NA } }}
將所有list列轉為向量:
for (i in usefulname){mydata[[i]]<-mydata[[i]] %>% unlist()}
去重:
mydata<-unique(mydata)
保存:
write.csv(mydata, file ="D:/R/File/yunketang_datafenxi.csv")
預覽:
其實多了解一些方法,在很多時候避免走彎路,CSS表達式和Xpath路徑表達式甚至正則表達式的門檻都很高,單是搞明白一種就已經很不容易,融會貫通就更難了。
但是多了解一些捷徑和方法,你完全可以規避掉這些,我們的目標是獲取數據,沒有人關心你用的什麼方法,學會靈活運用才是關鍵。
往期案例數據請移步本人GitHub:https://github.com/ljtyduyu/DataWarehouse/tree/master/File
在線課程請點擊文末原文鏈接:
Hellobi Live | 9月12日 R語言可視化在商務場景中的應用
推薦閱讀: