聰明不如鈍筆
총명불여둔필
assignment KindeR

최대한 친절하게 쓴 R 크롤러 만들기


살다 보면 가끔 인터넷에 있는 자료를 통째로 가져와서 데이터를 뽑아내고 싶다는 생각이 들 때가 있습니다.


이런 작업은 크롤링(crawling)이라고 부르기도 하고 (웹) 스크래이핑(scraping)이라고 부르기도 합니다.


전 세계에서 승객이 가장 많았던 공항 50군데를 시각화하면서 맛배기로 크롤링을 소개해 드렸던 적이 있습니다.


이번에는 한번 신문 기사를 긁어오는 과정을 알아보겠습니다. 


이번에도 사용할 도구는 역시 R입니다. R 공식 홈페이지는 "R는 통계 계산과 그래픽에 활용하는 무료 소프트웨어 환경(R is a free software environment for statistical computing and graphics)"이라고 밝혀두고 있습니다. 


러니 누구든 공짜로 이 프로그램을 사용할 수 있습니다. 아직 컴퓨터에 R가 없다면 이 다운로드 페이지에서 내려받아 설치하시면 됩니다. 그냥 다른 프로그램을 설치하실 때하고 똑같은 과정입니다.


아, 준비물이 하나 더 있습니다. 웹 브라우저입니다. 웹페이지 소스를 볼 수 있는 기능이 들어 있는 브라우저라면 어떤 것이든 관계 없습니다. 보통은 웹 브라우저에 저런 기능이 다 들어 있습니다.


저는 구글 크롬을 사용할 예정입니다. 역시 혹시나 크롬이 필요한데 설치하지 않으셨다면 이 링크를 찾아가시면 됩니다. (제가 추천하는 크롬 확장 프로그램을 같이 설치하셔도 좋습니다.)


신문 기사를 긁어올 때는 △원하는 기사를 상세하게 찾아서 △검색 결과에 나온 링크를 정리한 뒤 △해당 페이지에서 기사 내용을 긁어오는 세 단계를 거치는 게 일반적입니다.


넓은 의미에서 '인터넷 게시판'이 어떻게 생겼는지 떠올려 보면 각종 커뮤니티 사이트나 블로그, 인터넷 카페도 비슷한 과정을 거치면 된다는 걸 짐작할 수 있습니다. 한번 차근차근 천천히 해보겠습니다.


URL은 내 친구

이번에 목표로 세운 건 제가 여태 쓴 '베이스볼 비키니' 모두 가져오기.


원하는 자료만 골라 오려면 정밀하게 검색하는 것도 중요합니다. 저는 이 칼럼을 쓸 때 기자가 누군지 알려주는 '바이라인'에 e메일 주소 대신 페이스북 계정 주소(fb.com/bigkini)를 넣었습니다. 다른 기자가 기사에 'bigkini'라는 낱말을 쓸 확률은 사실상 제로에 가까울 터.


동아닷컴(www.donga.com)에서 'bigkini'로 검색했습니다. 그다음 신문에 나온 기사만 보여주도록 선택했습니다



검색을 마치셨으면 인터넷 주소(URL)를 한번 분석할 필요가 있습니다. 위 화면이 나왔을 때 제 주소창에는 이렇게 떠 있었습니다.

http://news.donga.com/search?check_news=1&more=1&sorting=1&range=3&search_date=&query=bigkini


주소를 자세히 보시면 '&낱말=숫자 또는 낱말' 구조가 이어지는 게 눈에 띕니다.


기사를 찾을 때는 검색 결과가 한 페이지로 끝나는 일이 드물죠?


맨 아래로 내려서 검색 결과 2 페이지를 찾아 보겠습니다. URL은 다시 이렇게 나옵니다.

http://news.donga.com/search?p=16&query=bigkini&check_news=1&more=1&sorting=1&search_date=1&v1=&v2=&range=3


뭐가 뭔지 잘 모르겠으니 다시 1 페이지로 돌아오겠습니다. URL이 이렇게 바뀐 걸 알 수 있습니다.

http://news.donga.com/search?p=1&query=bigkini&check_news=1&more=1&sorting=1&search_date=1&v1=&v2=&range=3


맨 처음에 쓴 URL하고 보여주는 결과는 똑같은데 구조가 다릅니다. 검색 결과가 여러 페이지로 나올 때는 페이지 사이를 왕복해 보면 좋습니다. URL 구조가 좀더 분명히 들어오기 때문입니다.


