99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁精彩閱讀用R語言做網(wǎng)頁爬蟲和文本分析
用R語言做網(wǎng)頁爬蟲和文本分析
2017-02-24
收藏

R語言做網(wǎng)頁爬蟲和文本分析

受到這篇情感分析的文章和這篇網(wǎng)頁爬蟲指南的雙重啟發(fā),我決定嘗試抓取并分析 Goodreads 網(wǎng)站的書評數(shù)據(jù)。這個項目將會呈現(xiàn)一個從數(shù)據(jù)收集到機器學(xué)習(xí)建模分析的完整案例,我在中途犯下的錯誤也會一并呈現(xiàn)。本文將以5本流行的愛情故事書的評論為研究對象,我很自覺地選了同類型的書,使得評論具有可比性。這五本書也足夠暢銷,我們可以輕松獲取上千條評論,如果你不喜歡愛情故事,你也可以選擇其他類型的書做研究。
為了使這篇文章更易讀,我把它分成了三個部分:
Part 1: 網(wǎng)頁抓取
Part 2: 探索性數(shù)據(jù)分析和情感分析
Part 3: 基于機器學(xué)習(xí)的預(yù)測分析
這篇文章更新到了Part 1,后續(xù)部分會持續(xù)更新。
Part 1 網(wǎng)頁抓取
Goodreads上的評論很容易抓取,在每條評論左側(cè)都有一個非本文類型的排名變量。然而評論頁面的切換是通過一個javascript按鈕而不是html鏈接來實現(xiàn)的,處理起來有一點難度。不過好在這個問題有一個簡單有效的解決方法,只要使用RSelenium包就可以了,點 這里 可以閱讀該包的小品文。

起步
讓我們先加載好要用的包并定義幾個變量
library(data.table)   # 為了rbindlist函數(shù)
library(dplyr)        # 為了數(shù)據(jù)整理
library(magrittr)     # 為了管道操作符 %>%
library(rvest)        # 為了read_html函數(shù)
library(RSelenium)    # 為了使用JavaScript進行網(wǎng)頁抓取

url <- "https://www.goodreads.com/book/show/18619684-the-time-traveler-s-wife#other_reviews"
book.title <- "The time traveler's wife"
output.filename <- "GR_TimeTravelersWife.csv"
請注意,對每本書而言我需要改變上述變量的值并重新運行腳本。如果你覺得麻煩,可以用代碼自動實現(xiàn)這個過程,但此處我就采取手動的做法。這么做也可以避免Goodreads'網(wǎng)站服務(wù)器過載。

讓我們啟動RSelenium服務(wù)器,利用Firefox瀏覽器可能會有些問題,為此我重新安裝了一個比較舊的版本

startServer()
remDr <- remoteDriver(browserName = "firefox", port = 4444) # instantiate remote driver to connect to Selenium Server
remDr$open() # 打開瀏覽器
remDr$navigate(url)
這些指令會打開瀏覽器并轉(zhuǎn)向你制定好的url,之后我們需要建立一個數(shù)據(jù)框,方便后續(xù)數(shù)據(jù)的操作。
global.df <- data.frame(book=character(),
                        reviewer = character(),
                        rating = character(),
                        review = character(),
                        stringsAsFactors = F)
現(xiàn)在萬事俱備,可以開始網(wǎng)頁抓取了。
網(wǎng)頁抓取流程
為了提取我們需要的內(nèi)容,對于每本書,我們將掃描其100頁的評論。這里我去掉了循環(huán),只掃描一頁的內(nèi)容,并對代碼的工作原理逐行解釋。

首先,我們需要定義書評在頁面中的位置。使用SelectorGadget就能完成這一步驟,利用Chrome的一個拓展能幫助你識別CSS selector。只要找到了正確的CSS selector(這里是#bookReviews.stacked),將其傳遞給RSelenium服務(wù)器的findElements函數(shù)就可以了。

reviews <- remDr$findElements("css selector", "#bookReviews .stacked")
我們把書評的html代碼先提取出來,然后再分離其中的內(nèi)容。
reviews.html <- lapply(reviews, function(x){x$getElementAttribute("outerHTML")[[1]]})
reviews.list <- lapply(reviews.html, function(x){read_html(x) %>% html_text()} )
reviews.text <- unlist(reviews.list)
現(xiàn)在我們已經(jīng)用list的格式保存了評論,然而其中依舊混雜著很多無關(guān)內(nèi)容,我們需要利用正則表達式(regex)來清洗數(shù)據(jù)。

利用正則表達式清洗數(shù)據(jù)
依照我的文本分析經(jīng)驗,正則表達式既是天使也是魔鬼。通過它你可以用一行短短的命令就把字符串中所有的非字母元素移除,可它本身也是一門晦澀難懂的語言,使你在重讀自己的代碼時會倍感艱辛。所以如果你能讀懂下面的代碼做了些什么,我會倍感欣慰。

# 移除字母和符號外的元素
reviews.text2 <- gsub("[^A-Za-z\\-]|\\.+", " ", reviews.text)
# 移除換行符和多余的空格
reviews.clean <- gsub("\n|[ \t]+", " ", reviews.text2)
關(guān)于正則表達式,下面幾個網(wǎng)址很有用:
http://www.regular-expressions.info/
http://stat545.com/block022_regular-expression.html
https://stat.ethz.ch/R-manual/R-devel/library/base/html/regex.html
用表格格式存儲評論數(shù)據(jù)
現(xiàn)在我們已經(jīng)得到了很干凈的評論數(shù)據(jù),然而由于html暗含的數(shù)據(jù)結(jié)構(gòu),我們會遇到這樣的問題:對每一條評論,評論者的姓名和評分會存在同一個字符串里,而評論內(nèi)容存在后一個字符串中。此外,預(yù)覽評論系統(tǒng)會使得每個評論的開頭在字符串中重復(fù)出現(xiàn)兩次。我們需要對這些做做處理,再次使用正則表達式,我們將得到表格格式的數(shù)據(jù)。

我們先數(shù)數(shù)一共有多少條評論(即字符串?dāng)?shù)量的一半),然后建立一個臨時數(shù)據(jù)框來存儲數(shù)據(jù)。
n <- floor(length(reviews)/2)
reviews.df <- data.frame(book = character(n),
                         reviewer = character(n),
                         rating = character(n),
                         review = character(n),
                         stringsAsFactors = F)
