data source

Kickstarter Scraper

讀入資料並將雙引號轉換正確

library(data.table)
kickstarter = fread("Kickstarter_2017-01-15T22_21_04_985Z/Kickstarter041.csv", data.table = F, verbose = T, stringsAsFactors = F)
Input contains no \n. Taking this to be a filename to open
[01] Check arguments
  Using 8 threads (omp_get_max_threads()=8, nth=8)
  NAstrings = [<<NA>>]
  None of the NAstrings look like numbers.
  show progress = 1
  0/1 column will be read as integer
[02] Opening the file
  Opening file Kickstarter_2017-01-15T22_21_04_985Z/Kickstarter041.csv
  File opened, size = 14.93MB (15651944 bytes).
  Memory mapped ok
[03] Detect and skip BOM
[04] Arrange mmap to be \0 terminated
  \n has been found in the input and different lines can end with different line endings (e.g. mixed \n and \r\n in one file). This is common and ideal.
[05] Skipping initial rows if needed
  Positioned on line 1 starting: <<id,photo,name,blurb,goal,pledg>>
[06] Detect separator, quoting rule, and ncolumns
  Detecting sep automatically ...
  sep=','  with 100 lines of 32 fields using quote rule 0
  Detected 32 columns on line 1. This line is either column names or first data row. Line starts as: <<id,photo,name,blurb,goal,pledg>>
  Quote rule picked = 0
  fill=false and the most number of columns found is 32
[07] Detect column types, good nrow estimate and whether first row is column names
  Number of sampling jump points = 10 because (15651943 bytes from row 1 to eof) / (2 * 473340 jump0size) == 16
  Type codes (jump 000)    : 5AAA57AA4AAA455554577AAAA4AA2222  Quote rule 0
  Type codes (jump 004)    : 5AAA77AA4AAA455554577AAAA4AA2222  Quote rule 0
  Type codes (jump 010)    : 5AAA77AA4AAA455554577AAAA4AA2222  Quote rule 0
  'header' determined to be true due to column 1 containing a string on row 1 and a lower type (int32) in the rest of the 1049 sample rows
  =====
  Sampled 1049 rows (handled \n inside quoted fields) at 11 jump points
  Bytes from first data row on line 2 to the end of last row: 15651617
  Line length: mean=4777.62 sd=92.31 min=4228 max=5341
  Estimated number of rows: 15651617 / 4777.62 = 3277
  Initial alloc = 3604 rows (3277 + 9%) using bytes/max(mean-2*sd,min) clamped between [1.1*estn, 2.0*estn]
  =====
[08] Assign column names
[09] Apply user overrides on column types
  After 0 type and 0 drop user overrides : 5AAA77AA4AAA455554577AAAA4AA2222
[10] Allocate memory for the datatable
  Allocating 32 column slots (32 - 0 dropped) with 3604 rows
[11] Read the data
  jumps=[0..3), chunk_size=5217205, total_size=15651617
Read 3269 rows x 32 columns from 14.93MB (15651944 bytes) file in 00:00.171 wall clock time
[12] Finalizing the datatable
  Type counts:
         4 : bool8     '2'
         4 : bool8     '4'
         6 : int32     '5'
         4 : float64   '7'
        14 : string    'A'
=============================
   0.009s (  5%) Memory map 0.015GB file
   0.093s ( 54%) sep=',' ncol=32 and header detection
   0.001s (  1%) Column type detection using 1049 sample rows
   0.001s (  1%) Allocation of 3604 rows x 32 cols (0.001GB) of which 3269 ( 91%) rows used
   0.067s ( 39%) Reading 3 chunks (0 swept) of 4.976MB (each chunk 1089 rows) using 3 threads
   +    0.031s ( 18%) Parse to row-major thread buffers (grown 0 times)
   +    0.022s ( 13%) Transpose
   +    0.014s (  8%) Waiting
   0.000s (  0%) Rereading 0 columns due to out-of-sample type exceptions
   0.171s        Total
kickstarter = sapply(kickstarter, FUN = function(X) gsub("\"\"", "\"", X))
kickstarter = data.frame(kickstarter, stringsAsFactors = F)

轉換資料欄位格式

kickstarter$goal <- as.numeric(kickstarter$goal)
kickstarter$pledged <- as.numeric(kickstarter$pledged)
kickstarter$state <- as.factor(kickstarter$state)
kickstarter$disable_communication <- as.factor(kickstarter$disable_communication)
kickstarter$country <- as.factor(kickstarter$country)
kickstarter$currency <- as.factor(kickstarter$currency)
kickstarter$currency_trailing_code <- as.factor(kickstarter$currency_trailing_code)
kickstarter$deadline <- as.numeric(kickstarter$deadline)
kickstarter$state_changed_at <- as.numeric(kickstarter$state_changed_at)
kickstarter$created_at <- as.numeric(kickstarter$created_at)
kickstarter$launched_at <- as.numeric(kickstarter$launched_at)
kickstarter$staff_pick <- as.factor(kickstarter$staff_pick)
# kickstarter$is_starrable <- as.factor(kickstarter$is_starrable)
kickstarter$backers_count <- as.numeric(kickstarter$backers_count)
kickstarter$static_usd_rate <- as.numeric(kickstarter$static_usd_rate)
kickstarter$usd_pledged <- as.numeric(kickstarter$usd_pledged)
kickstarter$spotlight <- as.factor(kickstarter$spotlight)
summary(kickstarter)
      id               photo               name              blurb                goal          
 Length:3269        Length:3269        Length:3269        Length:3269        Min.   :        0  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:      700  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :     2331  
                                                                             Mean   :    52307  
                                                                             3rd Qu.:     5500  
                                                                             Max.   :100000000  
                                                                                                
    pledged              state          slug           disable_communication    country    
 Min.   :     0   canceled  : 418   Length:3269        FALSE:3257            US     :2520  
 1st Qu.:     4   failed    :2085   Class :character   TRUE :  12            GB     : 370  
 Median :   118   live      :  38   Mode  :character                         CA     : 143  
 Mean   :  1780   successful: 716                                            AU     :  61  
 3rd Qu.:   785   suspended :  12                                            NL     :  32  
 Max.   :266305                                                              FR     :  23  
                                                                             (Other): 120  
    currency    currency_symbol    currency_trailing_code    deadline         state_changed_at   
 USD    :2520   Length:3269        FALSE: 507             Min.   :1.243e+09   Min.   :1.243e+09  
 GBP    : 370   Class :character   TRUE :2762             1st Qu.:1.366e+09   1st Qu.:1.366e+09  
 CAD    : 143   Mode  :character                          Median :1.415e+09   Median :1.415e+09  
 EUR    : 133                                             Mean   :1.404e+09   Mean   :1.404e+09  
 AUD    :  61                                             3rd Qu.:1.445e+09   3rd Qu.:1.445e+09  
 SEK    :  19                                             Max.   :1.489e+09   Max.   :1.485e+09  
 (Other):  23                                                                                    
   created_at         launched_at        staff_pick   backers_count    static_usd_rate  
 Min.   :1.242e+09   Min.   :1.242e+09   FALSE:3050   Min.   :   0.0   Min.   :0.04823  
 1st Qu.:1.361e+09   1st Qu.:1.364e+09   TRUE : 219   1st Qu.:   1.0   1st Qu.:1.00000  
 Median :1.409e+09   Median :1.412e+09                Median :   4.0   Median :1.00000  
 Mean   :1.399e+09   Mean   :1.401e+09                Mean   :  24.6   Mean   :1.04528  
 3rd Qu.:1.440e+09   3rd Qu.:1.442e+09                3rd Qu.:  18.0   3rd Qu.:1.00000  
 Max.   :1.484e+09   Max.   :1.485e+09                Max.   :1633.0   Max.   :1.71447  
                                                                                        
  usd_pledged         creator            location           category           profile         
 Min.   :     0.0   Length:3269        Length:3269        Length:3269        Length:3269       
 1st Qu.:     4.2   Class :character   Class :character   Class :character   Class :character  
 Median :   121.0   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :  1537.1                                                                              
 3rd Qu.:   800.0                                                                              
 Max.   :111111.8                                                                              
                                                                                               
 spotlight        urls            source_url          friends           is_starred       
 FALSE:2553   Length:3269        Length:3269        Length:3269        Length:3269       
 TRUE : 716   Class :character   Class :character   Class :character   Class :character  
              Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                         
                                                                                         
                                                                                         
                                                                                         
  is_backing        permissions       
 Length:3269        Length:3269       
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
                                      



Creator Biography

針對creator 找出符合json格式者

vjson = which(sapply(kickstarter$creator, jsonlite::validate) == TRUE)
attributes(vjson) <- NULL
creator = kickstarter$creator[vjson]

轉換json格式data frame

library(rjson)
creator = lapply(creator, fromJSON)

取得creator的網址

creator_url = sapply(X = creator, FUN = function(X) X$urls$web$user)
head(creator_url)
[1] "https://www.kickstarter.com/profile/1785411136"        "https://www.kickstarter.com/profile/686630136"        
[3] "https://www.kickstarter.com/profile/660752087"         "https://www.kickstarter.com/profile/1159279616"       
[5] "https://www.kickstarter.com/profile/littlealicecrafts" "https://www.kickstarter.com/profile/1988843766"       

使用xml2對creator頁面進行爬蟲

library(xml2)
library(XML)

creator的地區

這裡只有retrive 100頁,需要非常久的時間。

creator_location = Reduce(rbind, Map(function(x){
  url = paste0(x, "/about")
  doc = read_html(url)
  
  # 挑出location的文字
  xpath = '//span[contains(@class,"location")]'
  bio = paste0("",xml_text(xml_find_all(doc, xpath)))
  
  # 去除換行及前後空白
  bio = gsub("\n", "", bio)
  bio = gsub("^\\s|\\s$", "", bio)
  
  # 存成data frame
  df = data.frame(url = x, bio)
}, creator_url[1:100]))

View(creator_location)



募資者簡介頁面

針對urls 找出符合json格式者

urljson = which(sapply(kickstarter$urls, jsonlite::validate) == TRUE)
attributes(urljson) <- NULL
projects = kickstarter$urls[urljson]

轉換為json格式

library(rjson)
projects = lapply(projects, fromJSON)

取得urls的project網址

projects_url = sapply(X = projects, FUN = function(X) X$web$project)
head(projects_url)
[1] "https://www.kickstarter.com/projects/1785411136/burnin-4-you-woodburning?ref=category"              
[2] "https://www.kickstarter.com/projects/686630136/reasons-to-be-cheerful?ref=category"                 
[3] "https://www.kickstarter.com/projects/660752087/artwork-by-harriette-harrison?ref=category"          
[4] "https://www.kickstarter.com/projects/1159279616/pixel-pets-5-w-x-3-h-sticker-art?ref=category"      
[5] "https://www.kickstarter.com/projects/littlealicecrafts/the-little-alice-colouring-book?ref=category"
[6] "https://www.kickstarter.com/projects/1988843766/mr-bigfish-vinyl-stickers?ref=category"             

RSelenium

library(dplyr)
package 愼㸱愼㸵dplyr愼㸱愼㸶 was built under R version 3.5.1
Attaching package: 愼㸱愼㸵dplyr愼㸱愼㸶

The following objects are masked from 愼㸱愼㸵package:data.table愼㸱愼㸶:

    between, first, last

The following objects are masked from 愼㸱愼㸵package:stats愼㸱愼㸶:

    filter, lag

The following objects are masked from 愼㸱愼㸵package:base愼㸱愼㸶:

    intersect, setdiff, setequal, union
library(RSelenium)

start a chrome browser

rD <- rsDriver()
checking Selenium Server versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
Creating directory: C:\Users\adm\AppData\Local\binman\binman_seleniumserver\generic\3.14.0
Downloading binary: https://www.googleapis.com/download/storage/v1/b/selenium-release/o/3.14%2F...

BEGIN: POSTDOWNLOAD
checking chromedriver versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
checking geckodriver versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
checking phantomjs versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
[1] "Connecting to remote server"
$`acceptInsecureCerts`
[1] FALSE

$acceptSslCerts
[1] FALSE

$applicationCacheEnabled
[1] FALSE

$browserConnectionEnabled
[1] FALSE

$browserName
[1] "chrome"

$chrome
$chrome$`chromedriverVersion`
[1] "2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e)"

$chrome$userDataDir
[1] "C:\\Users\\adm\\AppData\\Local\\Temp\\scoped_dir15952_23970"


$cssSelectorsEnabled
[1] TRUE

$databaseEnabled
[1] FALSE

$`goog:chromeOptions`
$`goog:chromeOptions`$`debuggerAddress`
[1] "localhost:54493"


$handlesAlerts
[1] TRUE

$hasTouchScreen
[1] FALSE

$javascriptEnabled
[1] TRUE

$locationContextEnabled
[1] TRUE

$mobileEmulationEnabled
[1] FALSE

$nativeEvents
[1] TRUE

$networkConnectionEnabled
[1] FALSE

$pageLoadStrategy
[1] "normal"

$platform
[1] "Windows NT"

$rotatable
[1] FALSE

$setWindowRect
[1] TRUE

$takesHeapSnapshot
[1] TRUE

$takesScreenshot
[1] TRUE

$unexpectedAlertBehaviour
[1] ""

$version
[1] "68.0.3440.106"

$webStorageEnabled
[1] TRUE

$webdriver.remote.sessionid
[1] "09961742d677020761caa6714196ec31"

$id
[1] "09961742d677020761caa6714196ec31"
remDr <- rD[["client"]]
remDr$navigate(projects_url[55])
remDr$navigate('https://www.kickstarter.com/projects/771968170/reveries-an-analog-photo-book-about-guangzhou-chin?ref=home_new_and_noteworthy')

get into the small page

webElem$clickElement()
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
  input string 1 is invalid in this locale

get the information

first created
# xpath: //*[@id="react-project-header"]/div/div/div[7]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[1]/div/div[4]/span/text()
# xpath: //*[@id="react-project-header"]/div/div/div[8]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[1]/div/div[4]/span
webElem <- remDr$findElement(using = 'xpath', '//*[@id="react-project-header"]/div/div/div[7]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[1]/div/div[4]/span')

Selenium message:no such element: Unable to locate element: {"method":"xpath","selector":"//*[@id="react-project-header"]/div/div/div[7]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[1]/div/div[4]/span"}
  (Session info: chrome=68.0.3440.106)
  (Driver info: chromedriver=2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e),platform=Windows NT 10.0.17134 x86_64)

Error:   Summary: NoSuchElement
     Detail: An element could not be located on the page using the given search parameters.
     Further Details: run errorDetails method
created: 曾建立過的專案
created <- regmatches(text, regexpr(".+created", text))
created <- sub("created", "", created)
created <- sub("First", "1", created)
created <- trimws(created)
created
[1] "1"
baked: 曾贊助過的專案
backed <- regmatches(text, regexpr(".+backed", text))
backed <- sub(".+created..", "", backed)
backed <- sub("backed", "", backed)
backed <- trimws(backed)
backed
[1] "0"
facebook.com
# class : block type-16 link-soft-black medium
webElems <- remDr$findElements(using = 'class', "medium")
webElemsClasses <- unlist(lapply(webElems, function(x){x$getElementAttribute("class")}))
webElem <- webElems[[which(webElemsClasses == "block type-16 link-soft-black medium")[1]]]
# xpath: //*[@id="react-project-header"]/div/div/div[8]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[2]/div/a[2]
webElem <- remDr$findElement(using = 'xpath', '//*[@id="react-project-header"]/div/div/div[8]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[2]/div/a[2]')

寫一個MapReduce:)

0812

x = projects_url[5]
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
  input string 1 is invalid in this locale
  # 進入專案頁面
  remDr$navigate(x)
  
  # 進入小page
  webElems <- remDr$findElements(using = 'class', "flex-noshrink")
  webElemsClasses <- unlist(lapply(webElems, function(x){x$getElementAttribute("class")}))
  
  webElem <- webElems[[which(webElemsClasses == "w4 w7-md mb2-md pointer flex-noshrink keyboard-focusable")[1]]]
  webElem$clickElement()
  
  # 找到element
  webElem <- remDr$findElement(using = 'xpath', '//*[@id="react-project-header"]/div/div/div[7]/div/div/div[2]/div/div/div/div/div/div[2]/div/div[2]/div[1]/div/div[4]/span')
  text = webElem$getElementText()
  
  text
[[1]]
[1] "First created · 0 backed"
  

關掉Selenium Server

remDr$close()

# stop the selenium server
rD[["server"]]$stop() 




Comment

針對urls 找出符合json格式者

urljson = which(sapply(kickstarter$urls, jsonlite::validate) == TRUE)
attributes(urljson) <- NULL
projects = kickstarter$urls[urljson]

轉換為json格式

library(rjson)
projects = lapply(projects, fromJSON)

取得urls的project網址

projects_url = sapply(X = projects, FUN = function(X) X$web$project)
head(projects_url)

文字處理,去掉網址後方“?ref=”,並加上 “/comment”

projects_url <- sub(".ref=.*", "", projects_url)
projects_url <- trimws(projects_url)
# projects_url <- paste0(projects_url, "/comments")
projects_url

使用xml2對comments頁面進行爬蟲

library(xml2)
library(magrittr)
library(rvest)

comment <- read_html(url)%>% 
  html_nodes(".collaborator p") %>%
  html_text(.)  

all_comments = Reduce(rbind, Map(function(x){
  url <- paste0(x, "/comments")
  doc <- read_html(url)
  # xpath <- '//*[contains(concat( " ", @class, " " ), concat( " ", "collaborator", " " ))]//p'
  cmt <- paste0("",xml_text(html_nodes(doc, ".collaborator p")))
  cmt <- sub("^@[[:space:]]?[[:alpha:]]+.*\n", "", cmt)
  cmt <- trimws(cmt)
  df <- data.frame(url = x, cmt)
}, projects_url[1:20]))
all_comments[which(all_comments$cmt != ""),1]

creator biography可讀性