현재 주소에서는 '&낱말=숫자 또는 낱말' 구조를 하나씩 지우고 다시 페이지를 불러 보시면 각 단위가 어떤 구실을 하는지 알 수 있습니다. 눈치가 빠른 분이라면 '&query=bigkini'는 검색한 낱말이 'bigkini'라는 걸 알려주고 있다는 걸 알아채셨을 겁니다.


또 맨 처음에는 &query=bigkini가 URL 맨 끝에 있었는데 다시 불러왔을 때는 앞으로 자리를 옮겼네요? 이건 이 단위끼리 자리를 바꿔도 검색 결과에 영향을 끼치지 않는다는 증거입니다.


이것 저것 지우고 옮겨 보니 아래처럼 URL을 바꿔도 처음 본 검색 결과하고 똑같은 결과가 나온다는 걸 알 수 있었습니다.

http://news.donga.com/search?query=bigkini&more=1&range=3&p=1


이 주소로 페이지를 불러온 상태에서 2페이지로 가봤더니 URL이 이렇게 나왔습니다.

http://news.donga.com/search?p=16&query=bigkini&check_news=1|2|3|6|7|8|9|12|14&more=1&sorting=1&search_date=1&v1=&v2=&range=3


처음에도 2페이지에 갔을 때 'p=16이 눈에 띄었는데 이번에도 그러네요. 줄이고 줄인 URL 맨 끝에 있는 p만 16으로 바꿔 주소창에 넣어 봤습니다. 예상대로 2페이지하고 같은 결과가 나옵니다.


좀더 확인해 보시면 3페이지는 p=31, 4페이지는 p=46, 5페이지는 p=61로 숫자가 15씩 늘어난다는 걸 알 수 있습니다. 한번에 보여주는 검색 결과가 15개거든요.


여기까지 오셨으면 이 글을 처음 시작할 때 말씀 드린 세 단계 중에서 첫 단계는 끝내신 겁니다. 수고하셨습니다.


아직은 별로 어려운 게 없었습니다. 아마 앞으로도 그럴 겁니다. 그래도 혹시 모르니 심호흡 한번 하시고 스크롤을 내려보겠습니다.



print("Hello, R")

이제부터는 컴퓨터 프로그래밍이라고 부르기도 하고 코딩(coding)이라고 부르기도 하는 과정을 시작할 겁니다. 내가 이러저러한 게 하고 싶다고 컴퓨터에게 알려주는 게 바로 프로그래밍입니다.


예전에 썼던 글에서 그림을 가져오면 이렇게 print("Hello, World")라고 치는 것만으로 (한번도 코딩을 해보지 않으셨다면) 여러분은 생애 첫 프로그램을 완성하실 수 있습니다.



자, 우리는 여기서 URL을 가지고 뭔가 해보려고 합니다. 일단은 아래처럼 URL 여섯 줄을 만들어 내야 합니다.

http://news.donga.com/search?query=bigkini&more=1&range=3&p=1
http://news.donga.com/search?query=bigkini&more=1&range=3&p=16
http://news.donga.com/search?query=bigkini&more=1&range=3&p=31
http://news.donga.com/search?query=bigkini&more=1&range=3&p=46
http://news.donga.com/search?query=bigkini&more=1&range=3&p=61
http://news.donga.com/search?query=bigkini&more=1&range=3&p=76


맨 끝에 나온 숫자만 빼고는 전부 똑같은 구조입니다. 앞 부분은 놔두고 맨 끝만 반복적으로 갈아끼우면 되겠죠?


이럴 때 쓰라고 있는 게 문자 그대로 반복문입니다. 우리는 1, 16, 31, 46, 61, 76을 반복적으로 만들어야 합니다. 이걸 수식으로 표현하면 어떻게 될까요?


 1 = 0 × 15 + 1
16 = 1 × 15 + 1
31 = 2 × 15 + 1…


처럼 표현할 수 있습니다. 역시 맨 앞에 숫자만 하나씩 커지고 나머지는 변화가 없습니다.


R에서는 for가 반복문을 만드는 대표적인 명령어(함수)입니다.


아래처럼 써서 R에 입력하면 우리가 원하는 값을 얻을 수 있습니다. x, y는 그냥 수학 방정식에 등장하는 것처럼 대표적인 문자 기호일 뿐 다른 의미가 있는 건 아닙니다.

for(x in 0:5){
 y = x * 15 + 1
 print(y)
 }


이 반복문 실행이 모두 끝나고 나서 print(y) 또는 y라고 입력하시면 76이 나옵니다. 맨 마지막 값만 저장하고 있는 겁니다. 같은 방식으로 x를 치면 5가 나옵니다.우리는 저 링크 6개를 전부 저장해 두고 싶습니다. 이럴 때는 미리 그릇을 하나 만들어 놓고 거기 담으면 됩니다. 이런 그릇을 '변수'라고 합니다. 위에 나온 x, y도 변수입니다.


한번 x <- c(1, 2, 3, 4, 5) 라고 입력하신 다음 x를 치시면 저 숫자가 차례로 나올 겁니다. 여기서 c는 묶는다(concatenate)는 뜻입니다.


만약 x[1]이라고 치면 어떻게 될까요? 이때는 1이 나옵니다. x 중에서 첫 번째 값을 불러오라는 뜻이거든요. x[3]은? 네, 3이 나옵니다.


여기까지 이해하셨을 걸로 믿고 링크 6개를 저장하는 코드를 만들어 보겠습니다.

basic_url <- 'http://news.donga.com/search?query=bigkini&more=1&range=3&p='
 urls <- NULL
 for(x in 0:5){
 urls[x+1] <- paste0(basic_url, x*15+1)
 }


맨 첫 줄은 변하지 않는 부분을 컴퓨터에게 알려주는 겁니다. R에서 '<-' 또는 '='는 어떤 변수에 어떤 값을 넣으라는 뜻입니다.


다음 줄은 urls라는 방에 NULL을 넣으라는 뜻일 터. NULL은 빈 방이라는 뜻입니다. 미리 이런 변수를 만들어주지 않은 채로 '손님을 받으라'고 하면 R가 그 방을 못 찾아서 에러를 내거든요. 


urls[x+1]은 urls 중에서 몇 번째 칸인지 정하는 겁니다. x는 0에서 5까지 움직이는데 R에서 변수에는 0번째 칸이 없습니다. 그래서 하나씩 자리를 키워주는 겁니다. 


다음 나오는 paste0는 '붙여넣기'입니다. 이 변수와 저 변수를 붙이라는 뜻입니다. 그냥 paste를 쓰면 변수 사이에 한 칸을 띄웁니다. 여기서는 그러면 안 뇌니까 paste0을 써서 변수 사이를 붙였습니다.


결과를 확인해 볼까요?

urls
[1] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=1"
[2] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=16"
[3] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=31"
[4] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=46"
[5] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=61"
[6] "http://news.donga.com/search?query=bigkini&more=1&range=3&p=76"


생각처럼 잘 나왔죠? 이제 여러분은 한 걸음 더 크롤러(크롤링을 하는 프로그램) 완성에 가까이 다가가신 겁니다.



갑자기 웬 HTML?

저 URL 여섯 개는 검색 결과 페이지를 찾아가는 주소입니다. 우리가 필요한 건 기사 그 자체로 가는 URL입니다. 이제 그 주소를 찾아볼 겁니다.


인터넷 페이지는 보통 하이퍼텍스트 마크업 언어(HTML) 문서로 만듭니다.


그렇다고 HTML이 뭔지 당장 배우실 필요는 없습니다. 지금은 HTML 문서 특징만 이해하시면 됩니다.


먼저 HTML 문서는 보통 트리(tree) 구조로 돼 있습니다.


만약 윈도 탐색기로 '내 음악' 폴더에 들어 있는 어떤 음악 파일을 재생한다고 해보겠습니다. 이러려면 사용자는 내 문서 → 내 음악 → 해당 음악 파일 순서로 찾아갈 겁니다. 이게 바로 트리 구조입니다.


또 한 가지 특징은 HTML은 태그(tag)라는 녀석을 한 데 합친 결과물이라는 점입니다. 아래는 이 블로그에 쓴 'WWW의 스물 다섯 번째 생일'에서 태그 하나를 가져온 겁니다.

<a href="http://info.cern.ch/hypertext/WWW/TheProject.html" target="_blank" class="tx-link">세계 최초 웹페이지</a>


맨 앞에 있는 'a'가 바로 태그입니다. a는 링크를 만드는 구실을 합니다.


그다음에 있는 href, target, class 같은 녀석은 속성(attribute)입니다. 이 링크를 어떻게 보여줘야 할지 설정을 해주는 것.


'세계 최초 웹페이지'는 실제로 화면에 나오는 텍스트(text)입니다.


HTML에서는 태그를 끝낼 때 시작한 태그로 닫아줘야 합니다. </a>가 들어간 이유입니다.


구체적으로 href는 실제로 가야 할 URL을 담고 있습니다.


target="_blank"는 이 링크를 새 창에서 띄우라는 뜻입니다.


클래스(class)는 게시물에서 이 부분을 "tx-link"라고 미리 정한 모양대로 보여주라는 뜻입니다.


이어 나온 '세계 최초 웹페이지'라는 일곱 글자를 표시할 때 글자색은 어떻게 하고, 밑줄은 어떻게 치라는 의미입니다. 


한번 실제로 HTML 문서를 열어볼까요?


검색 결과 맨 처음에 나온 페이지에서 F12(크롬 기준)를 눌러 소스를 보겠습니다. 


 

문서가 너무 길어 보기 불편할 때는 소스 보기 창 맨 위에 있는 화살표 모양을 눌러 놓고 웹 페이지에서 원하는 부분을 클릭하면 그 부분이 나와 있는 코드로 이동합니다.


우리가 찾으려는 건 실제 기사로 이동하는 링크, 그러니까 a 태그입니다.


이걸 찾아 보니까 searchCont라는 클래스가 붙은 디비전(divisodn) 아래 링크가 들어 있다는 걸 알 수 있습니다.


이제 이 링크를 전부 긁어 오면 스크래이핑 두 번째 단계를 끝낼 수 있습니다.



rvest %>% 만세!

R로 크롤링을 할 때 가장 많이 쓰는 패키지는 rvest입니다.


아직 이 패키지를 설치하지 않으셨대도 걱정하실 거 없습니다. 그냥 install.packages("rvest")라고 R 입력창(콘솔)에 쳐넣기만 하면 됩니다.


그러고 나면 미러 선택창이 나오는데 아무 거나 고르셔도 됩니다.


R에서는 패키지를 설치만 했다고 바로 쓸 수 있는 건 아닙니다. 패키지를 메모리에 불러오는 과정이 필요합니다.


이번에도 쉽습니다. library('rvest')라고 입력하면 끝입니다.

install.packages('rvest')
library(rvest)


이제 R에 HTML을 불러와야겠죠? 이때 쓰는 함수는 read_html입니다. 한번 첫 번째 검색 페이지를 불러와 보면:

read_html(urls[1])
{xml_document}
<html lang="ko">
[1] <head>\n<title>동아닷컴</title>\n<meta name="keywords" content="뉴스, 기사, 속보 ...
[2] <body>\r\n<div class="skip"><a href="#contents">본문바로가기</a></div


잘 됩니다. 왜 urls[1]이라고 썼는지 모르는 분은 아니 계시겠죠? 그냥 두면 날아가니까 이 결과를 html이라는 변수에 넣어놓겠습니다.

html <- read_html(urls[1])


이제 기사로 가는 링크를 찾을 차례. rvest에서 특정 태그를 찾을 때는 html_node(s)를 쓰면 됩니다. s가 붙으면 같은 태그를 전부 찾고, 빼면 맨 처음에 나오는 하나만 찾습니다.


우리가 제일 먼저 찾아야 하는 건 'searchCont'라는 class가 붙은 div 태그입니다. 이건 이런 식으로 입력하면 됩니다.

html_nodes(html, '.searchCont')


'searchCont' 앞에 점(.)을 찍은 건 찾고자 하는 게 class라는 걸 알려주는 기능을 합니다.


실제로 크롤링할 때는 태그 속성 중에 class나 id를 이용할 때가 많습니다. class는 앞에 .을 찍고 id는 #을 붙여주면 그만입니다.


'html2 <- html_nodes(html, '.searchCont')'를 써서 이 결과를 html2라는 변수에 넣겠습니다.


html2에서 우리가 찾아야 하는 건 a 태그. 이번에는 html3에 저장합니다.

html3 <- html_nodes(html2, 'a')

이번에는 태그를 찾는 거니까 .이나 #을 쓰지 않았습니다. 결과는?


