輕鬆學習 R 語言:網頁資料擷取

以 jsonlite、xml2、rvest 套件實踐網站爬蟲

郭耀仁 Yao-Jen Kuo
Apr 23 · 19 min read

The world’s most valuable resource is no longer oil, but data.

The Economist

獲取資料(Getting Data)在資料科學專案中扮演攻擊發起點,如果這個專案目的是協助我們制定資料驅動的策略(data-driven strategy),而非傳統倚賴直覺的「根據經驗」策略,那麼為專案細心盤點資料來源與整理獲取方法,將可以為決策奠基穩固的基礎。常見的資料來源可以分為三種:

  1. 檔案
  2. 資料庫
  3. 網頁資料擷取

輕鬆學習 R 語言:資料輸入與輸出我們討論了如何透過 R 語言載入表格式檔案(包含 CSV 資料、Excel 試算表)、非表格式檔案(包含 TXT 資料、JSON 資料);在輕鬆學習 R 語言:向資料庫查詢我們簡介如何以 R 語言透過 DBIRSQLite 兩個套件連結本機端的 SQLite 資料庫,並利用 DBI 套件所提供的函數實踐四種常見資料庫表格操作,即所謂的 CRUD:Create、Read、Update 與 Delete。這個小節我們要討論第三種資料來源:網頁,而從網頁擷取資料的技巧,有著另外一個更為眾人耳熟能詳的名稱:網站爬蟲


網站爬蟲的核心任務

接著我們可依照解析資料的複雜程度將任務再細分為三類:JSON(全名為 JavaScript Object Notation,一種輕量級的資料交換格式)、XML(全名為 Extensible Markup Language)與 HTML(全名為 HyperText Markup Language)。很多的 Web API(全名為 Web Application Programming Interface,意即建構於網站伺服器的應用程式介面,作為不同系統之間的資料傳遞管道)其資料格式皆約定為 JSON 與 XML。如果資料格式是 JSON 在請求資料之後會以 R 語言的 listdata.frame 結構儲存,幾乎沒有額外的解析需求;假使資料格式是標記語言(XML 或 HTML),則會需要繼續以 XPath(提供在 XML/HTML 資料中以 XML 節點找尋特定資料位置的定位方法)或 CSS Selector(提供在 HTML 資料中以層疊樣式表找尋特定資料位置的定位方法)來解析。

安裝與載入套件

我們可以選擇透過命令列(Console)以 install.packages() 函數進行安裝。

## > # 安裝 jsonlite、rvest 與 magrittr
## > pkgs <- c("jsonlite", "rvest", "magrittr")
## > install.packages(pkgs)
## trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.5/jsonlite_1.6.tgz'
## Content type 'application/x-gzip' length 1114907 bytes (1.1 MB)
## ==================================================
## downloaded 1.1 MB
##
## trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.5/rvest_0.3.3.tgz'
## Content type 'application/x-gzip' length 912701 bytes (891 KB)
## ==================================================
## downloaded 891 KB
##
## trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.5/magrittr_1.5.tgz'
## Content type 'application/x-gzip' length 152395 bytes (148 KB)
## ==================================================
## downloaded 148 KB
##
##
## The downloaded binary packages are in
## /var/folders/0b/r__z5mpn6ldgb_w2j7_y_ntr0000gn/T//RtmpjWr2NP/downloaded_packages

或是透過圖形化介面(Graphic User Interface, GUI)的方法安裝,在右下角的 packages 頁籤點選 install,再輸入套件名稱接著點選安裝。

透過圖形化介面安裝

我們可以選擇透過命令列(Console)以 library() 函數將套件載入環境來使用。

## > # 載入 jsonlite、rvest 與 magrittr
## > library(jsonlite)
## > library(rvest)
## Loading required package: xml2
## > library(magrittr)

或是透過圖形化介面(Graphic User Interface, GUI)在右下角的 packages頁籤下搜尋然後將前面的核取方框打勾。

擷取 JSON 格式資料