# ra_bio = with(all_bio, readability(bio, url))
# ra_bio
# summary(ra_bio)
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8YnI+DQpbZGF0YSBzb3VyY2VdKGh0dHBzOi8vd2Vicm9ib3RzLmlvL2tpY2tzdGFydGVyLWRhdGFzZXRzLykgDQoNCiMgS2lja3N0YXJ0ZXIgU2NyYXBlcg0KDQojIyMg6K6A5YWl6LOH5paZ5Lim5bCH6ZuZ5byV6Jmf6L2J5o+b5q2j56K6DQpgYGB7cn0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCg0Ka2lja3N0YXJ0ZXIgPSBmcmVhZCgiS2lja3N0YXJ0ZXJfMjAxNy0wMS0xNVQyMl8yMV8wNF85ODVaL0tpY2tzdGFydGVyMDQxLmNzdiIsIGRhdGEudGFibGUgPSBGLCB2ZXJib3NlID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpraWNrc3RhcnRlciA9IHNhcHBseShraWNrc3RhcnRlciwgRlVOID0gZnVuY3Rpb24oWCkgZ3N1YigiXCJcIiIsICJcIiIsIFgpKQ0Ka2lja3N0YXJ0ZXIgPSBkYXRhLmZyYW1lKGtpY2tzdGFydGVyLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmBgYA0KDQoNCiMjIyDovYnmj5vos4fmlpnmrITkvY3moLzlvI8NCmBgYHtyfQ0Ka2lja3N0YXJ0ZXIkZ29hbCA8LSBhcy5udW1lcmljKGtpY2tzdGFydGVyJGdvYWwpDQpraWNrc3RhcnRlciRwbGVkZ2VkIDwtIGFzLm51bWVyaWMoa2lja3N0YXJ0ZXIkcGxlZGdlZCkNCmtpY2tzdGFydGVyJHN0YXRlIDwtIGFzLmZhY3RvcihraWNrc3RhcnRlciRzdGF0ZSkNCmtpY2tzdGFydGVyJGRpc2FibGVfY29tbXVuaWNhdGlvbiA8LSBhcy5mYWN0b3Ioa2lja3N0YXJ0ZXIkZGlzYWJsZV9jb21tdW5pY2F0aW9uKQ0Ka2lja3N0YXJ0ZXIkY291bnRyeSA8LSBhcy5mYWN0b3Ioa2lja3N0YXJ0ZXIkY291bnRyeSkNCmtpY2tzdGFydGVyJGN1cnJlbmN5IDwtIGFzLmZhY3RvcihraWNrc3RhcnRlciRjdXJyZW5jeSkNCmtpY2tzdGFydGVyJGN1cnJlbmN5X3RyYWlsaW5nX2NvZGUgPC0gYXMuZmFjdG9yKGtpY2tzdGFydGVyJGN1cnJlbmN5X3RyYWlsaW5nX2NvZGUpDQpraWNrc3RhcnRlciRkZWFkbGluZSA8LSBhcy5udW1lcmljKGtpY2tzdGFydGVyJGRlYWRsaW5lKQ0Ka2lja3N0YXJ0ZXIkc3RhdGVfY2hhbmdlZF9hdCA8LSBhcy5udW1lcmljKGtpY2tzdGFydGVyJHN0YXRlX2NoYW5nZWRfYXQpDQpraWNrc3RhcnRlciRjcmVhdGVkX2F0IDwtIGFzLm51bWVyaWMoa2lja3N0YXJ0ZXIkY3JlYXRlZF9hdCkNCmtpY2tzdGFydGVyJGxhdW5jaGVkX2F0IDwtIGFzLm51bWVyaWMoa2lja3N0YXJ0ZXIkbGF1bmNoZWRfYXQpDQpraWNrc3RhcnRlciRzdGFmZl9waWNrIDwtIGFzLmZhY3RvcihraWNrc3RhcnRlciRzdGFmZl9waWNrKQ0KIyBraWNrc3RhcnRlciRpc19zdGFycmFibGUgPC0gYXMuZmFjdG9yKGtpY2tzdGFydGVyJGlzX3N0YXJyYWJsZSkNCmtpY2tzdGFydGVyJGJhY2tlcnNfY291bnQgPC0gYXMubnVtZXJpYyhraWNrc3RhcnRlciRiYWNrZXJzX2NvdW50KQ0Ka2lja3N0YXJ0ZXIkc3RhdGljX3VzZF9yYXRlIDwtIGFzLm51bWVyaWMoa2lja3N0YXJ0ZXIkc3RhdGljX3VzZF9yYXRlKQ0Ka2lja3N0YXJ0ZXIkdXNkX3BsZWRnZWQgPC0gYXMubnVtZXJpYyhraWNrc3RhcnRlciR1c2RfcGxlZGdlZCkNCmtpY2tzdGFydGVyJHNwb3RsaWdodCA8LSBhcy5mYWN0b3Ioa2lja3N0YXJ0ZXIkc3BvdGxpZ2h0KQ0Kc3VtbWFyeShraWNrc3RhcnRlcikNCmBgYA0KDQoNCjxicj4gPGJyPiA8aHI+DQoNCiMgQ3JlYXRvciBCaW9ncmFwaHkNCiMjIyMg6Yed5bCNY3JlYXRvciDmib7lh7rnrKblkIhqc29u5qC85byP6ICFDQpgYGB7cn0NCnZqc29uID0gd2hpY2goc2FwcGx5KGtpY2tzdGFydGVyJGNyZWF0b3IsIGpzb25saXRlOjp2YWxpZGF0ZSkgPT0gVFJVRSkNCmF0dHJpYnV0ZXModmpzb24pIDwtIE5VTEwNCmNyZWF0b3IgPSBraWNrc3RhcnRlciRjcmVhdG9yW3Zqc29uXQ0KYGBgDQoNCiMjIyMg6L2J5o+banNvbuagvOW8j2RhdGEgZnJhbWUNCmBgYHtyfQ0KbGlicmFyeShyanNvbikNCmNyZWF0b3IgPSBsYXBwbHkoY3JlYXRvciwgZnJvbUpTT04pDQpgYGANCg0KIyMjIyDlj5blvpdjcmVhdG9y55qE57ay5Z2ADQpgYGB7cn0NCmNyZWF0b3JfdXJsID0gc2FwcGx5KFggPSBjcmVhdG9yLCBGVU4gPSBmdW5jdGlvbihYKSBYJHVybHMkd2ViJHVzZXIpDQpoZWFkKGNyZWF0b3JfdXJsKQ0KYGBgDQoNCiMjIyDkvb/nlKh4bWwy5bCNY3JlYXRvcumggemdoumAsuihjOeIrOifsg0KYGBge3J9DQpsaWJyYXJ5KHhtbDIpDQpsaWJyYXJ5KFhNTCkNCmBgYA0KDQoNCiMjIyMgY3JlYXRvcueahOWcsOWNgA0KDQrpgJnoo6Hlj6rmnIlyZXRyaXZlIDEwMOmgge+8jOmcgOimgemdnuW4uOS5heeahOaZgumWk+OAgg0KYGBge3J9DQpjcmVhdG9yX2xvY2F0aW9uID0gUmVkdWNlKHJiaW5kLCBNYXAoZnVuY3Rpb24oeCl7DQogIHVybCA9IHBhc3RlMCh4LCAiL2Fib3V0IikNCiAgZG9jID0gcmVhZF9odG1sKHVybCkNCiAgDQogICMg5oyR5Ye6bG9jYXRpb27nmoTmloflrZcNCiAgeHBhdGggPSAnLy9zcGFuW2NvbnRhaW5zKEBjbGFzcywibG9jYXRpb24iKV0nDQogIGJpbyA9IHBhc3RlMCgiIix4bWxfdGV4dCh4bWxfZmluZF9hbGwoZG9jLCB4cGF0aCkpKQ0KICANCiAgIyDljrvpmaTmj5vooYzlj4rliY3lvoznqbrnmb0NCiAgYmlvID0gZ3N1YigiXG4iLCAiIiwgYmlvKQ0KICBiaW8gPSBnc3ViKCJeXFxzfFxccyQiLCAiIiwgYmlvKQ0KICANCiAgIyDlrZjmiJBkYXRhIGZyYW1lDQogIGRmID0gZGF0YS5mcmFtZSh1cmwgPSB4LCBiaW8pDQp9LCBjcmVhdG9yX3VybFsxOjEwMF0pKQ0KDQpWaWV3KGNyZWF0b3JfbG9jYXRpb24pDQpgYGANCg0KPGJyPiA8YnI+IDxocj4NCg0KDQojIOWLn+izh+iAheewoeS7i+mggemdog0KDQoqIEZCIGZyaWVuZHMNCiogbnVtYmVyIG9mIGJhY2tlZA0KKiBudW1iZXIgb2YgY3JlYXRlZA0KDQojIyMjIOmHneWwjXVybHMg5om+5Ye656ym5ZCIanNvbuagvOW8j+iAhQ0KYGBge3J9DQp1cmxqc29uID0gd2hpY2goc2FwcGx5KGtpY2tzdGFydGVyJHVybHMsIGpzb25saXRlOjp2YWxpZGF0ZSkgPT0gVFJVRSkNCmF0dHJpYnV0ZXModXJsanNvbikgPC0gTlVMTA0KcHJvamVjdHMgPSBraWNrc3RhcnRlciR1cmxzW3VybGpzb25dDQpgYGANCg0KIyMjIyDovYnmj5vngrpqc29u5qC85byPDQpgYGB7cn0NCmxpYnJhcnkocmpzb24pDQpwcm9qZWN0cyA9IGxhcHBseShwcm9qZWN0cywgZnJvbUpTT04pDQpgYGANCg0KIyMjIyDlj5blvpd1cmxz55qEcHJvamVjdOe2suWdgA0KYGBge3J9DQpwcm9qZWN0c191cmwgPSBzYXBwbHkoWCA9IHByb2plY3RzLCBGVU4gPSBmdW5jdGlvbihYKSBYJHdlYiRwcm9qZWN0KQ0KaGVhZChwcm9qZWN0c191cmwpDQpgYGANCg0KIyMjIFJTZWxlbml1bQ0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShSU2VsZW5pdW0pDQpgYGANCg0KIyMjIyBzdGFydCBhIGNocm9tZSBicm93c2VyDQpgYGB7cn0NCnJEIDwtIHJzRHJpdmVyKCkNCnJlbURyIDwtIHJEW1siY2xpZW50Il1dDQpyZW1EciRuYXZpZ2F0ZShwcm9qZWN0c191cmxbNTVdKQ0KcmVtRHIkbmF2aWdhdGUoJ2h0dHBzOi8vd3d3LmtpY2tzdGFydGVyLmNvbS9wcm9qZWN0cy83NzE5NjgxNzAvcmV2ZXJpZXMtYW4tYW5hbG9nLXBob3RvLWJvb2stYWJvdXQtZ3Vhbmd6aG91LWNoaW4/cmVmPWhvbWVfbmV3X2FuZF9ub3Rld29ydGh5JykNCmBgYA0KDQojIyMjIGdldCBpbnRvIHRoZSBzbWFsbCBwYWdlIA0KYGBge3J9DQojIGEgY2xhc3PvvJogbTAgcDAgbWVkaXVtIHNvZnQtYmxhY2sgdHlwZS0xNCBwb2ludGVyIGtleWJvYXJkLWZvY3VzYWJsZQ0KIyBzcGFuIGNsYXNzIDogc29mdC1ibGFjayBtbDIgbWwwLW1kIGJyZWFrLXdvcmQNCiMgaW1hZ2UgYnV0dG9uIGEgY2xhc3MgOiB3NCB3Ny1tZCBtYjItbWQgcG9pbnRlciBmbGV4LW5vc2hyaW5rIGtleWJvYXJkLWZvY3VzYWJsZQ0Kd2ViRWxlbXMgPC0gcmVtRHIkZmluZEVsZW1lbnRzKHVzaW5nID0gJ2NsYXNzJywgImZsZXgtbm9zaHJpbmsiKQ0KDQoNCiMgIyB4cGF0aDogLy8qW2NvbnRhaW5zKGNvbmNhdCggIiAiLCBAY2xhc3MsICIgIiApLCBjb25jYXQoICIgIiwgInBvaW50ZXIiLCAiICIgKSldDQojIHdlYkVsZW0gPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAneHBhdGgnLCAiLy8qL2lucHV0W0BjbGFzcyA9ICdnYnFmaWYnXSIpDQojIA0KIyAjIGNzcy1zZWxlY3Rvcg0KIyB3ZWJFbGVtIDwtIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gJ2NzcyBzZWxlY3RvcicsICJhLnBvaW50ZXIiKQ0KDQoNCndlYkVsZW1zQ2xhc3NlcyA8LSB1bmxpc3QobGFwcGx5KHdlYkVsZW1zLCBmdW5jdGlvbih4KXt4JGdldEVsZW1lbnRBdHRyaWJ1dGUoImNsYXNzIil9KSkNCg0Kd2ViRWxlbSA8LSB3ZWJFbGVtc1tbd2hpY2god2ViRWxlbXNDbGFzc2VzID09ICJ3NCB3Ny1tZCBtYjItbWQgcG9pbnRlciBmbGV4LW5vc2hyaW5rIGtleWJvYXJkLWZvY3VzYWJsZSIpWzFdXV0NCg0Kd2ViRWxlbXNbW3doaWNoKHdlYkVsZW1zQ2xhc3NlcyA9PSAidzQgdzctbWQgbWIyLW1kIHBvaW50ZXIgZmxleC1ub3NocmluayBrZXlib2FyZC1mb2N1c2FibGUiKVsxXV1dDQoNCndlYkVsZW0kY2xpY2tFbGVtZW50KCkNCmBgYA0KDQoNCg0KIyMjIyBnZXQgdGhlIGluZm9ybWF0aW9uDQojIyMjIyBmaXJzdCBjcmVhdGVkDQpgYGB7cn0NCiMgeHBhdGg6IC8vKltAaWQ9InJlYWN0LXByb2plY3QtaGVhZGVyIl0vZGl2L2Rpdi9kaXZbN10vZGl2L2Rpdi9kaXZbMl0vZGl2L2Rpdi9kaXYvZGl2L2Rpdi9kaXZbMl0vZGl2L2RpdlsyXS9kaXZbMV0vZGl2L2Rpdls0XS9zcGFuL3RleHQoKQ0KIyB4cGF0aDogLy8qW0BpZD0icmVhY3QtcHJvamVjdC1oZWFkZXIiXS9kaXYvZGl2L2Rpdls4XS9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2L2Rpdi9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2WzJdL2RpdlsxXS9kaXYvZGl2WzRdL3NwYW4NCg0Kd2ViRWxlbSA8LSByZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICd4cGF0aCcsICcvLypbQGlkPSJyZWFjdC1wcm9qZWN0LWhlYWRlciJdL2Rpdi9kaXYvZGl2WzddL2Rpdi9kaXYvZGl2WzJdL2Rpdi9kaXYvZGl2L2Rpdi9kaXYvZGl2WzJdL2Rpdi9kaXZbMl0vZGl2WzFdL2Rpdi9kaXZbNF0vc3BhbicpDQoNCndlYkVsZW0gPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAneHBhdGgnLCAnLy8qW0BpZD0icmVhY3QtcHJvamVjdC1oZWFkZXIiXS9kaXYvZGl2L2Rpdls4XS9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2L2Rpdi9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2WzJdL2RpdlsxXS9kaXYvZGl2WzRdL3NwYW4nKQ0KDQp0ZXh0IDwtIHdlYkVsZW0kZ2V0RWxlbWVudFRleHQoKQ0KYGBgDQoNCg0KIyMjIyMgY3JlYXRlZDog5pu+5bu656uL6YGO55qE5bCI5qGIDQpgYGB7cn0NCmNyZWF0ZWQgPC0gcmVnbWF0Y2hlcyh0ZXh0LCByZWdleHByKCIuK2NyZWF0ZWQiLCB0ZXh0KSkNCmNyZWF0ZWQgPC0gc3ViKCJjcmVhdGVkIiwgIiIsIGNyZWF0ZWQpDQpjcmVhdGVkIDwtIHN1YigiRmlyc3QiLCAiMSIsIGNyZWF0ZWQpDQpjcmVhdGVkIDwtIHRyaW13cyhjcmVhdGVkKQ0KY3JlYXRlZA0KYGBgDQoNCg0KIyMjIyMgYmFrZWQ6IOabvui0iuWKqemBjueahOWwiOahiA0KYGBge3J9DQpiYWNrZWQgPC0gcmVnbWF0Y2hlcyh0ZXh0LCByZWdleHByKCIuK2JhY2tlZCIsIHRleHQpKQ0KYmFja2VkIDwtIHN1YigiLitjcmVhdGVkLi4iLCAiIiwgYmFja2VkKQ0KYmFja2VkIDwtIHN1YigiYmFja2VkIiwgIiIsIGJhY2tlZCkNCmJhY2tlZCA8LSB0cmltd3MoYmFja2VkKQ0KYmFja2VkDQpgYGANCg0KIyMjIyMgZmFjZWJvb2suY29tDQpgYGB7cn0NCiMgY2xhc3MgOiBibG9jayB0eXBlLTE2IGxpbmstc29mdC1ibGFjayBtZWRpdW0NCndlYkVsZW1zIDwtIHJlbURyJGZpbmRFbGVtZW50cyh1c2luZyA9ICdjbGFzcycsICJtZWRpdW0iKQ0Kd2ViRWxlbXNDbGFzc2VzIDwtIHVubGlzdChsYXBwbHkod2ViRWxlbXMsIGZ1bmN0aW9uKHgpe3gkZ2V0RWxlbWVudEF0dHJpYnV0ZSgiY2xhc3MiKX0pKQ0Kd2ViRWxlbSA8LSB3ZWJFbGVtc1tbd2hpY2god2ViRWxlbXNDbGFzc2VzID09ICJibG9jayB0eXBlLTE2IGxpbmstc29mdC1ibGFjayBtZWRpdW0iKVsxXV1dDQoNCiMgeHBhdGg6IC8vKltAaWQ9InJlYWN0LXByb2plY3QtaGVhZGVyIl0vZGl2L2Rpdi9kaXZbOF0vZGl2L2Rpdi9kaXZbMl0vZGl2L2Rpdi9kaXYvZGl2L2Rpdi9kaXZbMl0vZGl2L2RpdlsyXS9kaXZbMl0vZGl2L2FbMl0NCndlYkVsZW0gPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAneHBhdGgnLCAnLy8qW0BpZD0icmVhY3QtcHJvamVjdC1oZWFkZXIiXS9kaXYvZGl2L2Rpdls4XS9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2L2Rpdi9kaXYvZGl2L2RpdlsyXS9kaXYvZGl2WzJdL2RpdlsyXS9kaXYvYVsyXScpDQpgYGANCg0KIyMjIOWvq+S4gOWAi01hcFJlZHVjZTopDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyAwODEyDQpgYGB7cn0NCmNyZWF0ZWRfYmFja2VkX2RmID0gUmVkdWNlKHJiaW5kLCBNYXAoZnVuY3Rpb24oeCl7DQogIA0KICB4ID0gcHJvamVjdHNfdXJsWzVdDQogICMg6YCy5YWl5bCI5qGI6aCB6Z2iDQogIHJlbURyJG5hdmlnYXRlKHgpDQogIA0KICAjIOmAsuWFpeWwj3BhZ2UNCiAgd2ViRWxlbXMgPC0gcmVtRHIkZmluZEVsZW1lbnRzKHVzaW5nID0gJ2NsYXNzJywgImZsZXgtbm9zaHJpbmsiKQ0KICB3ZWJFbGVtc0NsYXNzZXMgPC0gdW5saXN0KGxhcHBseSh3ZWJFbGVtcywgZnVuY3Rpb24oeCl7eCRnZXRFbGVtZW50QXR0cmlidXRlKCJjbGFzcyIpfSkpDQogIA0KICB3ZWJFbGVtIDwtIHdlYkVsZW1zW1t3aGljaCh3ZWJFbGVtc0NsYXNzZXMgPT0gInc0IHc3LW1kIG1iMi1tZCBwb2ludGVyIGZsZXgtbm9zaHJpbmsga2V5Ym9hcmQtZm9jdXNhYmxlIilbMV1dXQ0KICB3ZWJFbGVtJGNsaWNrRWxlbWVudCgpDQogIA0KICAjIOaJvuWIsGVsZW1lbnQNCiAgd2ViRWxlbSA8LSByZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICd4cGF0aCcsICcvLypbQGlkPSJyZWFjdC1wcm9qZWN0LWhlYWRlciJdL2Rpdi9kaXYvZGl2WzddL2Rpdi9kaXYvZGl2WzJdL2Rpdi9kaXYvZGl2L2Rpdi9kaXYvZGl2WzJdL2Rpdi9kaXZbMl0vZGl2WzFdL2Rpdi9kaXZbNF0vc3BhbicpDQogIHRleHQgPSB3ZWJFbGVtJGdldEVsZW1lbnRUZXh0KCkNCiAgDQogIHRleHQNCiAgDQogICMg5a2Y5oiQZGF0YSBmcmFtZQ0KICBkZiA9IGRhdGEuZnJhbWUodGV4dCkgIA0KDQp9LCBwcm9qZWN0c191cmxbMTo1XSkpDQoNCmBgYA0KDQojIyMjIOmXnOaOiVNlbGVuaXVtIFNlcnZlcg0KYGBge3J9DQpyZW1EciRjbG9zZSgpDQoNCiMgc3RvcCB0aGUgc2VsZW5pdW0gc2VydmVyDQpyRFtbInNlcnZlciJdXSRzdG9wKCkgDQpgYGANCg0KDQoNCg0KDQoNCg0KDQotLS0NCg0KPGJyPiA8YnI+IDxocj4NCg0KIyBDb21tZW50DQojIyMjIOmHneWwjXVybHMg5om+5Ye656ym5ZCIanNvbuagvOW8j+iAhQ0KYGBge3J9DQp1cmxqc29uID0gd2hpY2goc2FwcGx5KGtpY2tzdGFydGVyJHVybHMsIGpzb25saXRlOjp2YWxpZGF0ZSkgPT0gVFJVRSkNCmF0dHJpYnV0ZXModXJsanNvbikgPC0gTlVMTA0KcHJvamVjdHMgPSBraWNrc3RhcnRlciR1cmxzW3VybGpzb25dDQpgYGANCg0KIyMjIyDovYnmj5vngrpqc29u5qC85byPDQpgYGB7cn0NCmxpYnJhcnkocmpzb24pDQpwcm9qZWN0cyA9IGxhcHBseShwcm9qZWN0cywgZnJvbUpTT04pDQpgYGANCg0KDQojIyMjIOWPluW+l3VybHPnmoRwcm9qZWN057ay5Z2ADQpgYGB7cn0NCnByb2plY3RzX3VybCA9IHNhcHBseShYID0gcHJvamVjdHMsIEZVTiA9IGZ1bmN0aW9uKFgpIFgkd2ViJHByb2plY3QpDQpoZWFkKHByb2plY3RzX3VybCkNCmBgYA0KDQoNCiMjIyMg5paH5a2X6JmV55CG77yM5Y675o6J57ay5Z2A5b6M5pa5Ij9yZWY9Iu+8jOS4puWKoOS4iiAiL2NvbW1lbnQiDQpgYGB7cn0NCnByb2plY3RzX3VybCA8LSBzdWIoIi5yZWY9LioiLCAiIiwgcHJvamVjdHNfdXJsKQ0KcHJvamVjdHNfdXJsIDwtIHRyaW13cyhwcm9qZWN0c191cmwpDQojIHByb2plY3RzX3VybCA8LSBwYXN0ZTAocHJvamVjdHNfdXJsLCAiL2NvbW1lbnRzIikNCnByb2plY3RzX3VybA0KYGBgDQoNCiMjIyMg5L2/55SoeG1sMuWwjWNvbW1lbnRz6aCB6Z2i6YCy6KGM54is6J+yDQpgYGB7cn0NCmxpYnJhcnkoeG1sMikNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KHJ2ZXN0KQ0KDQpjb21tZW50IDwtIHJlYWRfaHRtbCh1cmwpJT4lIA0KICBodG1sX25vZGVzKCIuY29sbGFib3JhdG9yIHAiKSAlPiUNCiAgaHRtbF90ZXh0KC4pICANCg0KYWxsX2NvbW1lbnRzID0gUmVkdWNlKHJiaW5kLCBNYXAoZnVuY3Rpb24oeCl7DQogIHVybCA8LSBwYXN0ZTAoeCwgIi9jb21tZW50cyIpDQogIGRvYyA8LSByZWFkX2h0bWwodXJsKQ0KICAjIHhwYXRoIDwtICcvLypbY29udGFpbnMoY29uY2F0KCAiICIsIEBjbGFzcywgIiAiICksIGNvbmNhdCggIiAiLCAiY29sbGFib3JhdG9yIiwgIiAiICkpXS8vcCcNCiAgY210IDwtIHBhc3RlMCgiIix4bWxfdGV4dChodG1sX25vZGVzKGRvYywgIi5jb2xsYWJvcmF0b3IgcCIpKSkNCiAgY210IDwtIHN1YigiXkBbWzpzcGFjZTpdXT9bWzphbHBoYTpdXSsuKlxuIiwgIiIsIGNtdCkNCiAgY210IDwtIHRyaW13cyhjbXQpDQogIGRmIDwtIGRhdGEuZnJhbWUodXJsID0geCwgY210KQ0KfSwgcHJvamVjdHNfdXJsWzE6MjBdKSkNCmFsbF9jb21tZW50c1t3aGljaChhbGxfY29tbWVudHMkY210ICE9ICIiKSwxXQ0KDQpgYGANCg0KDQoNCg0KIyMjIGNyZWF0b3IgYmlvZ3JhcGh55Y+v6K6A5oCnDQpgYGB7cn0NCiMgcmFfYmlvID0gd2l0aChhbGxfYmlvLCByZWFkYWJpbGl0eShiaW8sIHVybCkpDQojIHJhX2Jpbw0KIyBzdW1tYXJ5KHJhX2JpbykNCmBgYA==