html3
{xml_nodeset (55)}
 [1] <a href="http://news.donga.com/3/all/20170202/82676578/1" target="_bl ...
 [2] <a href="http://news.donga.com/3/all/20170202/82676578/1" target="_bl ...
 [3] <a href="http://web.donga.com/pdf/pdf_viewer.php?vcid=2017020245A2601 ...
 [4] <a href="http://news.donga.com/3/all/20170202/82676578/1" target="_bl ...
 [5] <a href="http://news.donga.com/3/all/20170126/82600548/1" target="_bl ...
(이하 생략)


뭔가 2% 부족합니다. 우리가 진짜 찾으려는 건 맨 처음 검색 결과 주소를 저장했을 때처럼 저 href 속성 안에 들어 있는 URL이거든요.


이번에도 문제가 없습니다. rvest에는 속성을 가져오는 html_attr 함수도 있으니까요. links라는 변수에 URL만 담아 보겠습니다.

links <- html_attr(html3, 'href')
links
 [1] "http://news.donga.com/3/all/20170202/82676578/1"
 [2] "http://news.donga.com/3/all/20170202/82676578/1"
 [3] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017020245A2601"
 [4] "http://news.donga.com/3/all/20170202/82676578/1"
 [5] "http://news.donga.com/3/all/20170126/82600548/1"
(이하 생략)


원하는 결과가 나왔습니다. 그런데 어차피 html → html2 → html3 → links 순서로 자료를 이동할 건데 이렇게 한 줄, 한 줄 치면 좀 피곤하지 않은가요?


그래서 세상에 나온 게 '파이프(pipe)'라는 녀석입니다.


현실 세계에서 파이프가 한 곳에서 다른 곳으로 물 같은 액체를 보낼 수 있게 도와주는 것처럼 이 녀석도 함수에서 다른 함수로 자료를 손쉽게 보내주는 구실을 합니다.


게다가 HTML이 트리 구조로 돼 있기 때문에 파이프를 쓰면 직관적으로 이해하기도 쉽습니다.


R에서 파이프를 쓰려면 dplyr라는 패키기자 필요합니다. 마찬가지로 설치하고 불러줍니다.

install.packages('dplyr')
library('dplyr')


deplyr를 설치하고 나면 HTML 문서를 읽어서 links에 저장하는 데 이 한 줄이면 충분합니다. R에서 파이프는 '%>%'로 표시합니다.

links <- html %>% html_nodes('.searchCont') %>% html_nodes('a') %>% html_attr('href')


여러 번에 나눠쳤던 걸 한 줄에 몰아 넣은 구조입니다.


파이프는 이 방 저 방 만들지 않고 한번에 끝낼 수 있다는 게 역시 제일 큰 장점입니다. 또 머릿속에 HTML 문서 구조가 있으면 입력하기도 훨씬 수월합니다.



URL에서 U는 유니크?

사실 우리가 links라는 방에 넣어둔 URL은 여전히 2% 부족합니다. 다시 볼까요?

links
[1] "http://news.donga.com/3/all/20170202/82676578/1"
[2] "http://news.donga.com/3/all/20170202/82676578/1"
[3] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017020245A2601"
[4] "http://news.donga.com/3/all/20170202/82676578/1"
[5] "http://news.donga.com/3/all/20170126/82600548/1"

(이하 생략)


[1], [2]가 똑같고 [4], [5]도 마찬가지입니다. 이건 기사 제목에만 링크가 들어 있는 게 아니라 대표 이미지(섬네일)에도 링크가 있는데 이를 거르지 않고 한번에 크롤링해서 생긴 일입니다. 


이번에도 걱정하실 거 없습니다. R에서는 중복 자료를 정리해 보여주는 unique라는 함수가 있거든요.


처음부터 파이프를 써서 'links <- html %>% html_nodes('.searchCont') %>% html_nodes('a') %>% html_attr('href') %>% unique()'라고 입력했어도 아래하고 같은 결과가 나왔을 겁니다.

 links <- unique(links)
links
[1] "http://news.donga.com/3/all/20170202/82676578/1"
[2] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017020245A2601"
[3] "http://news.donga.com/3/all/20170126/82600548/1"
[4] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017012645A2601"
[5] "http://news.donga.com/3/all/20170120/82482150/1"

(이하 생략)


이번에도 좀 이상합니다. [1]하고 [2]가 스타일이 달라 보입니다. 실제로 저 주소를 브라우저에 입력해 보면 [1] 같은 형태는 인터넷 기사로 가는 반면 [2]는 지면 PDF로 연결한다는 걸 확인할 수 있습니다. 저런 건 빼야겠죠?


그때 도와주는 게 grep이라는 함수입니다. 딱 봐도 주소에 pdf가 들어가 있을 때 빼면 됩니다.


그러면 먼저 pdf가 들어간 걸 찾아 보겠습니다.

grep("pdf", links)
 [1]  2  4  6  8 10 12 14 16 18 20 22 24 26 28 30


이상하게(?) 숫자가 나옵니다. 이 숫자는 pdf가 들어간 행을 나타냅니다. 위를 보시면 정말 [2], [4]에 pdf가 들어있네요


 grep은 특정한 문자(열)가 들어간 자료 순서를 알려준다는 사실을 알 수 있습니다. 만약 links[grep("pdf", links)]라고 입력하면 어떤 결과가 나올까요?

links[grep("pdf", links)]
[1] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017020245A2601"
[2] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017012645A2601"
[3] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2017012045A2601"
[4] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2016122845A3001"
[5] "http://web.donga.com/pdf/pdf_viewer.php?vcid=2016122245A3101"

(이하 생략)


pdf가 들어간 링크만 골라 보여줍니다. 위에서 변수 이름 옆에 []를 치면 그 순서에 맞는 자료만 보여준다는 걸 확인했습니다.


이번에는 [grep("pdf", links)]라고 입력했으니까 그 결과에 따라 순서를 골라 보여준 겁니다.


우리가 원하는 pdf가 들어간 자료만 남기는 게 아니라 빼는 겁니다. 특정 자료만 뺄 때는 []사이에 빼기(-) 표시를 하면 됩니다.


links[-grep("pdf", links)]처럼 말입니다.

links[-grep("pdf", links)]
[1] "http://news.donga.com/3/all/20170202/82676578/1" 
[2] "http://news.donga.com/3/all/20170126/82600548/1" 
[3] "http://news.donga.com/3/all/20170120/82482150/1" 
[4] "http://news.donga.com/3/all/20161228/82065527/1" 
[5] "http://news.donga.com/3/all/20161222/81980312/1"

(이하 생략)


좋습니다. 이제 이걸 links에 담으면 되니까 links <- links[-grep("pdf", links)]를 치면 끝입니다.


(이 꼭지 제목은 사실과 다릅니다. URL은 Uniform Resource Locator를 줄인 말입니다.)



Back at one

이제 겨우 딱 한 페이지에서 기사로 가는 URL을 찾았습니다. 여섯 페이지에서 똑같은 작업을 반복해야 하죠. 뭘 써야 할까요?


네, 정답은 반복문입니다. 


이때는 이미 urls에 URL 여섯 개가 들어 있는 상태이기 때문에 x 같은 변수를 따로 지정할 필요는 없습니다. 그냥 '변수 in 변수' 형태로 표현하면 그만입니다.


우리는 urls라는 변수에 URL을 담아 놓았으니 'url in urls'라고 표현해 보겠습니다.


아, 뭐 잊은 게 없냐고 묻는 분이 계시네요. 맞습니다. 칸이 있는 방(변수)을 쓰려면 미리 빈 방이라고 한번 선언을 해줘야 합니다.

links <- NULL
for(url in urls){
 html <- read_html(url)
 links <- c(links, html %>% html_nodes('.searchCont') %>% html_nodes('a') %>% html_attr('href') %>% unique())
 }


낯선 표현이 하나 있습니다. c(links, 블라블라) 형태네요. c는 자료를 묶는다는 뜻이라고 위에서 확인했습니다.


이 줄은 links라는 변수에 자료를 계속 더하라는 뜻입니다.


예를 들어:

x <- 1
x <- 2
x
[1] 2


이렇게 치고 나서 x를 확인해 보면 2가 나옵니다. 맨 마지막에 입력한 값이 나오는 것.


이때 1과 2를 모두 가지고 있는 변수를 만들고 싶다면 아래처럼 치면 됩니다.

x <- 1
x <- c(x, 2)
x
[1] 1 2


아직 PDF 링크는 그대로 남아 있으니까 grep까지 적용하고 나서 확인해 보시면 links안에 URL이 88개 들어 있는 걸 확인하실 수 있습니다. 처음에 검색 결과에 나왔던 바로 그 숫자입니다.

length(links)
[1] 88


length는 이름 그대로 자료 길이(숫자)를 알려주는 함수입니다. length(urls)를 치면? 처음에 링크 6개를 넣어 뒀으니까 6이 나옵니다. 이제 슬슬 목적지가 보입니다.



Misson Complete

URL이 있으니까 HTML 문서를 열어봐야겠죠?


개별 기사 소스를 확인하니까 'article_txt'라는 클래스로 된 div 안에 기사 본문이 들어 있는 게 보입니다.



클래스로 찾을 때는 .을 찍는다는 것만 기억하시면 어렵지 않습니다. 바로 앞에 했던 작업과 다른 건 본문 텍스트를 긁어 오는 거니까 html_text 함수를 써야 한다는 것뿐입니다.


그러면 이렇게 정리가 끝납니다.

txts <- NULL
for(link in links){
  html <- read_html(link)
  txts <- c(txts, html %>% html_nodes('.article_txt') %>% html_text())
 }
txts
[1] "\n            심심풀이로 봤습니다, 프로야구 감독들
[2] "\n            \n             “차우찬이 95억 원씩이나 받
[3] "\n            현실 안맞는 일률 보상규정 때문에 충분히
[4] "\n            11개 종합지 올 시즌 기사 3742건 분석\n 
[5] "\n            \n             올해 가장 비효율적으로 돈을

(이하 생략)


축하드립니다. 여러분은 지금 생애 첫 크롤러(스크래이퍼)를 완성하셨습니다.


이제 이 프로그램을 잘 보완하시면 (이론적으로는) 구글이나 네이버 같은 검색 엔진도 만들 수 있습니다.


이렇게 얻은 결과를 나중에 또는 다른 프로그램에 활용하고 싶을 수도 있으니까 저장만 하면 끝입니다.


이럴 때는 보통 CSV(Comma Separated Values)라는 형태를 많이 씁니다. 이 형식은 문자 그대로 쉼표(comma)로 구분한 값이라는 뜻인데요, 그냥 쉽게 이해하시려면 텍스트(.txt) 파일 한 형태라고 보시면 됩니다.

write.csv(txts, "text.csv")


이렇게 입력하시면 여러분이 지정한 R 작업 폴더(윈도 기본은 '내 문서')에 text.csv 파일이 생긴 걸 확인하실 수 있습니다.



ABC, DEF…

클로링한 자료는 여러분이 활용하시기 나름입니다. txts[4]에 등장하는 건 이 기사입니다.


텍스트 데이터로 할 수 있는 가장 기본이라고 할 수 있는 낱말 구름(word cloud)을 그렸습니다.



이 때는 R가 아니라 파이선(python)이라는 프로그래밍 언어를 썼는데 방식은 R하고 100% 똑같다고 해도 과언이 아닙니다. 말(언어)이 다르니 문법이 조금 다를 뿐이었습니다.


이 글은 크롤링 혹은 웹 스크래이퍼가 뭔지 전혀 모르지만 한번 그런 걸 만들어 보고 싶어하는 분들께 도움이 될까 해서 써봤습니다.


사실 저도 이런 건 만들어 보고 싶은 마음은 퍽 예전부터 있었는데, 코딩은 ABC도 잘 모르다 보니 어디서부터 시작해야 할지 막막했거든요. 


이런 분들께 도움을 드리려고 최대한 자세하게 쓰다 보니 글이 너무 길어졌습니다.


모쪼록 도움을 받는 분이 계시길 바라며 코드를 정리해 남깁니다.


아래 코드를 그냥 가져다 R 콘솔에 붙이기만 하셔도 똑같은 결과는 얻으실 수 있습니다.


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

basic_url <- 'http://news.donga.com/search?query=bigkini&more=1&range=3&p='

urls <- NULL
for(x in 0:5){
  urls[x+1] <- paste0(basic_url, as.character(x*15+1))
  }

links <- NULL
for(url in urls){
  html <- read_html(url)
  links <- c(links, html %>% html_nodes('.searchCont') %>% html_nodes('a') %>% html_attr('href') %>% unique())
  }
links <- links[-grep("pdf", links)]

txts <- NULL
for(link in links){
  html <- read_html(link)
  txts <- c(txts, html %>% html_nodes('.article_txt') %>% html_text())
  }

write.csv(txts, "text.csv")


댓글,

KindeR | 카테고리 다른 글 더 보기