## > # 安裝 jsonlite、rvest 與 magrittr
## > #pkgs <- c("jsonlite", "rvest", "magrittr")
## > #install.packages(pkgs)
## > # 載入 jsonlite
## > library(jsonlite)
## >
## > aqi_url <- "https://opendata.epa.gov.tw/ws/Data/AQI/?$format=json"
## > aqi <- fromJSON(aqi_url)
## > class(aqi)
## [1] "data.frame"
## > head(aqi)
## SiteName County AQI Pollutant Status SO2 CO CO_8hr O3 O3_8hr PM10 PM2.5 NO2 NOx NO WindSpeed WindDirec PublishTime PM2.5_AVG PM10_AVG
## 1 屏東(琉球) 屏東縣 設備維護 2.2 0.15 0.2 22 15 20 5.4 7.8 2.4 2.3 323 2019-04-23 10:00 22
## 2 苗栗(後龍) 苗栗縣 66 細懸浮微粒 普通 2.7 0.25 0.2 35 14 23 22 5.9 9.9 4 4.3 208 2019-04-23 10:00 22 26
## 3 彰化(大城) 彰化縣 51 細懸浮微粒 普通 2.1 0.16 0.2 29 16 23 14 3.1 4.2 1.1 0.4 228 2019-04-23 10:00 16 23
## 4 臺南(北門) 臺南市 23 良好 1.4 0.02 0.1 25 18 23 9 2.1 2.4 0.3 1.9 239 2019-04-23 10:00 7 22
## 5 富貴角 新北市 62 細懸浮微粒 普通 1.1 0.21 0.2 32 22 43 20 5.4 6.9 1.4 1.3 274 2019-04-23 10:00 20 39
## 6 麥寮 雲林縣 35 良好 2.1 0.18 0.2 24 14 31 13 5.2 7.2 2 2.7 236 2019-04-23 10:00 11 36
## SO2_AVG Longitude Latitude
## 1 2 120.377222 22.352222
## 2 2 120.786028 24.616369
## 3 2 120.273117 23.843139
## 4 2 120.124444 23.264722
## 5 1 121.536763 25.298562
## 6 2 120.251825 23.753506

同樣以jsonlite 套件中的 fromJSON() 函數示範擷取 data.nba.net 所提供的 Web API 將 JSON 格式資料載入 R 語言中,這時由於該 Web API 提供的是 JSON,擷取之後的資料結構則會是 list

## > # 安裝 jsonlite、rvest 與 magrittr
## > #pkgs <- c("jsonlite", "rvest", "magrittr")
## > #install.packages(pkgs)
## > # 載入 jsonlite
## > library(jsonlite)
## >
## > nba_url <- "http://data.nba.net/10s/prod/v1/2018/players.json"
## > nba_players <- fromJSON(nba_url)
## > class(nba_players)
## [1] "list"
## > paste(nba_players$league$standard$firstName, nba_players$league$standard$lastName)[1:10]
## [1] "Jaylen Adams" "Steven Adams" "Bam Adebayo" "Deng Adel" "LaMarcus Aldridge" "Rawle Alkins" "Grayson Allen"
## [8] "Jarrett Allen" "Kadeem Allen" "Al-Farouq Aminu"

擷取 XML 格式資料

## > # 安裝 jsonlite、rvest 與 magrittr
## > #pkgs <- c("jsonlite", "rvest", "magrittr")
## > #install.packages(pkgs)
## > # 載入 xml2, magrittr
## > library(xml2)
## > library(magrittr)
## >
## > aqi_url <- "https://opendata.epa.gov.tw/ws/Data/AQI/?$format=xml"
## > aqi <- read_xml(aqi_url)
## > class(aqi)
## [1] "xml_document" "xml_node"
## > site_names <- aqi %>%
## + xml_find_all(xpath = "//Data/SiteName") %>%
## + xml_text()
## > class(site_names)
## [1] "character"
## > site_names
## [1] "屏東(琉球)" "苗栗(後龍)" "彰化(大城)" "臺南(北門)" "富貴角"
## [6] "麥寮" "關山" "馬公" "金門" "馬祖"
## [11] "埔里" "復興" "永和" "竹山" "中壢"
## [16] "三重" "冬山" "宜蘭" "陽明" "花蓮"
## [21] "臺東" "恆春" "潮州" "屏東" "小港"
## [26] "前鎮" "前金" "左營" "楠梓" "林園"
## [31] "大寮" "鳳山" "仁武" "橋頭" "美濃"
## [36] "臺南" "安南" "善化" "新營" "嘉義"
## [41] "臺西" "朴子" "新港" "崙背" "斗六"
## [46] "南投" "二林" "線西" "彰化" "西屯"
## [51] "忠明" "大里" "沙鹿" "豐原" "三義"
## [56] "苗栗" "頭份" "新竹" "竹東" "湖口"
## [61] "龍潭" "平鎮" "觀音" "大園" "桃園"
## [66] "大同" "松山" "古亭" "萬華" "中山"
## [71] "士林" "淡水" "林口" "菜寮" "新莊"
## [76] "板橋" "土城" "新店" "萬里" "汐止"
## [81] "基隆"

擷取 HTML 格式資料

所以熟練定位網頁中特定資料位址的技巧就變得十分重要,如同在地圖上加入標記(Marker)一般,我們需要景點或建築物的位址,可以是門牌號碼、詳細地址甚至是精準的經緯度。在網頁上標記資料為止有非常多方法能夠表示,常見的像是使用:

  • HTML 的標籤名稱
  • HTML 標籤中給予的 id
  • HTML 標籤中給予的 class
  • CSS 選擇器(CSS Selector)
  • XPath

考量多數資料科學愛好者並不具備網頁工程師的背景技能,透過 Chrome 瀏覽器的外掛來取得資料所在的 CSS 選擇器或者 XPath 是快速入門的捷徑。

Chrome 瀏覽器外掛:Selector Gadget

  1. 前往 Chrome Web Store,點選外掛(Extensions)
  2. 搜尋 Selector Gadget 並點選加入到 Chrome 瀏覽器
  3. 確認要加入 Selector Gadget
  4. 完成安裝
前往 Chrome Web Store,點選外掛(Extensions)
搜尋 Selector Gadget 並點選加入到 Chrome 瀏覽器
確認要加入 Selector Gadget
完成安裝

依照下列步驟定位 HTML 格式資料的 CSS 選擇器。

  1. 點選 Selector Gadget 的外掛圖示
  2. 留意 Selector Gadget 的 CSS 選擇器
  3. 移動滑鼠到想要定位的元素
  4. 在想要定位的評分上面點選左鍵,留意此時的 CSS 選擇器位址與資料筆數,通常在第一次點擊後網頁上很多的資料都會同時被選到(以黃底標記),Clear 後面數字表示有多少筆
  5. 移動滑鼠點選不要選擇的元素(改以紅底標記),並同時注意 CSS 選擇器位址與資料筆數的變動,當資料筆數與預期相符時表示完成定位

我們以 IMDB.comAvengers: Infinity War (2018) 頁面示範如何以 CSS 選擇器定位評等。

點選 Selector Gadget 外掛圖示
留意 Selector Gadget 的 CSS 選擇器
在想要定位的評分上面點選左鍵,留意此時的 CSS 選擇器位址與資料筆數
移動滑鼠點選不要選擇的元素,當資料筆數與預期相符時表示完成定位

最後利用 rvest 套件的 read_html() 函數將 HTML 資料格式讀入,獲得的資料結構同樣是命名為 xml_documentlist,面對 xml_document 可以呼叫 rvest 套件提供的 html_nodes()html_text() 函數指定 CSS 選擇器解析出文字格式的資料。

## > # 安裝 jsonlite、rvest 與 magrittr
## > #pkgs <- c("jsonlite", "rvest", "magrittr")
## > #install.packages(pkgs)
## > # 載入 rvest, magrittr
## > library(rvest)
## > library(magrittr)
## >
## > movie_url <- "https://www.imdb.com/title/tt4154756"
## > movie <- read_html(movie_url)
## > class(movie)
## [1] "xml_document" "xml_node"
## > rating <- movie %>%
## + html_nodes(css = "strong span") %>%
## + html_text() %>%
## + as.numeric()
## > rating
## [1] 8.5

Chrome 瀏覽器外掛:XPath Helper

  1. 前往 Chrome Web Store,點選外掛(Extensions)
  2. 搜尋 XPath Helper 並點選加入到 Chrome 瀏覽器
  3. 確認要加入 XPath Helper
  4. 完成安裝
前往 Chrome Web Store,點選外掛(Extensions)
搜尋 XPath Helper 並點選加入到 Chrome 瀏覽器
確認要加入 XPath Helper
完成安裝

依照下列步驟定位 HTML 格式資料的 XPath。

  1. 點選 XPath Helper 的外掛圖示
  2. 留意 XPath Helper 介面左邊的 XPath 與右邊被定位到的資料
  3. 按住 shift 鍵移動滑鼠到想要定位的資料
  4. 試著縮減 XPath,從最前面的節點開始刪減,在最短的 XPath 依然能對應到資料即表示完成定位

我們以 IMDB.comAvengers: Infinity War (2018) 頁面示範如何以 XPath 定位評等。

點選 XPath Helper 的外掛圖示
留意 XPath Helper 介面左邊的 XPath 與右邊被定位到的資料
按住 shift 鍵移動滑鼠到想要定位的資料
從最前面的節點開始刪減,在最短的 XPath 依然能對應到資料即表示完成定位

最後利用 rvest 套件的 read_html() 函數將 HTML 資料格式讀入,獲得的資料結構同樣是命名為 xml_documentlist,面對 xml_document 可以呼叫 rvest 套件提供的 html_nodes()html_text() 函數指定 XPath 解析出文字格式的資料。

## > # 安裝 jsonlite、rvest 與 magrittr
## > #pkgs <- c("jsonlite", "rvest", "magrittr")
## > #install.packages(pkgs)
## > # 載入 rvest, magrittr
## > library(rvest)
## > library(magrittr)
## >
## > movie_url <- "https://www.imdb.com/title/tt4154756"
## > movie <- read_html(movie_url)
## > class(movie)
## [1] "xml_document" "xml_node"
## > rating <- movie %>%
## + html_nodes(xpath = "//strong/span") %>%
## + html_text() %>%
## + as.numeric()
## > rating
## [1] 8.5

小結


DataInPoint

DataInPoint 是一個超棒的資料科學專欄,主題涵蓋資料、程式、機器學習與高效能運算。

郭耀仁 Yao-Jen Kuo

Written by

Could that data be any tidier? It is always nice to meet a data enthusiast / 2:43 marathon runner.

DataInPoint

DataInPoint 是一個超棒的資料科學專欄,主題涵蓋資料、程式、機器學習與高效能運算。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade