【譯文】R語言網路爬蟲初學者指南(使用rvest包)

錢亦欣發表於2017-06-04

作者 SAURAV KAUSHIK

譯者 錢亦欣

引言

網上的資料和資訊無窮無盡,如今人人都用百度谷歌來作為獲取知識,瞭解新鮮事物的首要資訊源。所有的這些網上的資訊都是直接可得的,而為了滿足日益增長的資料需求,我堅信網路資料爬取已經是每個資料科學家的必備技能了。在本文的幫助下,你將會突破網路爬蟲的技術壁壘,實現從不會到會。

大部分網上呈現的資訊都是以非結構化的格式儲存(html)且不提供直接的下載連結,因此,我們需要學習一些知識和經驗來獲取這些資料。

本文我將帶你領略利用R做網路資料採集的全過程,通讀文章後你將掌握如何來使用因特網上各位資料的技能。

目錄

  1. 什麼是網路資料爬取
  2. 為什麼需要爬取資料
  3. 資料爬取方法
  4. 前提條件
  5. 使用R爬取網頁
  6. 分析從網頁爬取的資料

1. 什麼是網路資料爬取

網路爬蟲是講呈現在網頁上以非結構格式(html)儲存的資料轉化為結構化資料的技術,該技術非常簡單易用。

幾乎所有的主流程式語言都提供了網路資料爬取的實現方式,本文我們會用R來爬取IMDB上2016年最熱門電影的一些特徵。

我們將採集2016年度最熱門電影的若干特徵,同時我們也會遇到網頁程式碼不一致的問題並將其解決。這是在做網路爬蟲時最常遇到的問題之一。

如果你更喜歡用python變成,我建議你看這篇指南來學習如何用python做爬蟲。

2. 為什麼需要爬取資料

我確信你現在肯定在問“為什麼需要爬取資料”,正如前文所述,爬取網頁資料極有可能。(譯者注:原文如此,我沒看懂這個設問的邏輯)

為了提供一些使用的知識,我們將會爬取IMDB的資料,同時,利用爬蟲你還可以:

  • 爬取電影評分來構建推薦系統
  • 爬取維基百科等信源的文字作為訓練預料來構建深度學習模型以實現主體識別等功能
  • 爬取有標籤的影像(從Google,Flickr等網站)來訓練影像分類模型
  • 爬取社交媒體資料(Facebook 和 Twitter 等)做情感分析,觀點挖掘等
  • 爬取電商的使用者評論和反饋(從Amazon,Flipkart等)

3. 資料爬取方法

網路資料抓取的方式有很多,常用的有:

  • 人工複製貼上:這是採集資料的緩慢但有效的方式,相關的工作人員會自行分析並把資料複製到本地。
  • 文字模式匹配:另一種簡單有效的方法是利用程式語言中的正規表示式來匹配固定模式的文字,在這裡你可以學到關於正規表示式的更多內容。
  • 使用API:諸如Facebook,Twitter和Linkedin一類的許多網站都提供了公共或者私人的API,它們提供了標準化的程式碼供使用者請求規定格式的資料。
  • DOM解析:程式可以使用瀏覽器來獲取客戶端指令碼生成的動態內容。基於這些程式可以獲得的頁面來使用DOM樹來解析網頁也是可行的辦法,

我們會使用DOM解析的方式來獲取資料,並基於網頁的CSS選擇器來尋找含有所需資訊的網頁部分。但在開始之前,我們必須滿足一些前提條件。

4. 前提條件

利用R實現網路爬蟲的前提條件有兩大塊:

  • 要寫R語言爬蟲,你對R必須有一定了解。如果你還是個新手,我強烈建議參照這個學習路徑 來學習。本文將使用“Hadley Wickham(Hadley我愛你!!!)”開發的“rvest”包來實現爬蟲。你可以從這裡獲得這個包的文件。如果你沒有安裝這個包,請執行以下程式碼。

    install.packages('rvest')

  • 除此之外,HTML,CSS的相關知識也很重要。學習他們的有一個很好的資源。我見識過不少對HTML和CSS缺乏瞭解的資料科學家,因此我們將使用名為Selector Gadget的開源軟體來更高效地實現抓取。你可以在這裡下載這個工具包。請確保你的瀏覽器已經安裝了這個外掛(推薦用chrome瀏覽器),並且能正常使用。(譯者注:chrome中的css viewer 和 xpath helper 也是神器。) img

使用這個外掛你可以通過點選任一網頁中你需要的資料就能獲得相應的標籤。你也可以學習HTML和CSS的知識並且手動實現這一過程。而且,為了更深入地瞭解網路爬取這一藝術,我很推薦你學習下HTML和CSS來了解其背後的機理。

5. 使用R爬取網頁

現在讓我們開始爬取IMDB上2016年度最流行的100部故事片,你可以在這裡檢視相關資訊。

# 載入包
library('rvest')

# 指定要爬取的url
url <- 'http://www.imdb.com/search/title?    
count=100&release_date=2016,2016&title_type=feature'

# 從網頁讀取html程式碼
webpage <- read_html(url)

現在,讓我們爬取網頁上的這些資料:

  • Rank:從1到100,代表排名
  • Title:故事片的標題
  • Description:電影內容簡介
  • Runtime: 電影時長
  • Genre: 電影型別
  • Rating: IMDB提供的評級
  • Metascore: IMDB上該電影的評分
  • Votes: 電影的好評度
  • Gross_Earning_in_Mil: 電影總票房(百萬)
  • Director: 影片的總導演,如果有多位,取第一個
  • Actor: 影片的主演,如果有多位,取第一個

這是頁面的截圖

img

Step 1: 爬取的第一步是使用 selector gadget獲得排名的CSS選擇器。你可以點選瀏覽器中的外掛圖示並用游標點選排名的區域。

img

要確保所有的排名都被選擇了,你也可以再次點選選中區域來取消選擇,最終只有高亮的那些部分會被爬取。

Step 2: 一旦你已經選擇了正確的區域,你需要把在底部中心顯示的相應的CSS選擇器複製下來。

img

Step 3: 只要CSS選擇器包含排名,你就能用幾行簡單的程式碼來獲取所有的排名了:

# 用CSS選擇器獲取排名部分
rank_data_html <- html_nodes(webpage,'.text-primary')

# 把排名轉換為文字
rank_data <- html_text(rank_data_html)

# 檢查一下資料
head(rank_data)

[1] "1." "2." "3." "4." "5." "6."

Step 4: 獲取資料之後,請確保他們被你所需的格式儲存,我會把排名處理成數值型。

# 資料預處理:把排名轉換為數值型
rank_data<-as.numeric(rank_data)

# 再檢查一遍
head(rank_data)

[1] 1 2 3 4 5 6

Step 5: 現在你可以清空選擇部分並開始選擇電影標題了,你可以看見所有的標題都被選擇了,你依據個人需要做一些增刪。

img

Step 6: 正如從前,再次複製CSS選擇器並用下列程式碼爬取標題。

# 爬取標題
title_data_html <- html_nodes(webpage,'.lister-item-header a')

# 轉換為文字
title_data <- html_text(title_data_html)

# 檢查一下
head(title_data)

[1] "Sing"          "Moana"         "Moonlight"     "Hacksaw Ridge"
[5] "Passengers"    "Trolls"

Step 7: 下列程式碼會爬取剩餘的資料– Description, Runtime, Genre, Rating, Metascore, Votes, Gross_Earning_in_Mil , Director and Actor data.

# 爬取描述
description_data_html <- html_nodes(webpage,'.ratings-bar+ .text-muted')

# 轉為文字
description_data <- html_text(description_data_html)

# 檢查一下
head(description_data)

[1] "\nIn a city of humanoid animals, a hustling theater impresario's attempt to save his theater with a singing competition becomes grander than he anticipates even as its finalists' find that their lives will never be the same."

[2] "\nIn Ancient Polynesia, when a terrible curse incurred by the Demigod Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the Demigod to set things right."

[3] "\nA chronicle of the childhood, adolescence and burgeoning adulthood of a young, African-American, gay man growing up in a rough neighborhood of Miami."

[4] "\nWWII American Army Medic Desmond T. Doss, who served during the Battle of Okinawa, refuses to kill people, and becomes the first man in American history to receive the Medal of Honor without firing a shot."

[5] "\nA spacecraft traveling to a distant colony planet and transporting thousands of people has a malfunction in its sleep chambers. As a result, two passengers are awakened 90 years early."

[6] "\nAfter the Bergens invade Troll Village, Poppy, the happiest Troll ever born, and the curmudgeonly Branch set off on a journey to rescue her friends.

# 移除 '\n'
description_data<-gsub("\n","",description_data)

# 再檢查一下
head(description_data)

[1] "In a city of humanoid animals, a hustling theater impresario's attempt to save his theater with a singing competition becomes grander than he anticipates even as its finalists' find that their lives will never be the same."

[2] "In Ancient Polynesia, when a terrible curse incurred by the Demigod Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the Demigod to set things right."

[3] "A chronicle of the childhood, adolescence and burgeoning adulthood of a young, African-American, gay man growing up in a rough neighborhood of Miami."

[4] "WWII American Army Medic Desmond T. Doss, who served during the Battle of Okinawa, refuses to kill people, and becomes the first man in American history to receive the Medal of Honor without firing a shot."

[5] "A spacecraft traveling to a distant colony planet and transporting thousands of people has a malfunction in its sleep chambers. As a result, two passengers are awakened 90 years early."

[6] "After the Bergens invade Troll Village, Poppy, the happiest Troll ever born, and the curmudgeonly Branch set off on a journey to rescue her friends."

# 爬取runtime section
runtime_data_html <- html_nodes(webpage,'.text-muted .runtime')

# 轉為文字
runtime_data <- html_text(runtime_data_html)

# 檢查一下
head(runtime_data)

[1] "108 min" "107 min" "111 min" "139 min" "116 min" "92 min"

# 資料預處理: 去除“min”並把數字轉換為數值型

runtime_data <- gsub(" min","",runtime_data)
runtime_data <- as.numeric(runtime_data)

# 再檢查一下
head(rank_data)

[1] 1 2 3 4 5 6

# 爬取genre
genre_data_html <- html_nodes(webpage,'.genre')

# 轉為文字
genre_data <- html_text(genre_data_html)

# 檢查一下
head(genre_data)

[1] "\nAnimation, Comedy, Family "

[2] "\nAnimation, Adventure, Comedy "

[3] "\nDrama "

[4] "\nBiography, Drama, History "

[5] "\nAdventure, Drama, Romance "

[6] "\nAnimation, Adventure, Comedy "

# 去除“\n”
genre_data<-gsub("\n","",genre_data)

# 去除多餘空格
genre_data<-gsub(" ","",genre_data)

# 每部電影只保留第一種型別
genre_data<-gsub(",.*","",genre_data)

# 轉化為因子
genre_data<-as.factor(genre_data)

# 再檢查一下
head(genre_data)

[1] Animation Animation Drama     Biography Adventure Animation

10 Levels: Action Adventure Animation Biography Comedy Crime Drama ... Thriller

# 爬取IMDB rating
rating_data_html <- html_nodes(webpage,'.ratings-imdb-rating strong')

# 轉為文字
rating_data <- html_text(rating_data_html)

# 檢查一下
head(rating_data)

[1] "7.2" "7.7" "7.6" "8.2" "7.0" "6.5"

# 轉為數值型
rating_data<-as.numeric(rating_data)

# 再檢查一下
head(rating_data)

[1] 7.2 7.7 7.6 8.2 7.0 6.5

# 爬取votes section
votes_data_html <- html_nodes(webpage,'.sort-num_votes-visible span:nth-child(2)')

# 轉為文字
votes_data <- html_text(votes_data_html)

# 檢查一下
head(votes_data)

[1] "40,603"  "91,333"  "112,609" "177,229" "148,467" "32,497"

# 移除“,”
votes_data<-gsub(",", "", votes_data)

# 轉為數值型
votes_data<-as.numeric(votes_data)

# 再檢查一下
head(votes_data)

[1]  40603  91333 112609 177229 148467  32497

# 爬取directors section
directors_data_html <- html_nodes(webpage,'.text-muted+ p a:nth-child(1)')

# 轉為文字
directors_data <- html_text(directors_data_html)

# 檢查一下
head(directors_data)

[1] "Christophe Lourdelet" "Ron Clements"         "Barry Jenkins"
[4] "Mel Gibson"           "Morten Tyldum"        "Walt Dohrn"

# 轉為因子
directors_data<-as.factor(directors_data)

# 爬取actors section
actors_data_html <- html_nodes(webpage,'.lister-item-content .ghost+ a')

# 轉為文字
actors_data <- html_text(actors_data_html)

# 檢查一下
head(actors_data)

[1] "Matthew McConaughey" "Auli'i Cravalho"     "Mahershala Ali"
[4] "Andrew Garfield"     "Jennifer Lawrence"   "Anna Kendrick"

# 轉為因子
actors_data<-as.factor(actors_data)

我時爬Metascore時遇到問題,我希望你能仔細看看。

# 爬取metascore section
metascore_data_html <- html_nodes(webpage,'.metascore')

# 轉為文字
metascore_data <- html_text(metascore_data_html)

# 檢查一下
head(metascore_data)

[1] "59        " "81        " "99        " "71        " "41        "
[6] "56        "

# 去除多餘空格
metascore_data<-gsub(" ","",metascore_data)

# 檢查metascore data的長度
length(metascore_data)

[1] 96

Step 8: meta score只有96個資料,可我們卻爬取了100部電影。這個問題產生的原型是由4部電影沒有Metascore資料。

img

Step 9: 這是爬取所有網頁都會遇到的常見問題,如果我們只是簡單地用NA來填充這四個缺失值,它會自動填充第97到100部電影。通過一些視覺化檢查,我們發缺失matascore的是第39,73,80和89部電影。我用下面的函式來解決這個問題。

for (i in c(39,73,80,89)){
  a <- metascore_data[1:(i-1)]
  b<-metascore_data[i:length(metascore_data)]
  metascore_data <- append(a, list("NA"))
  metascore_data <- append(metascore_data, b)
}

# 轉換為數值型
metascore_data <- as.numeric(metascore_data)

# 再次檢查下長度
length(metascore_data)

[1] 100

# 看看描述性統計量
summary(metascore_data)

Min.    1st Qu.  Median   Mean    3rd Qu.   Max.     NA's
23.00   47.00    60.00    60.22   74.00     99.00    4

Step 10: 同樣的問題也會發生在Gross變數上,我用同樣的方式來解決。

# 爬取revenue section
gross_data_html <- html_nodes(webpage,'.ghost~ .text-muted+ span')

# 轉為文字
gross_data <- html_text(gross_data_html)

# 檢查一下
head(gross_data)

[1] "$269.36M" "$248.04M" "$27.50M"  "$67.12M"  "$99.47M"  "$153.67M"

# 去除'$' 和 'M' 標記
gross_data <- gsub("M", "", gross_data)
gross_data <- substring(gross_data, 2, 6)

# 檢查長度
length(gross_data)

[1] 86

# 填充缺失值
for (i in c(17,39,49,52,57,64,66,73,76,77,80,87,88,89)){
  a <- gross_data[1:(i-1)]
  b <- gross_data[i:length(gross_data)]
  gross_data <- append(a, list("NA"))
  gross_data <- append(gross_data, b)
}

# 轉為數值
gross_data<-as.numeric(gross_data)

# 再次檢車長度
length(gross_data)

[1] 100

summary(gross_data)

Min.   1st Qu.  Median   Mean   3rd Qu.   Max.     NA's
0.08   15.52    54.69    96.91  119.50    530.70   14

Step 11: .我們已經成功爬取了100部電影的11個特徵,讓我們建立一個資料框並看看結構。

# 合併所有list來建立一個資料框
movies_df <- data.frame(
  Rank = rank_data, 
  Title = title_data,
  Description = description_data, 
  Runtime = runtime_data,
  Genre = genre_data, 
  Rating = rating_data,
  Metascore = metascore_data, 
  Votes = votes_data,                           
  Gross_Earning_in_Mil = gross_data,
  Director = directors_data, 
  Actor = actors_data
)

# 檢視資料框結構
str(movies_df)

'data.frame'          : 100 obs. of  11 variables:
$ Rank                : num  1 2 3 4 5 6 7 8 9 10 ...
$ Title               : Factor w/ 99 levels "10 Cloverfield Lane",..: 66 53 54 32 58 93 8 43 97 7 ...
$ Description         : Factor w/ 100 levels "19-year-old Billy Lynn is brought home for a victory     tour after a harrowing Iraq battle. Through flashbacks the film shows what"| __truncated__,..: 57 59     3 100 21 33 90 14 13 97 ...
$ Runtime             : num  108 107 111 139 116 92 115 128 111 116 ...
$ Genre               : Factor w/ 10 levels "Action","Adventure",..: 3 3 7 4 2 3 1 5 5 7 ...
$ Rating              : num  7.2 7.7 7.6 8.2 7 6.5 6.1 8.4 6.3 8 ...
$ Metascore           : num  59 81 99 71 41 56 36 93 39 81 ...
$ Votes               : num  40603 91333 112609 177229 148467 ...
$ Gross_Earning_in_Mil: num  269.3 248 27.5 67.1 99.5 ...
$ Director            : Factor w/ 98 levels "Andrew Stanton",..: 17 80 9 64 67 95 56 19 49 28 ...
$ Actor               : Factor w/ 86 levels "Aaron Eckhart",..: 59 7 56 5 42 6 64 71 86 3 ...

現在2016年上映的最流行的100部故事片在IMDB上的資料已經爬取成功了!

6. 分析從網頁爬取的資料

爬取好資料後,你們隊資料進行一些分析與推斷,訓練一些機器學習模型。我在上面這個資料集的基礎上做了一些有趣的視覺化來回答下面的問題。

library('ggplot2')
qplot(data = movies_df,Runtime,fill = Genre,bins = 30)

img

**Question 1: ** 那個型別的電影市場最長?

ggplot(movies_df,aes(x=Runtime,y=Rating))+
geom_point(aes(size=Votes,col=Genre))

img

**Question 2: ** 市場130-160分鐘的電影裡,哪一型別東西好評率最高?

ggplot(movies_df,aes(x=Runtime,y=Gross_Earning_in_Mil))+
geom_point(aes(size=Rating,col=Genre))

img

**Question 3: ** 100-120分鐘的電影中,哪類作品的票房成績最好

結語

我相信本文會讓你對利用R爬取網頁有一定了解,你對採集資料過程中可能遇到的問題也有所涉獵了。由於網頁上的大部分資料是非結構化的,爬蟲真的是非常重要的一項技能。

原文連結:https://www.analyticsvidhya.com/blog/2017/03/beginners-guide-on-web-scraping-in-r-using-rvest-with-hands-on-knowledge/

相關文章