Naver API로 데이터 수집 XML 파싱과 정제 후 파일 입출력까지 [데이터 분석 with R]

안녕하세요. 언제나 휴일에 언휴예요.

이번 강의는 Naver API를 이용하여 도서 검색을 수행합니다.

XML 방식으로 검색한 후에 XML 데이터 중에 원하는 부분만 추출하고 data.frame으로 정제합니다.

정제한 자료를 파일 입출력까지 진행까지 진행합니다.

이미 이전 강의에서 Kakao 도서 검색 API(Json 방식)를 이용하여 수집 및 정제 후 파일 입출력을 해 보았습니다. 이번에는 XML 방식이라는 부분에서 차이가 있으며 보다 깔끔하게 정제해 볼 거예요.

- 네이버 검색 API를 이용하여 웹 요청(도서 검색 XML 방식)
- 웹 요청 결과 XML Document로 변환 후 원하는 노드 집합 추출
- XML 자료를 data.frame으로 변환 및 정제
- 파일 입출력 (CSV로 입출력 및 Binary로 입출력)

네이버 검색 API를 이용하여 웹 요청(도서 검색 XML 방식)

Kakao API에서 도서 검색하는 것과 웹 요청 작업 절차는 같습니다.

요청할 site_url과 헤더에 client id와 client secret를 전달하는 부분에서의 차이가 있을 뿐입니다.

client_id ="[발급받은 client id]"
client_secret = "[발급받은 client secret]"
site_url = "https://openapi.naver.com/v1/search/book_adv.xml"
query = "R언어"
query = URLencode(iconv(query,to="UTF-8"))
query_str = sprintf("%s?d_titl=%s",site_url,query)
library(httr)
resp = GET(query_str,add_headers("X-Naver-Client-Id"=client_id,"X-Naver-Client-Secret"=client_secret))

웹 요청 결과 XML Document로 변환 후 원하는 노드 집합 추출

이제 XML 파싱을 해야 하는데 여기에서는 xml2 라이브러리를 사용할게요.

웹 요청한 response 객체를 xml document로 변환할 때 read_xml 함수를 이용합니다.

네이버 검색 결과는 rss>channel>node 컬력션 형태로 구성하고 있습니다.

루트의 자식 노드인 채널 노드에 접근하기 위해 xml_child 함수를 호출합니다.

채널 노드에서 item 자식 노드들을 얻기 위해 xml_find_all 함수를 호출합니다.

library(xml2)
rxml = read_xml(resp)
cn = xml_child(rxml)
items = xml_find_all(cn,"item")

rxml을 확인해 보면 xml_document인 것을 알 수 있어요.

> rxml
{xml_document}
... 중략...

cn과 items는 여러분이 확인해 보세요. channle 노드 부분입니다.

items 부분은 책의 정보가 있는 item 노드들의 컬렉션입니다.

XML 자료를 data.frame으로 변환 및 정제

깔끔하게 정제하기 위한 라이브러리로 tidyr 가 있습니다.

lappy함수는 순차적으로 함수에 적용한 결과로 list를 만들어줍니다.

lapply(X, FUN, …)

X에 벡터나 객체 표현이 온 것을 두 번째 인자 함수에 적용하는 함수입니다.

items에 있는 각 item 노드에 있는 것을 함수에 적용하기 위해 lapply 함수를 호출할 거예요.

각 item 노드로 tibble 객체를 만들게요.

tibble(…)

tibble은 data.frame을 깔끔하게 정제하기 위해 사용하는 형식 중 하나입니다.

여기에서는 item 노드 하나를 순번, 키, 값으로 만들고자 합니다.

키는 요소의 attribute 이름으로 title, author, publisher 등으로 테이블의 컬럼 명으로 사용할 것들입니다.

값은 attribute의 내용입니다.

library(tidyr)
ld= lapply(1:length(items),
             function(n){
               temp_row = xml_find_all(items[n],'./*')
               tb = tibble(
                 index=n,
                 key =  xml_name(temp_row),
                 value = xml_text(temp_row)
               )
               return(tb)
             })

다음은 ld 를 확인한 것으로 list 형식 객체입니다.

각 항목은 책 한 권에 대한 정보로 구성하고 있습니다.

이제 ld에 있는 내용을 data.frame으로 변환할 거예요.

이러한 정제 작업에 강력한 dplyr 라이브러리를 사용할게요.

먼저 각 항목의 구조가 같아서 하나의 row로 취급할 수 있습니다.

bind_rows를 호출하여 이용할게요.

현재는 컬럼이 index, key, value입니다.

link, image, author, price 등의 attribute를 컬럼으로 바꾸기 위해 spread 함수를 호출할게요.

마지막으로 맨 앞에 index가 불필요하니 나머지 요소만 선택하기 위해 select 함수를 호출하세요.

brs = bind_rows(ld)
library(dplyr)
spd = spread(brs, key,value)
df = select(spd,2:length(spd))

 

파일 입출력 (CSV로 입출력 및 Binary로 입출력)

깔끔한 data.frame 형태로 만들어서 바로 CSV 파일로 저장이 가능합니다.

하지만 다시 읽었을 때 원래 구조는 사라집니다. 물론 data.frame 구조는 유지합니다.

write.csv(df,"data.csv",row.names = FALSE)
df2 = read.csv("data.csv")

df2값과 형식을 확인해 보면 data.frame은 유지하지만 df처럼 깔끔하지는 않습니다.

                        author
1     폴 제라드|라디아 M. 존슨
2                가렛 그롤먼드
3         홍정하|유원호|강병규
4                       이용훈
5  박성호|문경희|이호창|조미숙
6             제임스 W. 파팅턴
7                마이클 크롤리
8                슈테판 그리스
9                    켈리 블랙
10            제임스 W. 파팅턴
... 중간 생략...
[1] "data.frame"

현재 상태를 유지하기를 원한다면 save, load 함수를 이용하여 Binary 입출력을 하길 권합니다.

주의할 점은 load함수를 호출하면 save 함수에서 저장한 객체 자체가 만들어집니다.

테스트를 위해 save 함수 호출 후에 df 객체를 제거한 후에 laod함수를 호출하세요.

save(df,file="data.RData")
rm(df)
ls() #현재 객체들 확인
load("data.RData")
ls() #현재 객체들 확인