我們遍歷所有的字符串,逐評論地提取需要的內(nèi)容并存在數(shù)據(jù)框里。這里我們采用for循環(huán)實現(xiàn)遍歷,但如果是工業(yè)級應(yīng)用,你應(yīng)該更喜歡向量化處理。

下面的代碼可能有點難懂,我先來解釋下:
1. 第一部分,我先列舉了可能出現(xiàn)在評論人姓名和評分之間的一些表達式,再結(jié)合正則表達式來確定姓名的結(jié)束位置,以此提取姓名。

2. 第二部分,我列舉了可以出現(xiàn)在評分之后的表達式,有時這些表達式并不會出現(xiàn),因此我得把這一情況也考慮進去。通過這兩種方式,我們就可以提取評分了。

3. 第三部分,我把每個評論的開頭移除了,我會記錄50個字符重復(fù)出現(xiàn)的起始位置和結(jié)束位置。有時,評論可能篇幅較短還不到50字符,這里就用和第二部分相似的方法處理。

4. 最后,請注意這個循環(huán)的結(jié)構(gòu),我并沒有一一循環(huán)字符串,而是遍歷了評論,每個評論包含兩個字符串,因此用2*j和2*j-1索引。

for(j in 1:n){
  reviews.df$book[j] <- book.title

  # 提取評論人姓名
  auth.rat.sep <- regexpr(" rated it | marked it | added it ",
                          reviews.clean[2*j-1])
  reviews.df$reviewer[j] <- substr(reviews.clean[2*j-1], 5, auth.rat.sep-1)

  # 提取評分
  rat.end <- regexpr("· | Shelves| Recommend| review of another edition",
                     reviews.clean[2*j-1])
  if (rat.end==-1){rat.end <- nchar(reviews.clean[2*j-1])}
  reviews.df$rating[j] <- substr(reviews.clean[2*j-1], auth.rat.sep+10, rat.end-1)

  # 移除評論中重復(fù)的部分
  short.str <- substr(reviews.clean[2*j], 1, 50)
  rev.start <- unlist(gregexpr(short.str, reviews.clean[2*j]))[2]
  if (is.na(rev.start)){rev.start <- 1}
  rev.end <- regexpr("\\.+more|Blog", reviews.clean[2*j])
  if (rev.end==-1){rev.end <- nchar(reviews.clean[2*j])}
  reviews.df$review[j] <- substr(reviews.clean[2*j], rev.start, rev.end-1)
  }
現(xiàn)在我們的臨時數(shù)據(jù)框已經(jīng)填寫完畢,我們可以把它的內(nèi)容轉(zhuǎn)移到主數(shù)據(jù)框中了。
global.lst <- list(global.df, reviews.df)
global.df <- rbindlist(global.lst)
最后,我們需要告訴RSelenium點擊進入下一頁的按鈕,通過傳遞利用SelectorGadget定義CSS selector可以實現(xiàn)這個功能。同時,Relenium的效率比較低,可能在循環(huán)中不能及時響應(yīng),因此我們在每個循環(huán)的末尾讓R等待3秒。

NextPageButton <- remDr$findElement("css selector", ".next_page")
NextPageButton$clickElement()
Sys.sleep(3)
結(jié)束所有循環(huán)后,我們要把最終結(jié)果保存成一個文件。
write.csv(global.df,output.filename)
最終結(jié)果如下:

數(shù)據(jù)分析咨詢請掃描二維碼

若不方便掃碼,搜微信號:CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個參數(shù)驗證碼對象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺檢測極驗服務(wù)器是否宕機 new_captcha: data.new_captcha, // 用于宕機時表示是新驗證碼的宕機 product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說明請參見:http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計時完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }