brunch

You can make anything
by writing

C.S.Lewis

by 요나 Jul 08. 2021

'웹크롤링'이라는 걸 해봤다

네이버블로그 웹크롤링&텍스트마이닝 with R (상)

빅데이터 분석기사 자격증 실기시험을 6월 중순에 봤다. 지금은 결과를 기다리는 중이다. 애써 배운 R 언어를 까먹고 싶지 않아서 쉬는 동안 짬짬이 웹 크롤링 텍스트 마이닝을 연습하고 있다. 일반인의 관점에서 짧고 굵게 소개하자면, 웹 크롤링은 웹 브라우저상에 노출되는 모든 데이터를 긁어오는 <수집의 기술>이고, 텍스트 마이닝은 텍스트로 된 데이터의 패턴을 읽어내는 <분석의 기술>이다. 데이터 분석 분야에 있어서 이 두개가 그나마 실무에 많이 쓰일 것 같았다. 이 과정에서 내가 만든 예제와 얼기설기 엮어 만든 코드를 올린다. 그냥 문과생의 고군분투 에세이 정도로 가볍게 읽어주시라. 


예제) 책 '최고의 리더는 글을 쓴다(홍선표 作)'를 언급한 네이버 블로그 게시글 전체를 크롤링 한 뒤, 가장 많이 언급된 명사를 중심으로 텍스트 데이터를 시각화해보자. 


올해 재밌게 읽은 책 '최고의 리더는 글을 쓴다'를 리뷰한 네이버 블로그 글을 가져와 보겠다. (여담 : 이 책은 현재 무료 PDF로 배포 중이다(링크). 



1. 블로그 URL 정보 수집





네이버 블로그에서 '최고의 리더는 글을 쓴다'를 검색했을 때 나오는 페이지다. 이 블로그들의 URL을 일일이 수집해야 한다. 그 전에 필요한 패키지를 불러오자. 


library(httr)             #http 통신 관련 패키지. http 요청할 때 사용.
library(rvest)           #받은 정보가 xml일 때
library(jsonlite)      # 받은 정보가 json일 때 
library(tidyverse)    #파이프 연산자(%>%), dplyr 및 stringr 패키지 동시 호출



For 반복구문을 활용해 1~23페이지까지 텍스트를 수집하자. 

hong.df <- data.frame()  # 데이터를 저장할 빈 데이터 프레임을 만듦 
for (i in 1:23) { GET(url   = "https://section.blog.naver.com/ajax/SearchList.nhn",
                      query = list("countPerPage" = 7,
                                   "currentPage"  = i,
                                   "endDate"      = "2021-07-05",
                                   "keyword"      = "최고의 리더는 글을 쓴다",
                             "orderBy"      = "sim",
                                   "startDate"    = "2021-02-17",
                                   "type"         = "post"),
                                    add_headers("referer" = "https://section.blog.naver.com/Search/Post.nh")) %>% httr::content(as = "text") %>% str_remove(pattern = '\\)\\]\\}\',') %>% jsonlite::fromJSON() -> plus 
  data <- plus$result$searchList 
  hong.df <- rbind(hong.df, data)
  cat(i, "번째 페이지 정리 완료\n")}



사실 여기서  url 때문에 좀 많이 헤맸다. 구글 개발자도구(F12) 엘리먼트에서 확인한 html 정보로는 데이터를 가져올 수 없었다. 그 이유는 해당 페이지에 ajax가 적용되어서다.



왓더


ajax(Asynchronous Javascript And XML)란?

비동기적으로 자바스크립트를 이용해서 서버와 통신하는 방식. 

HTML만으로 어려운 다양한 작업을 웹 페이지로 구현. 사용자들이 별도 프로그램을 설치하거나 웹 페이지를 새로고침하지 않아도 페이지내 일부 데이터 로드 가능. 

주고 받은 data를 html로 받는 것이 아니라 json 형식으로 받는다. 고로 HTML에서 데이터를 추출하는 것이 아니라 서버에서 HTML과는 다른 형식의 데이터를 가져와야 한다. 

ajax를 쉽게 설명한 유튜브(https://www.youtube.com/watch?v=U_ICTI-1DBc)



ajax가 적용된 페이지의 호출 url은 대체 어디서 알고 가져와야 하는 것인가? 


크롬 개발자도구 Network 중 XHR(XMLHttpRequest) 탭에서 확인할 수 있다. 웹페이지를 사용하면서 Server와 Client가 어떤 데이터를 주고 받는지, 속도가 얼마나 걸리는지 등의 정보가 이곳에 기록되기 때문이다.



> 결과


url을 구성할 blogID, logNo 등의 정보를 다음과 같이 가져왔다. 이 책에 대해 리뷰한 블로거들의 url을 다 모았다. 드디어 본격 크롤링인가.... 



 2. 호출 URL 조합하기

blog.df <- hong.df %>% 
dplyr::select(1, 2, 4, 6, 9) %>% 
  rename(id= blogId,               
                 no= logNo,
                posturl = postUrl,
                title = noTagTitle,
                name= blogName)
blog.df <- blog.df %>% mutate(url= str_glue("http://blog.naver.com/PostView.naver?blogId={id}&logNo={no}"),                 contents   = NA) 

여기서도 좀 헤맸다. 처음엔 앞서 수집한 posturl을 이용해 블로그 텍스트를 수집하려고 했었다. 그대로 GET함수에 집어넣었는데 결과는 Fail이었다. 


다시 크롬 개발자 도구 <네트워크>로 돌아가자. 여기에서 request url 구조를 확인할 수 있다. 


http://blog.naver.com/PostView.naver?blogId={id}&logNo={no} 


url에 blogId와 logNo 들어간다. which means ..  좀전에 수집한 정보를 활용해 호출 url을 새로 조합해야 하는 거다. (왜 posturl로는 수집이 안되는지 아직도 잘 모르겠다. 알면 좀 알려주시라. 주변에 물어볼 사람이 없다. )



 3. 진짜 본격 웹 크롤링

이제 진짜 가자~~화이팅~~~

for(i in 1:nrow(blog.df)){
         blog.df$contents[i] <- GET(url=blog.df$url[i]) %>%
         read_html()%>%
        html_nodes("div.se-main-container") %>%
        html_text(trim=T)
        cat(i,"번째 블로그 글 내용 취합 중...\n")} 


> 결과 

Error in blog.df$contents[i] <- GET(url = blog.df$url[i]) %>% read_html() %>%  : replacement has length zero



수집 과정에서 55번째 글에 에러가 발생했다는 메시지가 뜨며 동작을 멈춘다. 


스스메~

당황하지 말자. try혹은 trycatch 함수를 쓰면 오류가 발생해도 다음 단계로 건너 뛸 수 있다. 그야말로 진격의 함수인 셈. 

     try( ) : 리턴의 오류 / 에러에 상관없이 다음코드로 계속 진행   #try(null_object, silent=T)

tryCatch( ) : 리턴의 오류/에러가 발생하면 명령어 시행하고 / 다음 코드로 계속 진행


for(i in 1:nrow(blog.df)){
  try({  blog.df$contents[i] <- GET(url = blog.df$url[i]) %>% 
            xml2::read_html() %>% 
            html_nodes(css = "div.se-main-container")%>% 
            html_text(trim = TRUE)
            cat(i, "번째 블로그 글 내용 취합 완료\n") })}




 4. 텍스트 전처리 


수집한 텍스트를 확인하자. 



\n 같은 잡음이 너무 많다. 텍스트 마이닝에 들어가기  앞서 간단한 전처리를 해준다.


hong.txt <- blog.df$contents
hong.txt <- str_remove_all(hong.txt, pattern='\n')
hong.txt <- str_remove_all(hong.txt, pattern='[A-z]') # 영문 제거
hong.txt <- gsub("\\s{2,}", " ", hong.txt)                  # 2개이상 공백을 한개의 공백으로 처리
hong.txt <- str_remove_all(hong.txt, pattern = '<.*?>')
hong.txt <- str_remove_all(hong.txt, pattern='\U0001f538️')
hong.txt <- str_remove_all(hong.txt, pattern='\u2b50️')
hong.txt <- str_remove_all(hong.txt, pattern='\\<U+200B>️')#<U+200B>는 폭 없는 공백문자를 의미하는 태그임.
hong.txt <- gsub("[[:punct:]]", "", hong.txt) # 특수문자 제거 
hong.txt[2]


제법 그럴 듯해졌다. 



텍스트 마이닝과 워드 클라우드는 <하>편에서 정리해보겠다.


투비 컨티뉴,,,





매거진의 이전글 진성 문과생, 빅데이터에 도전하다
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari