September, 2017

Web scraping tool

알아둘 것

  • 모든 웹페이지는 긁을 수 있다.
  • 하지만 모든 사람이 할 수 있는 것은 아니다.

rvest

  • URL의 확인: http://tvcast.naver.com/jtbc.youth
  • 페이지 구조 확인: 크롬에서 마우스 오른쪽 클릭 후 검사 버튼 클릭
  • 트리 구조를 확인하고 탐색하고 있는 텍스트 주변의 태그들 확인하기
  • 회차별 설명 텍스트를 모으기

rvest

read.html 함수

  • read.html 함수는 지정한 URL에서 클라이언트 컴퓨터로 보내주는 페이지를 저장한다.
if(!require(rvest)){install.packages('rvest') ; library(rvest)}
## Loading required package: rvest
## Loading required package: xml2
url_tvcast = 'http://tvcast.naver.com/jtbc.youth'
html_tvcast = read_html(x = url_tvcast, encoding = 'UTF-8')

rvest

html_nodes

  • html_nodes 함수는 참조하는 웹페이지에서 태그 하위에 있는 텍스트를 저장한다.
  • "태그.class 태그" 혹은 "태그#id 태그" 를 사용한다.
html_tvcast %>% html_nodes(".title a") %>% head(n=3)
## {xml_nodeset (3)}
## [1] <a href="http://tv.naver.com/v/1081631" onclick="clickcr(this, 'chh. ...
## [2] <a href="/v/1081631" onclick="clickcr(this, 'chh.vtit', '', '', ''); ...
## [3] <a href="/v/1081613" onclick="clickcr(this, 'chh.vtit', '', '', ''); ...
  • title class 아래 쪽 하이퍼링크 태그 \(<\)a\(>\) 사이에 문자열을 모은다.

rvest

html_text

  • html_text 함수는 참조하는 텍스트 문자열에서 text 문자열 정보를 모은다.
html_tvcast %>% html_nodes(".title a") %>% html_text()%>%head(n=3)
## [1] "[워맨스 스페셜]나도 이런 연ㅇ..아니 우정 하고 싶다..."
## [2] "[워맨스 스페셜]나도 이런 연ㅇ..아니 우정 하고 싶다..."
## [3] "[메이킹] '똥차' 고두영 시원하게 때려잡는 하메들!"

rvest

rvest

예제

url_tvcast = 'http://tv.naver.com/mbc.kingloves'
html_tvcast = read_html(x = url_tvcast, encoding = 'UTF-8')
html_tvcast %>% html_nodes(".title a") %>% html_text() %>% 
  data.frame() %>% head(n = 3)
##                                                             .
## 1        임시완, 윤아·홍종현에 대한 '애절한 그리움'으로 엔딩
## 2  《메이킹》 '왕은 사랑한다' 178일간의 대장정, 그들의 이야기
## 3 《메이킹》 임시완·임윤아·홍종현,  '원산린' 우리 이제 안녕

rvest

Wikipedia 테이블 수집

  • URL의 확인: https://en.wikipedia.org/wiki/Student%27s_t-distribution
  • T분포 표의 테이블을 수집하자.
  • 페이지 구조 확인: 크롬에서 마우스 오른쪽 클릭 후 검사 버튼 클릭
  • 트리 구조를 확인하고 탐색하고 있는 텍스트 주변의 태그들 확인하기

rvest

Wikipedia 테이블 수집

  • classwikitable 임을 확인
  • 페이지 받기
url_wiki <- "https://en.wikipedia.org/wiki/Student%27s_t-distribution"
html_wiki <- read_html(x=url_wiki, encoding = 'UFT-8')

rvest

Wikipedia 테이블 수집

  • html_table 함수는 저장된 html 문서에서 테이블값을 저장해주는 함수임
html_wiki %>% html_nodes('.wikitable') %>% html_table() %>% 
  data.frame() %>% head(n=3)
##   One.sided  X75.  X80.  X85.  X90.  X95. X97.5.  X99. X99.5. X99.75.
## 1 Two-sided   50%   60%   70%   80%   90%    95%   98%    99%   99.5%
## 2         1 1.000 1.376 1.963 3.078 6.314  12.71 31.82  63.66   127.3
## 3         2 0.816 1.080 1.386 1.886 2.920  4.303 6.965  9.925   14.09
##   X99.9. X99.95.
## 1  99.8%   99.9%
## 2  318.3   636.6
## 3  22.33   31.60

rvest

MLB 경기기록 수집

if(!require(rvest)){install.packages("rvest"); library(rvest)}

url <- "http://www.baseball-reference.com/leagues/MLB/2017.shtml"
webpage <- read_html(url)

rvest

MLB 경기기록 수집

  • 기록 테이블을 확인하고 구조를 확인하여라.
  • id 는 웹페이지에서 유일한 값이기 때문에 web scarping에 유용한 정보다.

rvest

MLB 경기기록 수집

webpage %>% html_nodes('div#div_teams_standard_batting table') %>% 
  html_table() %>% data.frame() %>% head(n = 3)
##    Tm X.Bat BatAge  R.G   G   PA   AB   R    H X2B X3B  HR RBI  SB CS  BB
## 1 ARI    45    0.0 5.01 162 6224 5525 812 1405 314  39 220 776 103 30 578
## 2 ATL    49    0.0 4.52 162 6216 5584 732 1467 289  26 165 706  77 31 474
## 3 BAL    50    0.0 4.59 162 6140 5650 743 1469 269  12 232 713  32 13 392
##     SO   BA  OBP  SLG  OPS OPS.   TB GDP HBP SH SF IBB  LOB
## 1 1456 .254 .329 .445 .774      2457 106  54 39 27  44 1118
## 2 1184 .263 .326 .412 .738      2303 137  66 59 32  57 1127
## 3 1412 .260 .312 .435 .747      2458 138  50 10 37  12 1041

rvest

MLB 경기기록 수집

  • 1950년 부터 2017년까지 Team Standard Batting 자료를 모두 저장하여라.
  • for, paste 를 활용하면 가능함.

rvest

rvest

기상청 데이터 수집

  • F12를 눌러 개발자 도구 창을 열기

rvest

기상청 데이터 수집

  • 네트워크 창을 통해 어떤 정보들이 처리되는지 확인
  • Type 이 Document인 것을 확인
  • Method의 확인: GET 방식 (URL을 변경하여 요청을 보낼 수 있음, 기상청은 로그인 정보를 비롯한 요청을 보내는 헤더에 부가 정보가 필요없으므로 URL을 변경함으로써 페이지 요청 가능)
  • 검사 버튼을 이요해서 테이블 구조 파악: <table class="table_develop3" summary="기상실황표로 지점, 날씨, 기온, 강수, 바람, 기압등을 안내한 표입니다.">

rvest

기상청 데이터 수집

  • html_table 을 이용하여 테이블에 입력된 텍스트만 수집한다.
url = "http://www.weather.go.kr/weather/observation/currentweather.jsp?auto_man=m&type=t99&tm=2017.09.06.13%3A00&x=19&y=3"
webpage <- read_html(url, encoding = "EUC-KR")

Sys.setlocale("LC_ALL", "English")

webpage %>% html_nodes("table.table_develop3")
tmp <- webpage %>% html_nodes("table.table_develop3") %>% 
  html_table(header = FALSE, fill=TRUE)%>%
  data.frame()
head(tmp)

rvest

기상청 데이터 수집

  • 인코딩의 변경: rvest 라이브러리에 있는 repair_encoding 함수의 이용
Sys.setlocale("LC_ALL", "Korean")

for(i in 1:ncol(tmp)){
  tmp[,i] = rvest::repair_encoding(tmp[,i])
}
head(tmp)

rvest

rvest

네이버 영화 평점 웹데이터 수집

if(!require(httr)){install.packages('httr') ; library(httr)}

url = paste0("https://movie.naver.com/movie/point/af/list.nhn?&page=1")
mov_html = read_html(GET(url), Encoding = "UTF-8")
  
content = html_nodes(mov_html, '.title') %>% html_text()
content = gsub('\n|\t|<.*?>|&quot;','',content)
  
sub_con = strsplit(content, "\r")
data.frame(do.call(rbind, 
                     lapply(sub_con, function(x) {x[x != "" & x != "신고"]}))) %>% head(3)

rvest

네이버 영화 평점 웹데이터 수집

  • for문을 이용하여 page를 변화 시키며 데이터를 가져와보자.
total_con = NULL

for(i in 1:10){
  url = paste0("https://movie.naver.com/movie/point/af/list.nhn?&page=",i)
  mov_html = read_html(GET(url), encoding = "CP949")
  content = html_nodes(mov_html, '.title') %>% html_text()
  content = gsub('\n|\t|<.*?>|&quot;','',content)
  part_con = data.frame(do.call(rbind, 
              lapply(strsplit(content, "\r"), function(x) {x[x != "" & x != "신고"]})))
  total_con = rbind(total_con, part_con)
  cat(i, "\n")
}

rvest

네이버 영화 평점 웹데이터 수집

  • 추가적으로 평점 정보를 추가해보자.
  • 구조를 보면 point class에 점수가 지정되어 있음을 알 수 있다.
total_dat = NULL

for(i in 1:10){
  url = paste0("https://movie.naver.com/movie/point/af/list.nhn?&page=",i)
  mov_html = read_html(GET(url), encoding = "CP949")
  content = html_nodes(mov_html, '.title') %>% html_text()
  content = gsub('\n|\t|<.*?>|&quot;','',content)
  point = html_nodes(mov_html, '.point') %>% html_text()
  part_con = data.frame(do.call(rbind, 
              lapply(strsplit(content, "\r"), function(x) {x[x != "" & x != "신고"]})), point = point)
  total_dat = rbind(total_dat, part_con)
  cat(i, "\n")
}

Naver API

API

API?

  • Application interface: 서버컴퓨터가 어떤 서비스를 제공하기 위해서 클라이언트 컴퓨터와 데이터를 주고 받기 위한 메세지 형식
  • API를 이용하여 클라이언트 컴퓨터가 서버 컴퓨터에게 메세지를 보내고, 정해진 형식에 맞춰 데이터를 받게 됨.
  • 검색 API를 받아보자

API

API 받기

  • 준비물: 핸드폰(본인확인용)
  • 네이버 개발자 센터("https://developers.naver.com/main/")
  • Documents > 서비스API > 검색 > 블로그
  • 우리는 XML 포맷으로 데이터를 주고 받을 것이다. (2. API 기본정보에서 요청 URL 확인)
  • GET 방식
  • 요청변수의 확인: query,display,start, sort

API

header의 처리

  • GET 방식으로 요청할 때 header에 client_id와 client_secret을 URL 과 함께 같이 보내야 한다.
  • httr::add_headers 함수의 이용
client_id = '???';
client_secret = '???';
header = httr::add_headers(
      'X-Naver-Client-Id' = client_id,
      'X-Naver-Client-Secret' = client_secret)

API

API

URL의 처리

  • 단 query는 UTF-8로 변환해서 URL을 작성해야 한다.
  • iconv 함수의 이용
query = '새우깡'
# encoding 변화
query = iconv(query, to = 'UTF-8', toRaw = T)
# iconv(query, to = "UTF-8", toRaw = F)
query = paste0('%', paste(unlist(query), collapse = '%'))
query = toupper(query)

API

URL의 저장

  • 요청변수를 작성하여 URL을 완성하자.
  • 다음 read_xml 함수와 GET 함수를 이용하여 요청 문서를 저장한다.
if(!require(httr)){install.packages("httr"); library(httr)}

end_num = 1000
display_num = 100
start_point = seq(1,end_num,display_num)
i = 1
url = paste0('https://openapi.naver.com/v1/search/blog.xml?query=',
    query,'&display=',display_num,'&start=',
    start_point[i],'&sort=sim')
url_body = read_xml(GET(url, header))

API

텍스트 정보의 추출

title = url_body %>% xml_nodes('item title') %>%
  xml_text()
bloggername = url_body %>% 
  xml_nodes('item bloggername') %>% xml_text()
postdate = url_body %>% xml_nodes('postdate') %>%
  xml_text()
link = url_body %>% xml_nodes('item link') %>%
  xml_text()
description = url_body %>% xml_nodes('item description') %>%
  html_text()

API

Rcode

i = 1
final_dat = NULL
for(i in 1:length(start_point))
{
  # request xml format
  url = paste0('https://openapi.naver.com/v1/search/blog.xml?query=',query,'&display=',display_num,'&start=',start_point[i],'&sort=sim')
  #option header
  url_body = read_xml(GET(url, header), encoding = "UTF-8")
  title = url_body %>% xml_nodes('item title') %>% xml_text()
  bloggername = url_body %>% xml_nodes('item bloggername') %>% xml_text()
  postdate = url_body %>% xml_nodes('postdate') %>% xml_text()
  link = url_body %>% xml_nodes('item link') %>% xml_text()
  description = url_body %>% xml_nodes('item description') %>% html_text()
  temp_dat = cbind(title, bloggername, postdate, link, description)
  final_dat = rbind(final_dat, temp_dat)
  cat(i, '\n')
}
final_dat = data.frame(final_dat, stringsAsFactors = F)
head(final_dat)

서울 열린 데이터 광장 사용

API

  • 서울 열린데이터 광장에서 데이터를 API를 이용해 불러오기.
  • 서울 열린데이터 홈페이지 ("http://data.seoul.go.kr/")에서 API key를 생성한다.
  • XML 포멧으로 받을 것이다.
  • xmlTreeParse, xmlRoot, xmlSApply 함수 이용.

URL 처리

  • API key 변수
  • 각 데이터셋의 SERVICE명
  • START INDEX, END INDEX 등 여러 정보들 필요
  • url에 저장된 홈페이지에 가보면 구조를 확인해 볼 수 있다.

Example

  • 서울시 지하철 이용고객 데이터 : 시간대별 2호선 이용 고객
  • 지정변수 : 날짜, start, end, query, api_key
api_key = "???"
service = "CardSubwayTime"
start = 1
end = 40
query = iconv("2호선", to = 'UTF-8')
date = 201603
url = paste0("http://openapi.seoul.go.kr:8088/", api_key, "/xml/",service ,"/",start,"/",end,"/", date, "/", query)

서울시 Openapi 이용

R code

if(!require(XML)){install.packages("XML"); library(XML)}

raw.data = xmlTreeParse(url, useInternalNodes = TRUE, encoding = "UTF-8")
rootNode <- xmlRoot(raw.data)

list_dat = list()
for(i in 3:length(names(rootNode))){
  list_dat[[i-2]] = xmlSApply(rootNode[[i]], xmlValue)
}

total_set = data.frame(do.call(rbind, list_dat), stringsAsFactors = F)

for(i in 4:ncol(total_set)){
  total_set[, i] = as.numeric(total_set[, i])
}

서울시 Openapi 이용

R code

head(total_set)
##   USE_MON LINE_NUM         SUB_STA_NM FOUR_RIDE_NUM FOUR_ALIGHT_NUM
## 1  201603    2호선               시청            45               2
## 2  201603    2호선         을지로입구           170              10
## 3  201603    2호선          을지로3가             6               1
## 4  201603    2호선          을지로4가             7               1
## 5  201603    2호선 동대문역사문화공원           298               6
## 6  201603    2호선               신당            32               0
##   FIVE_RIDE_NUM FIVE_ALIGHT_NUM SIX_RIDE_NUM SIX_ALIGHT_NUM SEVEN_RIDE_NUM
## 1          1625            2147         3071          23933           6447
## 2          3947            3225         4121          31555           9676
## 3          1210            1676         2989          20549           5710
## 4           707             793         1712           9063           3368
## 5         11498            1098         7824          11483           9914
## 6          7417            1520        13119          14420          32984
##   SEVEN_ALIGHT_NUM EIGHT_RIDE_NUM EIGHT_ALIGHT_NUM NINE_RIDE_NUM
## 1           117316          10418           228778         13803
## 2           156210          18777           366246         25769
## 3            70654          11834           164763         16181
## 4            26746           6042            90334         10725
## 5            36806          15046            74621         16107
## 6            22267          44810            55590         28475
##   NINE_ALIGHT_NUM TEN_RIDE_NUM TEN_ALIGHT_NUM ELEVEN_RIDE_NUM
## 1           87635        18883          34537           25241
## 2          172126        34208          96346           44029
## 3           78132        18772          38927           22216
## 4           53611        15083          29855           16984
## 5           45056        17431          41366           22103
## 6           31332        20998          23822           22102
##   ELEVEN_ALIGHT_NUM TWELVE_RIDE_NUM TWELVE_ALIGHT_NUM THIRTEEN_RIDE_NUM
## 1             33180           26032             31111             33603
## 2            102424           50409             82347             67344
## 3             31461           24372             26366             28559
## 4             27270           19969             25650             23019
## 5             38832           24771             39766             29632
## 6             23434           23879             25203             26299
##   THIRTEEN_ALIGHT_NUM FOURTEEN_RIDE_NUM FOURTEEN_ALIGHT_NUM
## 1               36488             38652               33337
## 2               85996             77650               76381
## 3               28921             33154               26826
## 4               24607             27486               25270
## 5               43317             35721               43036
## 6               27528             27316               28023
##   FIFTEEN_RIDE_NUM FIFTEEN_ALIGHT_NUM SIXTEEN_RIDE_NUM SIXTEEN_ALIGHT_NUM
## 1            45321              29169            49778              26542
## 2            93403              76436           102669              73118
## 3            35314              23028            39686              22413
## 4            29904              21492            32574              18962
## 5            46038              39786            52843              36949
## 6            27884              28585            32731              31169
##   SEVENTEEN_RIDE_NUM SEVENTEEN_ALIGHT_NUM EIGHTEEN_RIDE_NUM
## 1              77646                27006            175561
## 2             132600                76404            283613
## 3              56709                22739            123603
## 4              41986                15884             76223
## 5              57605                38981             68661
## 6              37732                36113             46717
##   EIGHTEEN_ALIGHT_NUM NINETEEN_RIDE_NUM NINETEEN_ALIGHT_NUM
## 1               29218            101820               18696
## 2               87578            200094               61555
## 3               24452             78249               16792
## 4               14412             52441                9099
## 5               43661             52560               55806
## 6               44799             32993               38999
##   TWENTY_RIDE_NUM TWENTY_ALIGHT_NUM TWENTY_ONE_RIDE_NUM
## 1           64862              9503               64315
## 2          174840             31900              152765
## 3           45841              8497               43484
## 4           22870              5215               17018
## 5           42045             38291               40882
## 6           23295             25147               21359
##   TWENTY_ONE_ALIGHT_NUM TWENTY_TWO_RIDE_NUM TWENTY_TWO_ALIGHT_NUM
## 1                  8068               41971                  7575
## 2                 24551              111340                 20863
## 3                  7524               33183                  6769
## 4                  5122               12406                  5030
## 5                 29326               37792                 23015
## 6                 23058               16500                 23682
##   TWENTY_THREE_RIDE_NUM TWENTY_THREE_ALIGHT_NUM MIDNIGHT_RIDE_NUM
## 1                 17526                    5271              2486
## 2                 50891                   12548              6165
## 3                 15776                    5232              2662
## 4                  5710                    3702              1061
## 5                 24098                   16951              4542
## 6                  8704                   14686              1217
##   MIDNIGHT_ALIGHT_NUM ONE_RIDE_NUM ONE_ALIGHT_NUM TWO_RIDE_NUM
## 1                1760            2             76            0
## 2                4700           18           1145            5
## 3                1552            3             65            0
## 4                1057            2             47            0
## 5                5903            8            161            0
## 6                4914            1             48            0
##   TWO_ALIGHT_NUM THREE_RIDE_NUM THREE_ALIGHT_NUM  WORK_DT
## 1              0              0                0 20160408
## 2              5              7                8 20160408
## 3              0              0                2 20160408
## 4              0              0                0 20160408
## 5              0              0                0 20160408
## 6              0              0                0 20160408