오늘은 웹 크롤링한 데이터를 csv ( 엑셀 ) 형태로 저장을 시키고 , 저장시킨 엑셀 파일을 정제시키고 , 데이터를 가져와서 시각화 해보는 작업을 해 볼 것이다.
내가 크롤링할 사이트는 https://www.greenclimate.fund/home 이며 영어로 된 뉴스 사이트다.
코드를 보면서 분서해 보자 .
크게 rvest 패키지와 XML패키지가 필요하며 시각화 할 때 필요한 wordcloud2 패키지가 필요하다 .
문서를 다루는 tm패키지가 보통 깔려있는데 , 나는 없어서 깔아 주었다.
library(rvest)
library(XML)
# 크롤링을 해오기 위해서 필요한 라이브러리들
install.packages("tm")
install.packages("wordcloud2")
# 필요한 패키지들
#wordcloud2를 구동하기 위한 라이브러리들
library(devtools)
library(htmlwidgets)
library(htmltools)
library(jsonlite)
library(yaml)
library(base64enc)
library(tm)
library(wordcloud2)
패키지를 업로드 시켜주었다면 , 내가 접근하고자 하는 사이트 주소를 가져와야 하는데
여기서 생각해야 할 부분이있다.
내가 특정 페이지의 값들을 가져올 것인가 ??
아니면 페이지 수에 따른 데이터들을 가져올 것인가 ??
오늘 다뤄볼 내용은 페이지 이동에 따른 각각의 페이지에 존재하는 뉴스기사들을 크롤링하는 것이기 때문에 , 페이지 이동에 따른 뉴스기사를 크롤링 해와야 할 것이다.
그러기 위해서는 사이트에 들어가서 페이지이동에 따른 주소값의 변화인데 , 이 부분을 이해하고 그거에 맞게 R에서 다뤄줘야 한다 .
우선 사이트를 들어가서 확인해 보자 .
내가 크롤링하고자 하는 뉴스 -스토리 부분에 가서 , 주소값 이동에 따른 변화를 보면 , 주소값에 cur =1 이라는 부분이 보일 것인데 , 이부분에 페이지 이동에 따른 값의 변화가 생긴다 .
그 의미는 무엇이냐면 , 페이지가 첫 페이지면 cur = 1 이 되고 , 두번째 페이지로 이동하면 cur = 2 로 값이 변화한다 .
이제 확인이 되었고 , 우리는 다음 코드를 통해 이해해 보자 .
First_url <- "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur="
우선 나는 cur 의 값을 동적으로 주기 위해서 cur을 기준으로 First_url , second_url로 나누어 보았다.
왜냐하면 cur은 변수로 대체될 거기 때문이다 .
# First_url : greenclimate 에서의 뉴스정보들을 모아논 곳의 주소
second_url <- "#portlet_101_INSTANCE_tLw79zWwerZZ"
# second_url : greenclimate 에서의 뉴스정보들을 모아논 곳의 주소 두번째
나는 컴퓨터 사양상 많은 페이지 검색은 못했고 , 1~5페이지 까지만 검색을 할 것이다 .
urls <- NULL
for(x in 1:5)
{
urls[x] <- paste0(First_url,as.character(x),second_url)
}
# urls에 greenclimate NEWS + stories Save
è 위 코드는 1~5페이지을 기준으로 urls에 모든 url을 저장시킨다.
è 1~5 페이지에 존재하는 모든 url이 저장될 것이다.
결과값은 다음과 같다
[1] "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur=1#portlet_101_INSTANCE_tLw79zWwerZZ"
[2] "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur=2#portlet_101_INSTANCE_tLw79zWwerZZ"
[3] "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur=3#portlet_101_INSTANCE_tLw79zWwerZZ"
[4] "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur=4#portlet_101_INSTANCE_tLw79zWwerZZ"
[5] "https://www.greenclimate.fund/what-we-do/newsroom/news-stories?p_p_id=101_INSTANCE_tLw79zWwerZZ&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_101_INSTANCE_tLw79zWwerZZ_delta=30&_101_INSTANCE_tLw79zWwerZZ_keywords=&_101_INSTANCE_tLw79zWwerZZ_advancedSearch=false&_101_INSTANCE_tLw79zWwerZZ_andOperator=true&p_r_p_564233524_resetCur=false&_101_INSTANCE_tLw79zWwerZZ_cur=5#portlet_101_INSTANCE_tLw79zWwerZZ"
5개의 url 주소값들이 저장되어 있다 .
이제 url주소를 가져왔으니 , 접근하고자 하는 부분의 class명을 확인할 필요가 있다.
사이트에 들어가서 확인해 보면 , news-mosaic이라는 id 값으로 div태그로 감싸져 있다.
이 부분은 각 뉴스들의 메인부분이다.
links <- NULL
for(url in urls)
{
#에러 방지
download.file(url, destfile = "scrapedpage.html", quiet = TRUE)
html <- read_html("scrapedpage.html")
links <- c(links, html %>% html_nodes('#news-mosaic') %>% html_nodes('a') %>% html_attr('href') %>% unique())
# 찾고자 하는 부분이 class일 경우 앞에 . 을 붙이고 , id이면 #을 붇인다.
# 가져오고자 하는 클래스명에 띄어쓰기가 있을 경우 띄어쓰기 있는 부분에 .을 사용하면 된다.
}
# 기사가 존재하는 url을 모아서 links에 저장한다.
è 위 코드를 실행 함으로써 각 페이지 존재하는 기사들을 links에 싹다 저장시킨다.
Links 결과값 ( 결과값이 어마하게 많아서 처음부분과 끝부분만 출력한다 )
[1] "https://www.greenclimate.fund/-/gcf-supporting-energy-transformation-in-small-island-developing-states-sids-with-the-financing-of-energy-storage-systems?inheritRedirect=true&redirect=%2Fwhat-we-do%2Fnewsroom%2Fnews-stories%3Fp_p_id%3D101_INSTANCE_tLw79zWwerZZ%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-2%26p_p_col_count%3D1%26_101_INSTANCE_tLw79zWwerZZ_delta%3D30%26_101_INSTANCE_tLw79zWwerZZ_keywords%3D%26_101_INSTANCE_tLw79zWwerZZ_advancedSearch%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_andOperator%3Dtrue%26p_r_p_564233524_resetCur%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_cur%3D1%23portlet_101_INSTANCE_tLw79zWwerZZ"
[150] "https://www.greenclimate.fund/-/election-process-of-gcf-board-for-2019-2021-membership-underway?inheritRedirect=true&redirect=%2Fwhat-we-do%2Fnewsroom%2Fnews-stories%3Fp_p_id%3D101_INSTANCE_tLw79zWwerZZ%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-2%26p_p_col_count%3D1%26_101_INSTANCE_tLw79zWwerZZ_delta%3D30%26_101_INSTANCE_tLw79zWwerZZ_keywords%3D%26_101_INSTANCE_tLw79zWwerZZ_advancedSearch%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_andOperator%3Dtrue%26p_r_p_564233524_resetCur%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_cur%3D5%23portlet_101_INSTANCE_tLw79zWwerZZ"
이걸 통해 1~5페이지 에는 150개의 뉴스 기사들이 있는 것을 확인할 수 있다. 참 많다….
그런데 , 크롤링을 할려고 보면 , 이 사이트에는 특이점이 하나 있다.
Greenclimate.func 사이트인데 , 정작 쓰여진 기사들을 보면 링크되어진 기사들도 많다…
나는 이 사이트외에는 관심이없고 , 이 사이트만 뺄 필요를 느꼈다.
그래서 grep를 사용해서 걸러준다.
# News-stories의 기사에는 다른 사이트의 기사로 링크되는 기사도 많다
# 그렇기 때문에 grep함수를 사용해서 greenclimate.fund가 있는 열의 위치를 가져올 것이다 . 쉽게 말해 이 사이트가 포함된 것만 가져올 것이라는 얘기이다.
num_GCFurl <- grep("www.greenclimate.fund",links)
num_GCFurl 결과값
[1] 1 2 3 4 5 6 8 9 11 12 13 14 15 16 17
[16] 18 19 23 25 27 29 30 31 32 33 34 35 36 38 39
[31] 41 42 43 44 45 46 47 48 49 53 55 57 59 60 61
[46] 62 63 64 65 66 68 69 71 72 73 74 75 76 77 78
[61] 79 83 85 87 89 90 91 92 93 94 95 96 98 99 101
[76] 102 103 104 105 106 107 108 109 113 115 117 119 120 121 122
[91] 123 124 125 126 128 129 131 132 133 134 135 136 137 138 139
[106] 143 145 147 149 150
결과값이 조금 깨져보이지만 , R에서는 문제없이 이쁘게 출력된다 .
근데 결과값을 보면 , 특이한게 숫자로 표현되어 있는 것을 알 수 있는데 , 이것은 grep 자체가 반환을 할 때 , 있다면 그 문자 자체를 반환하게 된다.
위에서 우리는 150개의 뉴스 기사들이 있음을 확인했다.
쉽게 말해서 차곡차곡 1부터 시작해서 greenclimate.fund 문자열이 있는 url들만 쌓이게 되는 것이다.
links_green <- NULL
# 각 번호에 해당되는 links의 url 을 links_green에 담는다
# 이 말은 즉슨 , green 기사 url만 links_green에 저장시키는 것이다.
for(num in num_GCFurl)
{
links_green <- c(links_green, links[num])
}
links_green 결과값 ( 이것또한 너무 많아서 처음부분과 마지막만 출력할 것이다 )
[1] "https://www.greenclimate.fund/-/gcf-supporting-energy-transformation-in-small-island-developing-states-sids-with-the-financing-of-energy-storage-systems?inheritRedirect=true&redirect=%2Fwhat-we-do%2Fnewsroom%2Fnews-stories%3Fp_p_id%3D101_INSTANCE_tLw79zWwerZZ%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-2%26p_p_col_count%3D1%26_101_INSTANCE_tLw79zWwerZZ_delta%3D30%26_101_INSTANCE_tLw79zWwerZZ_keywords%3D%26_101_INSTANCE_tLw79zWwerZZ_advancedSearch%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_andOperator%3Dtrue%26p_r_p_564233524_resetCur%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_cur%3D1%23portlet_101_INSTANCE_tLw79zWwerZZ"
[110] "https://www.greenclimate.fund/-/election-process-of-gcf-board-for-2019-2021-membership-underway?inheritRedirect=true&redirect=%2Fwhat-we-do%2Fnewsroom%2Fnews-stories%3Fp_p_id%3D101_INSTANCE_tLw79zWwerZZ%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-2%26p_p_col_count%3D1%26_101_INSTANCE_tLw79zWwerZZ_delta%3D30%26_101_INSTANCE_tLw79zWwerZZ_keywords%3D%26_101_INSTANCE_tLw79zWwerZZ_advancedSearch%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_andOperator%3Dtrue%26p_r_p_564233524_resetCur%3Dfalse%26_101_INSTANCE_tLw79zWwerZZ_cur%3D5%23portlet_101_INSTANCE_tLw79zWwerZZ"
보면 결과값이 1~110 , 40개 정도 걸러진 것이 보일 것이다 .
# 우리는 인터넷 기사만 가져올 것이다 . 그렇기 때문에 ,
# 불필요한 video or youtube or pdf 는 제외시킨다.
# 그런데 결과를 보면 links_green에는 불필요한 것들이
# 없는 것을 확인 할 수 있다.
grep("video",links_green)
grep("youtube",links_green)
grep("pdf",links_green)
grep 결과값들
> grep("video",links_green)
integer(0)
> grep("youtube",links_green)
integer(0)
> grep("pdf",links_green)
integer(0)
links_green <- links_green[-c(grep("video",links_green),grep("youtube",links_green),grep("pdf",links_green))]
# 검색된 열들은 links_green에서 빼준다 .
# 허나 우리는 이 기능은 사용안해도 될 것같다 .
# 불필요한 요소들이 이미 제외되어 있기 때문에.
# 제외시키고 남은 기사의 개수를 확인한다.
length(links_green)
length(links_green) 결과값
> length(links_green)
[1] 110
txts <- NULL
# 추출된 url에서 글을 가져올 것이다 .
그러기 위해서는 다시 사이트로 들어가서 클래스명을 확인해 보자 .
보면 body html-editable 클래스로 지정되어 있다.
for(link in links_green)
{
download.file(link, destfile = "scrapedpage.html" , quite=TRUE)
html <- read_html("scrapedpage.html")
txts <- c(txts, html %>% html_nodes(".body.html-editable") %>% html_text())
# 사이트의 주소를 보면 내가 가져오고자 하는 기사의 클래스명이 body html-editable이며 , 이 클래스의 해당 문자들을 전부 가져온다
}
추출된 txts 를 csv 파일로 저장 시킬 것이다.
이때 write.csv를 사용하며 , 경로를 지정할 때 전체경로를 지정해 줘야 한다.
write.csv(txts,"C://Users/user/Desktop/3학년2학기/데이터마이닝과통계/기말_HW2/GettingArticleImfor.csv")
# 저장 시켜준 csv파일을 상대경로로써 가져올 것이다.
# 이때 내가 저장시켜준 파일을 경로변경을 해주어야 한다.
# 왜 파일 경로를 바꿔주면 , 상대경로가 USer/user/Document로
# 경로 지정이 되어 있기 때문이다.
news_path <- paste0(getwd(),"/GettingArticleImfor.csv")
modi_txt <- readLines(news_path)
# 가져온 데이터에서 불필요한 문자 or 특수기호들을 제거해줄 것인데 이때 gsub를 사용할 것이다
# 특이한 점은 gsub는 정규표현식이 작동이 한다는 것
# <U+2013> or < U+00A0> 를 제거할 것인데 , 이것은
# 각각 하이픈 , 빈칸이다 .
# 결과적으로 가져온 데이터에서 하이픈과 공백은 제거가 되는 것
modi_txt <- gsub("<U.00A0>"," ",modi_txt)
modi_txt <- gsub("<U.2013>","-",modi_txt)
# 특이한 점은 's 를 제거해 주게 될 것인데 ,
# 영어 사이트이다 보니 소유격인 's또한 문자로 되어있을 것이다
# 소유격인 's는 굳이 필요가 없기 때문에 제거해준다.
modi_txt <- gsub("'s","",modi_txt)
# txt 파일의 총 Line수 구하기
# 그리고 총 line수만큼 1씩 증가하는 벡터를 생성
last_number_of_lines <- length(readLines(news_path))
line_numbers <- seq(1,last_number_of_lines,1)
# line별 마다 번호를 매기기
# data.frame을 생성해 줄 것인데 , line별 마다의 번호 , txt ,To avoid problems delay re-encoding of strings by using
# 참고 사이트 : https://www.r-bloggers.com/r-tip-use-stringsasfactors-false/
doc_ids <- line_numbers
df <- data.frame(doc_id = doc_ids,text = modi_txt, stringsAsFactors = FALSE)
# 참고사이트 : https://thebook.io/006723/ch10/07/01/
# Corpus : 여기서는 DFSource의 df의 내용을 볼 것이다 정도 이해
modi_data <- Corpus(DataframeSource(df))
Corpus 결과값
<<SimpleCorpus>>
Metadata: corpus specific: 1, document level (indexed): 0
Content: documents: 106
# 영어 ( us ) 로 언어설정
Sys.setlocale(category = "LC_ALL", locale ="us")
[1] "LC_COLLATE=English_United States.1252;LC_CTYPE=English_United States.1252;LC_MONETARY=English_United States.1252;LC_NUMERIC=C;LC_TIME=English_United States.1252"
# 공백이 2개 이상인 걸 1개로 만든다( 문장내에서 혹은 문장 끝에서)
modi_data <- tm_map(modi_data,stripWhitespace)
# 대문자를 소문자로 변경 , 같은 단어의 대,소문자 차이로 다른 단어로 인식이 될 수도 있기 때문에
modi_data <- tm_map(modi_data,tolower)
#단어를 추출할거기 때문에 , 내부 숫자들은 다 제거해준다.
modi_data <- tm_map(modi_data,removeNumbers)
#stopwords("") <-- ""안에 있는 어떤 단어들이 있는지 확인해주는 기능인데 , 여기서 우리는 a , an , the , does 등을 제거할 것이다.
modi_data <- tm_map(modi_data, removeWords,stopwords("english"))
#구둣점 제거 ( 예를 들어서 `!~@#$%^&*등등 제거 )
modi_data <- tm_map(modi_data,removePunctuation)
# 각 line속의 단어들을 매트릭스 형태로 만들어서 엑셀에 저장
# 현재 line별로 되어 있는 modi_data --> matrix 형태로 만듦
# TDM 생성
tdm_modi <- TermDocumentMatrix(modi_data)
# matrix format
TDM1 <- as.matrix((tdm_modi))
# 모든 단어들의 빈도수를 체크
v=sort(rowSums(TDM1), decreasing = TRUE)
profile = data.frame(word=names(v),freq=v)
#최상위 10개 결과 확인
head(profile,10)
word freq
climate climate 975
gcf gcf 725
will will 330
usd usd 255
finance finance 255
countries countries 250
board board 240
change change 230
developing developing 225
private private 225
# 저장위치 설정
word_path <- paste0(getwd(),"/GettingArticleImfor_word.csv")
# csv파이로 저장
write.csv(profile, word_path)
#수정한 csv파일 불러오기
data <- read.csv(word_path)
#첫 column은 단어의 중복이므로 삭제한다.
data <- data[,-1]
#일정 횟수 이상 검색된 항목만 추출한다.
data_pick <- subset(data, freq>=5)
# 제일 많이 검색된 순서대로 보기 (10개까지)
head(data_pick,10)
word freq
1 climate 975
2 gcf 725
3 will 330
4 usd 255
5 finance 255
6 countries 250
7 board 240
8 change 230
9 developing 225
10 private 225
# 참고사이트 : https://html-color-codes.info/Korean/
#특정 개수 이상 추출된 글자만 색깔을 변경할 것 이다.
# freq 가 500이상이면 보라색 , 아니면 민트색으로
in_out_colors = "function(word,weight)
{return(weight > 500 ? '#4B088A':'#81F7F3')}"
# word cloud 그리기
# 기존모형으로 wordcloud 생성
# 모양선택 : shape = 'circle' , 'cardioid' , 'diamond ' , 'triangle -forward', 'triangle' , 'pentagon' , 'star'
wordcloud2(data_pick,shape = "diamond",size=0.9,color=htmlwidgets::JS(in_out_colors), backgroundColor = "black")
최종결과값 :
'학부공부 > 데이터마이닝과통계' 카테고리의 다른 글
논문제목+논문저자+논문발간일 크롤링 / wordcloud2 (5) | 2018.11.18 |
---|---|
API를 사용해서 실시간 버스 위치정보 시각화 (1) | 2018.11.15 |
동아신문 스크랩핑 (0) | 2018.11.14 |
나이키 사이트 크롤링 (0) | 2018.11.10 |
쿠팡 웹 크롤링 맛보기 (2) | 2018.11.01 |
#IT #먹방 #전자기기 #일상
#개발 #일상