Pengantar Web Scraping dengan R dan Paket rvest

Profile

1. Pendahuluan

Web scraping adalah teknik pengambilan data dari situs web dan mengubahnya menjadi format yang dapat dianalisis lebih lanjut. Dalam R, kita dapat menggunakan paket rvest untuk mengotomatisasi proses ini tanpa perlu memahami HTML secara mendalam.

Laporan ini akan membahas bagaimana menggunakan rvest untuk mengambil data dari tabel yang tersedia di situs web serta bagaimana mengekstrak informasi dari elemen HTML tertentu.

2. Instalasi dan Persiapan

Sebelum melakukan web scraping, pastikan R dan paket yang diperlukan sudah terinstal.

Instalasi Paket:

install.packages("rvest") library(rvest)

Selain itu, kita juga dapat menggunakan paket tambahan seperti tidyverse untuk manipulasi data:

install.packages("tidyverse") library(tidyverse)

3. Contoh Web Scraping

3.1 Mangambil Data dari Tabel Web

Jika sebuah situs memiliki tabel yang ingin kita ekstrak, kita dapat menggunakan fungsi html_table(). Sebagai contoh, kita akan mengambil data medali Olimpiade dari sebuah situs web.

Langkah-langkah:

  1. Menentukan URL situs web.
  2. Menggunakan read_html() untuk membaca halaman web.
  3. Menemukan elemen tabel dan mengubahnya menjadi data frame.
# Load library yang dibutuhkan
library(rvest)
## Warning: package 'rvest' was built under R version 4.4.3
library(httr)

# URL yang akan di-scrape
url <- "https://example.com/olympic-medals"

# Gunakan GET dengan User-Agent agar tidak diblokir
response <- GET(url, user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"))

# Cek apakah halaman dapat diakses
if (status_code(response) == 200) {
  # Baca HTML dari konten response
  page <- read_html(content(response, as = "text", encoding = "UTF-8"))
  
  # Ambil data dari tabel pertama
  data_medali <- page %>% html_node("table") %>% html_table(fill = TRUE)
  
  # Tampilkan 6 baris pertama
  head(data_medali)
} else {
  print(paste("Gagal mengakses halaman. Status code:", status_code(response)))
}
## [1] "Gagal mengakses halaman. Status code: 404"

3.2 Mengambil Data dari Elemen HTML Spesifik

Terkadang, data yang kita butuhkan tidak dalam bentuk tabel tetapi tersembunyi dalam elemen HTML tertentu. Kita bisa menggunakan html_nodes() untuk mengambil data dari elemen tersebut.

Sebagai contoh, kita akan mengambil daftar nama paket R dari situs web CRAN.

# Load library yang dibutuhkan
library(rvest)
library(httr)
library(dplyr)  
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
# URL halaman CRAN yang akan di-scrape
url <- "https://cran.r-project.org/web/packages/available_packages_by_name.html"

# Menggunakan GET dengan User-Agent agar tidak diblokir
response <- GET(url, user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"))

# Cek apakah halaman dapat diakses
if (status_code(response) == 200) {
  # Baca HTML dari konten response
  page <- read_html(content(response, as = "text", encoding = "UTF-8"))
  
  # Mengambil tabel pertama dari halaman
  nama_paket <- page %>% html_nodes("table") %>% .[[1]] %>% html_table(fill = TRUE)
  
  # Memastikan hanya mengambil kolom pertama dan menghapus header yang salah
  nama_paket <- nama_paket[, 1, drop = FALSE]
  
  # Menghapus baris kosong atau tidak valid
  nama_paket <- nama_paket %>% 
    filter(nchar(X1) > 0) %>%  # Hapus string kosong
    filter(X1 != "X1")         # Hapus header duplikat (jika ada)
  
  # Menyimpan hasil tanpa mencetak langsung
  nama_paket <- head(nama_paket, -1)  # Menghapus baris terakhir dari output
} else {
  print(paste("Gagal mengakses halaman. Status code:", status_code(response)))
}

3.3 Web Scraping dengan Loop

Untuk mengambil data dari beberapa halaman web, kita bisa menggunakan loop. Sebagai contoh, kita akan mengambil data pemain dari beberapa tim sepak bola.

# Load library yang dibutuhkan
library(rvest)
library(httr)
library(dplyr)

# Daftar URL yang akan di-scrape
urls <- c("https://example.com/team1", 
          "https://example.com/team2", 
          "https://example.com/team3")

# Inisialisasi data frame kosong untuk menyimpan hasil
data_pemain <- data.frame()

# Loop melalui setiap URL
for (url in urls) {
  # Menggunakan GET dengan User-Agent agar tidak diblokir
  response <- GET(url, user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"))
  
  # Periksa apakah halaman dapat diakses
  if (status_code(response) == 200) {
    # Baca HTML dari konten response
    page <- read_html(content(response, as = "text", encoding = "UTF-8"))
    
    # Mengambil tabel pertama dari halaman
    tabel <- page %>% html_node("table") %>% html_table(fill = TRUE)
    
    # Menambahkan kolom URL untuk identifikasi
    tabel$Source <- url
    
    # Menggabungkan data dengan data frame utama
    data_pemain <- bind_rows(data_pemain, tabel)
  } else {
    message(paste("Gagal mengakses:", url, "- Status:", status_code(response)))
  }
}
## Gagal mengakses: https://example.com/team1 - Status: 404
## Gagal mengakses: https://example.com/team2 - Status: 404
## Gagal mengakses: https://example.com/team3 - Status: 404
# Menampilkan beberapa baris pertama hasil scraping
head(data_pemain)

4. Tantangan dalam Web Scraping

  • Beberapa situs memiliki perlindungan anti-scraping yang dapat memblokir akses otomatis.
  • Struktur HTML dapat berubah dari waktu ke waktu, sehingga skrip harus diperbarui secara berkala.
  • Untuk scraping data dalam jumlah besar, sebaiknya batasi jumlah permintaan untuk menghindari pemblokiran.

5. Kesimpulan

Web scraping menggunakan rvest di R memungkinkan pengambilan data dari berbagai sumber web dengan mudah. Dengan memahami dasar-dasar rvest, kita dapat mengekstrak tabel, teks, dan elemen HTML lainnya untuk dianalisis lebih lanjut.

LS0tDQp0aXRsZTogIlBlbmdhbnRhciBXZWIgU2NyYXBpbmcgZGVuZ2FuIFIgZGFuIFBha2V0IHJ2ZXN0Ig0KDQphdXRob3I6IA0KICAgIC0gIk5hYmlsYSBBbmdnaXRhIFB1dHJpIg0KICAgIA0KZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJUIgJWQsICVZJylgIg0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoNCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQ0KICAgIHRodW1ibmFpbHM6IHRydWUNCiAgICBsaWdodGJveDogdHJ1ZQ0KICAgIGdhbGxlcnk6IHRydWUNCiAgICBsaWJfZGlyOiBsaWJzDQogICAgZGZfcHJpbnQ6ICJwYWdlZCINCiAgICBjb2RlX2ZvbGRpbmc6ICJzaG93Ig0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNzczogInN0eWxlL3N0eWxlLmNzcyINCi0tLQ0KPGltZyBzcmM9ImltZy9wcm9maWxlLmpwZyIgYWx0PSJQcm9maWxlIiBpZD0ibG9nby11dGFtYSIgc3R5bGU9IndpZHRoOjMwMHB4OyBkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIvPg0KDQoNCiMgKioxLiBQZW5kYWh1bHVhbioqDQoNCldlYiBzY3JhcGluZyBhZGFsYWggdGVrbmlrIHBlbmdhbWJpbGFuIGRhdGEgZGFyaSBzaXR1cyB3ZWIgZGFuIG1lbmd1YmFobnlhIG1lbmphZGkgZm9ybWF0IHlhbmcgZGFwYXQgZGlhbmFsaXNpcyBsZWJpaCBsYW5qdXQuIERhbGFtIFIsIGtpdGEgZGFwYXQgbWVuZ2d1bmFrYW4gcGFrZXQgYGBydmVzdGBgIHVudHVrIG1lbmdvdG9tYXRpc2FzaSBwcm9zZXMgaW5pIHRhbnBhIHBlcmx1IG1lbWFoYW1pIEhUTUwgc2VjYXJhIG1lbmRhbGFtLg0KDQpMYXBvcmFuIGluaSBha2FuIG1lbWJhaGFzIGJhZ2FpbWFuYSBtZW5nZ3VuYWthbiBgYHJ2ZXN0YGAgdW50dWsgbWVuZ2FtYmlsIGRhdGEgZGFyaSB0YWJlbCB5YW5nIHRlcnNlZGlhIGRpIHNpdHVzIHdlYiBzZXJ0YSBiYWdhaW1hbmEgbWVuZ2Vrc3RyYWsgaW5mb3JtYXNpIGRhcmkgZWxlbWVuIEhUTUwgdGVydGVudHUuDQoNCiMgKioyLiBJbnN0YWxhc2kgZGFuIFBlcnNpYXBhbioqDQoNClNlYmVsdW0gbWVsYWt1a2FuIHdlYiBzY3JhcGluZywgcGFzdGlrYW4gUiBkYW4gcGFrZXQgeWFuZyBkaXBlcmx1a2FuIHN1ZGFoIHRlcmluc3RhbC4NCg0KKipJbnN0YWxhc2kgUGFrZXQqKjoNCg0KYGBpbnN0YWxsLnBhY2thZ2VzKCJydmVzdCIpDQpsaWJyYXJ5KHJ2ZXN0KWBgDQoNClNlbGFpbiBpdHUsIGtpdGEganVnYSBkYXBhdCBtZW5nZ3VuYWthbiBwYWtldCB0YW1iYWhhbiBzZXBlcnRpIHRpZHl2ZXJzZSB1bnR1ayBtYW5pcHVsYXNpIGRhdGE6DQoNCmBgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmxpYnJhcnkodGlkeXZlcnNlKWBgDQoNCiMgKiozLiBDb250b2ggV2ViIFNjcmFwaW5nKioNCg0KIyMgKiozLjEgTWFuZ2FtYmlsIERhdGEgZGFyaSBUYWJlbCBXZWIqKg0KDQpKaWthIHNlYnVhaCBzaXR1cyBtZW1pbGlraSB0YWJlbCB5YW5nIGluZ2luIGtpdGEgZWtzdHJhaywga2l0YSBkYXBhdCBtZW5nZ3VuYWthbiBmdW5nc2kgYGBodG1sX3RhYmxlKClgYC4gU2ViYWdhaSBjb250b2gsIGtpdGEgYWthbiBtZW5nYW1iaWwgZGF0YSBtZWRhbGkgT2xpbXBpYWRlIGRhcmkgc2VidWFoIHNpdHVzIHdlYi4NCg0KKipMYW5na2FoLWxhbmdrYWgqKjoNCg0KMS4gTWVuZW50dWthbiBVUkwgc2l0dXMgd2ViLg0KMi4gTWVuZ2d1bmFrYW4gYGByZWFkX2h0bWwoKWBgIHVudHVrIG1lbWJhY2EgaGFsYW1hbiB3ZWIuDQozLiBNZW5lbXVrYW4gZWxlbWVuIHRhYmVsIGRhbiBtZW5ndWJhaG55YSBtZW5qYWRpIGRhdGEgZnJhbWUuDQpgYGB7ciBlY2hvPVRSVUV9DQojIExvYWQgbGlicmFyeSB5YW5nIGRpYnV0dWhrYW4NCmxpYnJhcnkocnZlc3QpDQpsaWJyYXJ5KGh0dHIpDQoNCiMgVVJMIHlhbmcgYWthbiBkaS1zY3JhcGUNCnVybCA8LSAiaHR0cHM6Ly9leGFtcGxlLmNvbS9vbHltcGljLW1lZGFscyINCg0KIyBHdW5ha2FuIEdFVCBkZW5nYW4gVXNlci1BZ2VudCBhZ2FyIHRpZGFrIGRpYmxva2lyDQpyZXNwb25zZSA8LSBHRVQodXJsLCB1c2VyX2FnZW50KCJNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTEuMC40NDcyLjEyNCBTYWZhcmkvNTM3LjM2IikpDQoNCiMgQ2VrIGFwYWthaCBoYWxhbWFuIGRhcGF0IGRpYWtzZXMNCmlmIChzdGF0dXNfY29kZShyZXNwb25zZSkgPT0gMjAwKSB7DQogICMgQmFjYSBIVE1MIGRhcmkga29udGVuIHJlc3BvbnNlDQogIHBhZ2UgPC0gcmVhZF9odG1sKGNvbnRlbnQocmVzcG9uc2UsIGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpKQ0KICANCiAgIyBBbWJpbCBkYXRhIGRhcmkgdGFiZWwgcGVydGFtYQ0KICBkYXRhX21lZGFsaSA8LSBwYWdlICU+JSBodG1sX25vZGUoInRhYmxlIikgJT4lIGh0bWxfdGFibGUoZmlsbCA9IFRSVUUpDQogIA0KICAjIFRhbXBpbGthbiA2IGJhcmlzIHBlcnRhbWENCiAgaGVhZChkYXRhX21lZGFsaSkNCn0gZWxzZSB7DQogIHByaW50KHBhc3RlKCJHYWdhbCBtZW5nYWtzZXMgaGFsYW1hbi4gU3RhdHVzIGNvZGU6Iiwgc3RhdHVzX2NvZGUocmVzcG9uc2UpKSkNCn0NCmBgYA0KDQojIyAqKjMuMiBNZW5nYW1iaWwgRGF0YSBkYXJpIEVsZW1lbiBIVE1MIFNwZXNpZmlrKioNCg0KVGVya2FkYW5nLCBkYXRhIHlhbmcga2l0YSBidXR1aGthbiB0aWRhayBkYWxhbSBiZW50dWsgdGFiZWwgdGV0YXBpIHRlcnNlbWJ1bnlpIGRhbGFtIGVsZW1lbiBIVE1MIHRlcnRlbnR1LiBLaXRhIGJpc2EgbWVuZ2d1bmFrYW4gYGBodG1sX25vZGVzKClgYCB1bnR1ayBtZW5nYW1iaWwgZGF0YSBkYXJpIGVsZW1lbiB0ZXJzZWJ1dC4NCg0KU2ViYWdhaSBjb250b2gsIGtpdGEgYWthbiBtZW5nYW1iaWwgZGFmdGFyIG5hbWEgcGFrZXQgUiBkYXJpIHNpdHVzIHdlYiBDUkFOLg0KYGBge3IgZWNobz1UUlVFfQ0KIyBMb2FkIGxpYnJhcnkgeWFuZyBkaWJ1dHVoa2FuDQpsaWJyYXJ5KHJ2ZXN0KQ0KbGlicmFyeShodHRyKQ0KbGlicmFyeShkcGx5cikgIA0KDQojIFVSTCBoYWxhbWFuIENSQU4geWFuZyBha2FuIGRpLXNjcmFwZQ0KdXJsIDwtICJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvYXZhaWxhYmxlX3BhY2thZ2VzX2J5X25hbWUuaHRtbCINCg0KIyBNZW5nZ3VuYWthbiBHRVQgZGVuZ2FuIFVzZXItQWdlbnQgYWdhciB0aWRhayBkaWJsb2tpcg0KcmVzcG9uc2UgPC0gR0VUKHVybCwgdXNlcl9hZ2VudCgiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzkxLjAuNDQ3Mi4xMjQgU2FmYXJpLzUzNy4zNiIpKQ0KDQojIENlayBhcGFrYWggaGFsYW1hbiBkYXBhdCBkaWFrc2VzDQppZiAoc3RhdHVzX2NvZGUocmVzcG9uc2UpID09IDIwMCkgew0KICAjIEJhY2EgSFRNTCBkYXJpIGtvbnRlbiByZXNwb25zZQ0KICBwYWdlIDwtIHJlYWRfaHRtbChjb250ZW50KHJlc3BvbnNlLCBhcyA9ICJ0ZXh0IiwgZW5jb2RpbmcgPSAiVVRGLTgiKSkNCiAgDQogICMgTWVuZ2FtYmlsIHRhYmVsIHBlcnRhbWEgZGFyaSBoYWxhbWFuDQogIG5hbWFfcGFrZXQgPC0gcGFnZSAlPiUgaHRtbF9ub2RlcygidGFibGUiKSAlPiUgLltbMV1dICU+JSBodG1sX3RhYmxlKGZpbGwgPSBUUlVFKQ0KICANCiAgIyBNZW1hc3Rpa2FuIGhhbnlhIG1lbmdhbWJpbCBrb2xvbSBwZXJ0YW1hIGRhbiBtZW5naGFwdXMgaGVhZGVyIHlhbmcgc2FsYWgNCiAgbmFtYV9wYWtldCA8LSBuYW1hX3Bha2V0WywgMSwgZHJvcCA9IEZBTFNFXQ0KICANCiAgIyBNZW5naGFwdXMgYmFyaXMga29zb25nIGF0YXUgdGlkYWsgdmFsaWQNCiAgbmFtYV9wYWtldCA8LSBuYW1hX3Bha2V0ICU+JSANCiAgICBmaWx0ZXIobmNoYXIoWDEpID4gMCkgJT4lICAjIEhhcHVzIHN0cmluZyBrb3NvbmcNCiAgICBmaWx0ZXIoWDEgIT0gIlgxIikgICAgICAgICAjIEhhcHVzIGhlYWRlciBkdXBsaWthdCAoamlrYSBhZGEpDQogIA0KICAjIE1lbnlpbXBhbiBoYXNpbCB0YW5wYSBtZW5jZXRhayBsYW5nc3VuZw0KICBuYW1hX3Bha2V0IDwtIGhlYWQobmFtYV9wYWtldCwgLTEpICAjIE1lbmdoYXB1cyBiYXJpcyB0ZXJha2hpciBkYXJpIG91dHB1dA0KfSBlbHNlIHsNCiAgcHJpbnQocGFzdGUoIkdhZ2FsIG1lbmdha3NlcyBoYWxhbWFuLiBTdGF0dXMgY29kZToiLCBzdGF0dXNfY29kZShyZXNwb25zZSkpKQ0KfQ0KYGBgDQojIyAqKjMuMyBXZWIgU2NyYXBpbmcgZGVuZ2FuIExvb3AqKg0KDQpVbnR1ayBtZW5nYW1iaWwgZGF0YSBkYXJpIGJlYmVyYXBhIGhhbGFtYW4gd2ViLCBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gbG9vcC4gU2ViYWdhaSBjb250b2gsIGtpdGEgYWthbiBtZW5nYW1iaWwgZGF0YSBwZW1haW4gZGFyaSBiZWJlcmFwYSB0aW0gc2VwYWsgYm9sYS4NCmBgYHtyIGVjaG89VFJVRX0NCiMgTG9hZCBsaWJyYXJ5IHlhbmcgZGlidXR1aGthbg0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkoaHR0cikNCmxpYnJhcnkoZHBseXIpDQoNCiMgRGFmdGFyIFVSTCB5YW5nIGFrYW4gZGktc2NyYXBlDQp1cmxzIDwtIGMoImh0dHBzOi8vZXhhbXBsZS5jb20vdGVhbTEiLCANCiAgICAgICAgICAiaHR0cHM6Ly9leGFtcGxlLmNvbS90ZWFtMiIsIA0KICAgICAgICAgICJodHRwczovL2V4YW1wbGUuY29tL3RlYW0zIikNCg0KIyBJbmlzaWFsaXNhc2kgZGF0YSBmcmFtZSBrb3NvbmcgdW50dWsgbWVueWltcGFuIGhhc2lsDQpkYXRhX3BlbWFpbiA8LSBkYXRhLmZyYW1lKCkNCg0KIyBMb29wIG1lbGFsdWkgc2V0aWFwIFVSTA0KZm9yICh1cmwgaW4gdXJscykgew0KICAjIE1lbmdndW5ha2FuIEdFVCBkZW5nYW4gVXNlci1BZ2VudCBhZ2FyIHRpZGFrIGRpYmxva2lyDQogIHJlc3BvbnNlIDwtIEdFVCh1cmwsIHVzZXJfYWdlbnQoIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS85MS4wLjQ0NzIuMTI0IFNhZmFyaS81MzcuMzYiKSkNCiAgDQogICMgUGVyaWtzYSBhcGFrYWggaGFsYW1hbiBkYXBhdCBkaWFrc2VzDQogIGlmIChzdGF0dXNfY29kZShyZXNwb25zZSkgPT0gMjAwKSB7DQogICAgIyBCYWNhIEhUTUwgZGFyaSBrb250ZW4gcmVzcG9uc2UNCiAgICBwYWdlIDwtIHJlYWRfaHRtbChjb250ZW50KHJlc3BvbnNlLCBhcyA9ICJ0ZXh0IiwgZW5jb2RpbmcgPSAiVVRGLTgiKSkNCiAgICANCiAgICAjIE1lbmdhbWJpbCB0YWJlbCBwZXJ0YW1hIGRhcmkgaGFsYW1hbg0KICAgIHRhYmVsIDwtIHBhZ2UgJT4lIGh0bWxfbm9kZSgidGFibGUiKSAlPiUgaHRtbF90YWJsZShmaWxsID0gVFJVRSkNCiAgICANCiAgICAjIE1lbmFtYmFoa2FuIGtvbG9tIFVSTCB1bnR1ayBpZGVudGlmaWthc2kNCiAgICB0YWJlbCRTb3VyY2UgPC0gdXJsDQogICAgDQogICAgIyBNZW5nZ2FidW5na2FuIGRhdGEgZGVuZ2FuIGRhdGEgZnJhbWUgdXRhbWENCiAgICBkYXRhX3BlbWFpbiA8LSBiaW5kX3Jvd3MoZGF0YV9wZW1haW4sIHRhYmVsKQ0KICB9IGVsc2Ugew0KICAgIG1lc3NhZ2UocGFzdGUoIkdhZ2FsIG1lbmdha3NlczoiLCB1cmwsICItIFN0YXR1czoiLCBzdGF0dXNfY29kZShyZXNwb25zZSkpKQ0KICB9DQp9DQoNCiMgTWVuYW1waWxrYW4gYmViZXJhcGEgYmFyaXMgcGVydGFtYSBoYXNpbCBzY3JhcGluZw0KaGVhZChkYXRhX3BlbWFpbikNCmBgYA0KDQojICoqNC4gVGFudGFuZ2FuIGRhbGFtIFdlYiBTY3JhcGluZyoqDQoNCi0gQmViZXJhcGEgc2l0dXMgbWVtaWxpa2kgcGVybGluZHVuZ2FuIGFudGktc2NyYXBpbmcgeWFuZyBkYXBhdCBtZW1ibG9raXIgYWtzZXMgb3RvbWF0aXMuDQotIFN0cnVrdHVyIEhUTUwgZGFwYXQgYmVydWJhaCBkYXJpIHdha3R1IGtlIHdha3R1LCBzZWhpbmdnYSBza3JpcCBoYXJ1cyBkaXBlcmJhcnVpIHNlY2FyYSBiZXJrYWxhLg0KLSBVbnR1ayBzY3JhcGluZyBkYXRhIGRhbGFtIGp1bWxhaCBiZXNhciwgc2ViYWlrbnlhIGJhdGFzaSBqdW1sYWggcGVybWludGFhbiB1bnR1ayBtZW5naGluZGFyaSBwZW1ibG9raXJhbi4NCg0KDQojICoqNS4gS2VzaW1wdWxhbioqDQoNCldlYiBzY3JhcGluZyBtZW5nZ3VuYWthbiBgYHJ2ZXN0YGAgZGkgUiBtZW11bmdraW5rYW4gcGVuZ2FtYmlsYW4gZGF0YSBkYXJpIGJlcmJhZ2FpIHN1bWJlciB3ZWIgZGVuZ2FuIG11ZGFoLiBEZW5nYW4gbWVtYWhhbWkgZGFzYXItZGFzYXIgYGBydmVzdGBgLCBraXRhIGRhcGF0IG1lbmdla3N0cmFrIHRhYmVsLCB0ZWtzLCBkYW4gZWxlbWVuIEhUTUwgbGFpbm55YSB1bnR1ayBkaWFuYWxpc2lzIGxlYmloIGxhbmp1dC4=