[기업 이미지 파악을 위한 소비자 후기 키워드 분석]

 (바로가기를 클릭하시면 해당 게시글로 넘어갑니다.)



Ⅰ 웹 페이지 크롤링


1. 크롬 드라이버 실행 (바로가기)


2. 웹 페이지 크롤링  (바로가기)



Ⅱ 키워드 추출


3. 명사 추출 및 카운팅 (바로가기)


# 1-1. 크롬 드라이버 실행


ch = wdman::chrome(port=4567L)


> ch = wdman::chrome(port=4567L)

checking chromedriver versions:

BEGIN: PREDOWNLOAD

BEGIN: DOWNLOAD

BEGIN: POSTDOWNLOAD



chrome <- remoteDriver(remoteServerAddr = "localhost", port=4567L, browserName='chrome')

chrome$open()


> chrome$open()


'wdman' 패키지에 있는 'chrome'를 이용하여 크롬 드라이버에 포트를 배정해 주었습니다. 배정한 포트를 이용하여 원격접속 설정을 하고 'open'함수를 사용하면 다음과 같이 원격 조종되는 크롬 창이 실행됩니다. 'chrome'으로 여러가지 기능을 사용할 수 있는데, 대표적으로는 특정 사이트에 접속하는 'navigate'가 있습니다. 사용 방법은 아래와 같습니다.



chrome$navigate("https://www.google.com")


> chrome$navigate("https://www.google.com")


# 2-1. URL 구조 파악



국내 포털사이트 중 하나인 네이버에서 블로그 검색을 하면 한 페이지당 10개의 글이 노출됩니다. 페이지를 변경할 때 주소가 어떻게 달라지는 지를 확인하니 다음과 같았습니다.


https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=1


https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=11


https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=21


https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=31


앞 부분은 동일하므로 별도의 변수로 저장해주고 뒤의 숫자만 'for문'을 이용해서 변화시켜 주소를 한번에 담아주겠습니다.




# 2-2. URL 저장


target_url <- 'https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start='


urls <- NULL

for (i in 0:99){

 urls[i+1] <- paste0(target_url, i*10+1)

}
head(urls, 5)

> head(urls, 5)

 [1] "https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=1"  

 [2] "https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=11"

 [3] "https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=21"

 [4] "https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=31"

 [5] "https://search.naver.com/search.naver?date_from=&date_option=0&date_to=&dup_remove=1&nso=&post_blogurl=&post_blogurl_without=&query=%EC%A0%9C%EC%A3%BC%ED%95%AD%EA%B3%B5&sm=tab_pge&srchby=all&st=sim&where=post&start=41"


 'paste0'를 이용해 고정된 부분과 변화하는 부분을 더해 주소 리스트를 만들어 주었습니다. 'head'만 출력해본 결과 제대로 생성되었음을 확인할 수 있었습니다.


# 2-3. 사이트 원격 접속 및 HTML 페이지 저장


 크롬 드라이버 실행 시 사용해 보았던 'navigate'를 사용하면 페이지에 접속할 수 있습니다. 접속 후 개발자 도구를 이용하여 태그 정보를 확인, HTML 소스를 불러오겠습니다. '태그 명'은 노란 박스를 참조해주세요.


for (i in 0:99){

 chrome$navigate(urls[i+1])

html_txt <- read_html(urls[i+1])

}




html_class <- html_nodes(html_txt, '.blog.section._blogBase')

html_content <- html_nodes(html_class, '.sh_blog_top')  




html_titles <- html_nodes(html_content, '.sh_blog_title._sp_each_url._sp_each_title')




html_contents <- html_nodes(html_content, '.sh_blog_passage')



불러올 정보는 타이틀과 검색 화면에서 노출된 텍스트입니다. 이 정보가 담긴 부분은 개발자 도구를 사용하면 쉽게 확인할 수 있습니다. 먼저 큰 클래스에 접근한 뒤 작은 클래스로 접근하면 다음과 같이 깔끔하게 필요한 부분이 담긴 것을 확인할 수 있습니다.



head(html_titles)


> head(html_titles)

{xml_nodeset (6)}

[1] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/ssangcopi335?Redirect=Log&amp;lo ...

[2] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/jennyan1117?Redirect=Log&amp;log ...

[3] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/hoorayhm?Redirect=Log&amp;logNo= ...

[4] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/starapplesand?Redirect=Log&amp;l ...

[5] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/kol8321?Redirect=Log&amp;logNo=2 ...

[6] <a class="sh_blog_title _sp_each_url _sp_each_title" href="https://blog.naver.com/gayoun3?Redirect=Log&amp;logNo=2 ...



head(html_contents)


>   head(html_contents)

{xml_nodeset (6)}

[1] <dd class="sh_blog_passage">\n<strong class="hl">제주 항공</strong> 면접 블라우스 당신의 꿈에 날개를 달아드립니다. 면접 복장 전문 드림윙즈입니다. <stro ...

[2] <dd class="sh_blog_passage">\n<strong class="hl">제주항공</strong> JJ 멤버스 위크 7월 출발 항공권 <strong class="hl">제주항공</strong ...

[3] <dd class="sh_blog_passage">\n<strong class="hl">제주항공</strong>을 타고 사이판을 가면 얻게되는 혜택들이 엄청난데요! 바로... <strong class="h ...

[4] <dd class="sh_blog_passage">안녕하세요 충순입니다 오늘은 <strong class="hl">제주항공</strong>의 회원 등급과 리프레시 포인트에 대해 알려드리려고 해요 ▲ <str ...

[5] <dd class="sh_blog_passage">\n<strong class="hl">제주항공</strong> 서포터즈 ː 제주에이터 캠퍼스어택 '신촌 연세대학교' 에 가다! 안녕하세요! #<strong ...

[6] <dd class="sh_blog_passage">나름 저렴하게 <strong class="hl">제주항공</strong>으로 왕복/Tax포함 1인 26만원 정도에 다녀왔다. 항공편명 : 인천-사이판 7C ...




# 2-4. TEXT 저장


text_titles <- str_replace_all(html_text(html_titles), "\\W", " ")

text_contents <- str_replace_all(html_text(html_contents), "\\W", " ")


추출한 html 소스를 'html_text'를 이용해 텍스트로 저장했습니다. 저장 시 필요없는 특수문자(정규표현식: '\\W')를 'str_replace_all'을 이용하여 제거해주었습니다. 'head'를 이용해 일부를 출력해보면 다음과 같이 html 코드 부분을 제외한 텍스트만 남아있는 것을 알 수 있습니다.



head(text_titles)


> head(text_titles)

[1] "제주 항공 면접 블라우스 디테일을 살려 구매하기"             

[2] "제주항공 JJ멤버스 위크 특가 7월 출발 항공권"                

[3] " 조이버  사이판갈때는 제주항공타고 할인 혜택받자 "          

[4] "정보  제주항공 회원 등급 리프레시 포인트"                   

[5] " 조이버 14기  제주항공 서포터즈 ː 제주에이터 캠퍼스어택   "

[6] " 사이판  제주항공 사이판 밤비행 이용 후기   ESTA 이스타 "



head(text_contents)


>   head(text_contents)

[1] "제주 항공 면접 블라우스 당신의 꿈에 날개를 달아드립니다  면접 복장 전문 드림윙즈입니다 제주 항공 면접 블라우스 서류 발표가 나면 면접까지 준비기간이 짧기   "

[2] "제주항공 JJ 멤버스 위크 7월 출발 항공권 제주항공 얼리버드 특가 JJ멤버스 위크 시작합니다 판매기간 2018년 3월 7일 오전 10시부터 3월 13일까지 출발기간 2018년   "

[3] "제주항공을 타고 사이판을 가면 얻게되는 혜택들이 엄청난데요  바로 제주항공을 타고 사이판에서 켄싱턴 호텔에서 숙박하게 되면 객실료1박당 20불 할인   "

[4] "안녕하세요 충순입니다 오늘은 제주항공의 회원 등급과 리프레시 포인트에 대해 알려드리려고 해요   제주항공 JEJU AIR 제주항공 리프레시 포인트란 리프레시 "

[5] "제주항공 서포터즈 ː 제주에이터 캠퍼스어택  신촌 연세대학교 에 가다 안녕하세요 제주항공 서포터즈  조이버 14기 애리입니다 오늘은 저희 팀 제주에이터 "         

[6] "나름 저렴하게 제주항공으로 왕복 Tax포함 1인 26만원 정도에 다녀왔다  항공편명 인천 사이판 7C3404 사이판 인천 7C3403 제주항공은 여러번 이용해본 적 있는 터라      "


# 3-1. 명사 추출


nount <- extractNoun(text_titles)

nounc <- extractNoun(text_contents)


head(nount, 3)


> head(nount, 3)

[[1]]

[1] "제주"     "항공" "면접"     "블라우스" "디테일" "구매"     "하" "기"


[[2]]

[1] "제주"     "항공" "JJ멤버스" "위크"     "특가" "7" "월"    "출발" "항공" "권"


[[3]]

[1] "조이버" "사이판" "갈"     "제주" "항공" "타고" "할인"   "혜택받"



head(nounc, 3)


> head(nounc, 3)

[[1]]

[1] "제주"           "항공" "면접"          "블라우스" "당신" "꿈"            

[7] "날개"           "면접" "복장"          "전문" "드림윙즈입니다" "제주"         

[13] "항공"           "면접" "블라우스"       "서류" "발표"  "나"

[19] "면접"           "준비" "기간"          "짧" "기"


[[2]]

[1] "제주"     "항공" "JJ"      "멤버스" "위크" "7"        "월" "출발" "항공권" "제주"    

[11] "항공"     "얼리버드" "특가"  "JJ멤버스" "위크" "시작"     "판매" "기간" "2018"   "년"

[21] "3"        "월" "7"     "오전" "10" "시"       "3" "월" "13"  "일"

[31] "출발"     "기간" "2018"     "년"


[[3]]

[1] "제주"        "항공" "사"        "이" "판" "혜택"        "들이" "제주"

[9] "항공"        "사" "이"         "판" "켄싱턴" "호텔"        "숙박" "하"

[17] "객실료1박당" "20"          "불" "인"


명사 추출을 위해서 'KoNLP'에 있는 'useNIADic()'을 사용하였습니다. 또한 n행 1열의 데이터 프레임 형태로 만들어 주기 위해 아래와 같이 'for문'을 이용하였습니다.



df_nount <- NULL

df_nounc <- NULL


for (i in 0:9){

  df_nount_b <- as.data.frame(nount[i+1])

  names(df_nount_b) <- c('keywords')

  df_nount <- rbind(df_nount, df_nount_b)  

  df_nounc_b <- as.data.frame(nounc[i+1])

  names(df_nounc_b) <- c('keywords')

  df_nounc <- rbind(df_nounc, df_nounc_b)

}


jkeywords <- rbind(df_nount, df_nounc)

head(jkeywords)


> head(jkeywords)

 keywords

1     제주

2     항공

3     국제

4       선

5   파일럿

6   기내식




# 3-2. 카운팅 (빈도 확인)


추출된 키워드는 'group_by'를 이용해 동일 키워드를 기준으로 묶고 'summarise'와 'n()'을 사용해 카운팅했습니다. 'order'를 이용하여 내림차순 정렬하면 빈도수가 높은 단어 순으로 확인할 수 있습니다.



jkeywords$keywords <- as.character(jkeywords$keywords)

count_key <- jkeywords %>% filter(nchar(keywords)>=2 & nchar(keywords)<=9) %>% group_by(keywords) %>% summarise(n=n())

keywords <- count_key[order(-count_key$n), ]


head(keywords, 10)


> head(keywords, 10)

# A tibble: 10 x 2

  keywords     n

  <chr>    <int>

1 제주            3174

2 항공            2913

3 여행            586

4 특가            302

5 항공권         301

6 이용            170

7 출발            164

8 항공우주    164

9 2018           138

10 시간           135



[인스타그램 해시태그(#) 검색 시 관련 여행 국가 출력]

 (바로가기를 클릭하시면 해당 게시글로 넘어갑니다.)



Ⅰ 필요 데이터 제작


1. 전세계 모든 국가, 일부 도시명 리스트 영어, 한국어, 일본어 번역(Google Translate API) (바로가기)


2. 도시의 위도, 경도 얻기(ggmap, Google Map API) (바로가기)


3. 공항 리스트 얻어오기(출처: ICAO) (바로가기)


(참고) 데이터 자동 번역을 위한 Google translate API Key 발급 (예정)


(참고) 데이터프레임에서 중복값이 있는 필드(행) 제거하기(예정)



Ⅱ 해시태그(#) 얻어오기


4. 특정 해시태그(#)가 포함된 글의 모든 해시태그 얻어오기 (예정)


(참고) ...  (예정)



Ⅲ 추출된 해시태그(#)를 관련 공항으로 연결시키기


5. 해쉬태그와 가장 관련이 높은 도시 연결하기(도시명 리스트 이용) (예정)


6. 해당 도시와 가장 가까운 공항 연결하기(같은 국가 내에서만 위, 경도를 통해 거리 계산) (예정)


7. 공항 출력 (예정)







# 1-1. 파일 불러오기, 사용할 필드 선택하기


country_city01 <- read.csv("world-cities.csv", stringsAsFactors = FALSE)

head(country_city01)


> country_city01 <- read.csv("world-cities.csv", stringsAsFactors = FALSE)

> head(country_city01)

              name              country         subcountry geonameid

1     les Escaldes              Andorra Escaldes-Engordany   3040051

2 Andorra la Vella              Andorra   Andorra la Vella   3041563

3   Umm al Qaywayn United Arab Emirates     Umm al Qaywayn    290594

4   Ras al-Khaimah United Arab Emirates    Raʼs al Khaymah    291074

5     Khawr Fakkān United Arab Emirates       Ash Shāriqah    291696

6            Dubai United Arab Emirates              Dubai    292223


세계의 모든 국가, 도시명이라고는 했지만 정확한 자료를 구하기가 힘들어 MySQL에서 제공하는 World DB 안의 테이블을 사용하였습니다. 도시명(name), 국가명(country) 등 4개의 필드로 구성된 것을 확인할 수 있습니다. 본 프로젝트에서는 도시명과 국가명이 필요하므로 아래와 같이 필요한 필드만 따로 저장하겠습니다.


country_city02 <- country_city01 %>% select(name, country)

head(country_city02)


> country_city02 <- country_city01 %>% select(name, country)

> head(country_city02)

              name              country

1     les Escaldes              Andorra

2 Andorra la Vella              Andorra

3   Umm al Qaywayn United Arab Emirates

4   Ras al-Khaimah United Arab Emirates

5     Khawr Fakkān United Arab Emirates

6            Dubai United Arab Emirates




# 1-2. 구글 translate API 사용 위해 API key 불러오기


out <- read_rds("api_key.enc.rds")

api_key <- decrypt_envelope(out$data, out$iv, out$session, "/.ssh/id_api", password="") %>%

   unserialize()


한국어, 일본어 필드를 추가하기 위해 구글에서 제공하는 translate API를 이용해 자동번역을 했습니다. translate API key 발급에 관한 내용은 아래 포스팅을 참조해주세요. 저 같은 경우 api key를 미리 암호화해두었기 때문에 키를 불러오는 과정을 거쳤습니다.


(참고) 데이터 자동 번역을 위한 Google translate API Key 발급 (예정)




# 1-3. 영문 도시명 한국어/일본어로 번역하기


country_city03 <- translateR::translate(dataset = country_city02,

                               content.field = 'name',

                               google.api.key = api_key,

                               source.lang = 'en',

                               target.lang = 'ko')

country_city03 <- country_city03 %>% select(city_ko = translatedContent)


country_city03 <- str_replace_all(country_city03$city_ko, "[a-zA-Z]+", " ") %>%

as.data.frame()

country_city03 <- str_replace_all(country_city03$., "[0-9]+", " ") %>% as.data.frame()

country_city03 <- str_replace_all(country_city03$., "\\W", " ") %>% as.data.frame()

country_city03 <- str_replace_all(country_city03$., "[à-źÀ-Ź]+", " ") %>% as.data.frame()

country_city03 <- str_replace_all(country_city03$., "\\s", "") %>% as.data.frame()


head(country_city03)


> head(country_city03) city_ko 1    레에스칼데스 2    안도라라벨라 3    음알케이웨이 4    라스알카이마 5 6 두바이


영문 도시명이 담긴 데이터프레임(dataset)에서 해당 필드(content.field)를 영어(source.lang)에서 한국어(target.lang)로 번역하였습니다. 패키지 'translate'와 'translateR'이 충돌하여 사용 패키지를 코드에 명시해 주었는데, 'translateR'만 로드하신 분들은 [translateR::]부분은 제외하고 코드 작성하시면 될 것 같습니다. 또한, 원본 데이터가 영문이라고 표기되어 있지만 핀란드어, 스페인어 등에서 나타나는 알파벳이 좀 있고, Google에서 인식하지 못하는 값들이 있어 불량 값들을 제거하는 과정을 거쳤습니다. 결과를 헤드로 출력한 결과 번역이 잘 되었음을 확인할 수 있었습니다. 일본어 및 국가명 번역도 위와 동일하게 진행하겠습니다.


country_city04 <- translateR::translate(dataset = country_city02,

                               content.field = 'name',

                               google.api.key = api_key,

                               source.lang = 'en',

                               target.lang = 'ja')

country_city04 <- country_city04 %>% select(city_ja = translatedContent)


country_city04 <- str_replace_all(country_city04$city_ja, "[a-zA-Z]+", " ") %>% as.data.frame()

country_city04 <- str_replace_all(country_city04$., "[0-9]+", " ") %>% as.data.frame()

country_city04 <- str_replace_all(country_city04$., "\\W", " ") %>% as.data.frame()

country_city04 <- str_replace_all(country_city04$., "[à-źÀ-Ź]+", " ") %>% as.data.frame()

country_city04 <- str_replace_all(country_city04$., "\\s", "") %>% as.data.frame()


head(country_city04)


> head(country_city04)

           city_ja

1                 

2     アンドララベリャ

3                 

4      ラスアルカイマ

5                 

6            ドバイ




# 1-4. 영문 국가명 한국어/일본어로 번역하기


country_city05 <- translateR::translate(dataset = country_city02,

                               content.field = 'country',

                               google.api.key = api_key,

                               source.lang = 'en',

                               target.lang = 'ko')

country_city05 <- country_city05 %>% select(country_ko=translatedContent)

head(country_city05)


> head(country_city05) country_ko 1 안도라 2 안도라 3 아랍 에미리트 4 아랍 에미리트 5 아랍 에미리트 6 아랍 에미리트


country_city06 <- translateR::translate(dataset = country_city02,

                               content.field = 'country',

                               google.api.key = api_key,

                               source.lang = 'en',

                               target.lang = 'ja')

country_city06 <- country_city06 %>% select(country_ja=translatedContent)

head(country_city06)


> head(country_city06)

        country_ja

1         アンドラ

2         アンドラ

3 アラブ首長国連邦

4 アラブ首長国連邦

5 アラブ首長国連邦

6 アラブ首長国連邦


국가명의 경우 원본 데이터(영문)에서 구글 번역기가 인식 가능한 값만 담겨있어 오류 값 없이 모두 정상적으로 번역되었습니다. 따라서 불량 값을 제거하는 과정은 따로 진행하지 않았습니다.




# 1-5. 최종 정제(중복 제거) 후 파일로 저장하기


country_city07 <- country_city02 %>%

       mutate(country_city_ko=country_city03$city_ko, country_ko=country_city05$country_ko,

country_city_ja=country_city04$city_ja, country_ja=country_city06$country_ja)

table(duplicated(country_city07$city))


> table(duplicated(country_city07$city))

FALSE    TRUE

16224    957


'duplicated'의 테이블을 출력한 결과 도시명에서 중복되는 값이 957개가 있는 걸 알게되었습니다. 도시명이 중복되어도 필드 내의 다른 칼럼의 값이 달라 'unique'를 통해서는 제거하기 힘드니 'filter'를 이용하여 TRUE, FALSE 출력에 대한 조건을 이용하여 중복되는 값을 제거해주겠습니다. 조건을 이용할 경우 중복되는 값들 중 하나만 남게 됩니다. 중복에 대한 자세한 내용은 아래 포스팅을 참조해주세요.


(참고) 데이터프레임에서 중복값이 있는 필드(행) 제거하기(예정)


country_city <- country_city07 %>% filter(duplicated(test07$city)=="FALSE")

str(country_city07)
str(country_city)

> str(country_city07)

'data.frame':     17182 obs. of  6 variables:

$ city      : chr "les Escaldes" "Andorra la Vella" "Umm al Qaywayn" "Ras al-Khaimah" ...

$ country   : chr "Andorra" "Andorra" "United Arab Emirates" "United Arab Emirates" ...

$ city_ko   : chr "레에스칼데스" "안도라라벨라" "음알케이웨이" "라스알카이마" ...

$ country_ko: chr  "안도라" "안도라" "아랍 에미리트" "아랍 에미리트" ...

$ city_ja   : chr NA "アンドララベリャ" NA "ラスアルカイマ" ...

$ country_ja: chr  "アンドラ" "アンドラ" "アラブ首長国連邦" "アラブ首長国連邦" ...


> str(country_city)

'data.frame':       16225 obs. of  6 variables:

 $ city      : chr  "les Escaldes" "Andorra la Vella" "Umm al Qaywayn" "Ras al-Khaimah" ...

 $ country   : chr  "Andorra" "Andorra" "United Arab Emirates" "United Arab Emirates" ...

 $ city_ko   : chr  "레에스칼데스" "안도라라벨라" "음알케이웨이" "라스알카이마" ...

 $ country_ko: chr  "안도라" "안도라" "아랍 에미리트" "아랍 에미리트" ...

 $ city_ja   : chr  NA "アンドララベリャ" NA "ラスアルカイマ" ...

 $ country_ja: chr  "アンドラ" "アンドラ" "アラブ首長連邦" "アラブ首長連邦" ...


중복값을 제거한 결과 17,182개였던 데이터가 16,225개로 줄었음을 확인할 수 있었습니다. 사실... 중복제거를 미리 하지 않아서 뒤의 과정을 진행하던 중 문제가 생겼습니다. 뒤늦게 중복 제거 하고, 원본 데이터와 가공된 데이터를 다시 끼워 맞추느라 고생한건... 많은 시간이 버려진 건!!! 안비밀입니다!!! 전처리가 다 끝났다고 생각될 때 혹시 모르니 중복 데이터가 있는지 꼭! 꼭! 꼭! 확인해보시는걸 추천드립니다. (전처리를 잘하자!!!)


write.csv(country_city, "country_city.csv")

head(country_city)


> head(country_city)

  city              country              city_ko    country_ko   city_ja      country_ja

1 les Escaldes      Andorra         레에스칼데스  안도라                   アンドラ

2 Andorra la Vella  Andorra         안도라라벨라  안도라        アンドララベリャ アンドラ

3 Umm al Qaywayn United Arab Emirates 음알케이웨이 아랍 에미리트              アラブ首長国連邦

4 Ras al-Khaimah United Arab Emirates 라스알카이마  아랍 에미리트  ラスアルカイマ アラブ首長国連邦

5 Khawr Fakkān United Arab Emirates            아랍 에미리트              アラブ首長国連邦

6 Dubai United Arab Emirates 두바이      아랍 에미리트  ドバイ        アラブ首長国連邦


작업이 끝난 4개의 데이터를 합쳐 'write.csv'를 이용해 파일로 저장해두었습니다.  다음 단계에서는 위 자료의 영문 도시명을 이용하여 위도, 경도를 얻어오겠습니다.



# 2-1. 위도, 경도 얻어올 도시명 불러오기


city <- country_city %>% select(city)

city <- unique(city)                                            # 16225 obs.

country <- unique(country_city$country) %>% as.data.frame()   # 237 obs.


'ggmap'으로 위도, 경도를 요청하는 쿼리는 개수 제한이 있습니다. (free 계정은 2,500개, API키를 입력하면 그 이상입니다.)  중복 요청을 피하기 위해 도시과 국가명을 분리, 중복되는 값들은 'rJava'패키지에 있는 'unique'함수로 제거해주었습니다. 요청은 물론 차집합을 구하는 'setdiff'나 조인함수를 사용할 때 중복되는 데이터가 있으면 개수가 안맞게 될 수 있으므로, 미리 중복제거(De-duplication)를 하는 것을 추천드립니다. 



# 2-2. 위도, 경도 얻어오기

# Trial 1

gcity01 <- head(city, 2500)

gcode01 <- geocode(gcity01$city, source="google")

table(is.na(gcode01))


> gcity01 <- head(city, 2500)

> gcode01 <- geocode(gcity01$city, source="google")


> table(is.na(gcode01))


FALSE  TRUE

4078   822


'geocode' 1번째 시도라는 의미에서 [gcity01]라는 이름을 지어 "head"를 이용해 2,500개의 도시명을 넣어주었습니다. 이를 이용해 'ggmap' 패키지의 'geocode'를 실행했고 lat, log 각각 2,500개씩 총 5,000개의 값을 요청했습니다. 'is.na'로 확인해본 결과 1,644개의 값을 정상적으로 받았다는 것을 알 수 있었습니다. (한국어와 일본어 도시명은 각각 해당 영문 도시명과 같다고 연결해 줄 예정이므로 영문 국가명을 이용해 위, 경도를 요청했습니다.)


* 참고: 에러 발생 시
gcode01 <- geocode(gcity01$city, source="google")

>  gcode01 <- geocode(gcity01$city, source="google")
query max exceeded, see ?geocode.  current total = 2500

위와 같은 에러 문이 뜨는 경우 문장을 그대로 해석해보아도 알 수 있지만, 하루 쿼리 요청 제한을 초과한 것입니다. 저는 API key를 사용하지 않았기 때문에 요청 쿼리가 2,500개를 넘어가면 위의 메세지가 떴습니다. Stack of flow에 다양한 방법이 올라와 있지만 저는 그냥 하루에 개수를 맞춰 요청하는 방법을 사용하고 있습니다.


rcity01 <- gcity01 %>%

           mutate(lon=gcode01$lon, lat=gcode01$lat) %>%

           filter(!is.na(lat) & !is.na(lon))

table(is.na(rcity01))


> table(is.na(rcity01))


FALSE

6117


위, 경도를 받아오는 데 실패하여 NA값이 저장된 행을 제거해주기 위하여 'filter'함수를 사용해 1번째 시도 결과(result)로 저장해주었습니다. 'is.na'로 조회해본 결과 NA가 아닌 값만 잘 남아있는 것을 확인할 수 있었습니다. 실제로 출력하면 아래와 같이 도시와 해당 위, 경도를 확인할 수 있습니다.


head(rcity01)


> head(rcity01, 10)

             city lon     lat

1 les Escaldes 1.538786 42.51008 2 Andorra la Vella 1.521835 42.50632 3 Umm al Qaywayn 55.713391 25.52048 4 Ras al-Khaimah 55.980417 25.67413 5 Dubai 55.270783 25.20485 6 Dibba Al-Fujairah 56.259403 25.59112 7 Dibba Al-Hisn 56.273375 25.61819 8 Sharjah 55.420932 25.34626 9 Al Fujayrah 56.248228 25.41108 10 Al Ain 55.802312 24.13016


제대로 값이 들어온 것을 확인한 후! 일일 쿼리가 제한되어있는 만큼! 정상적으로 얻어진 데이터는 아래와 같이 바로바로 파일로 저장해두었습니다.

rcity <- rcity01
write.csv(rcity, file='rcity.csv')

> rcity <- rcity01
> write.csv(rcity, file='rcity.csv')

원본 데이터에서 [rcity01] 데이터에 담긴, 즉 위, 경도가 제대로 얻어진 도시만 제거해서 두 번째 시도 시 사용할 데이터를 만들겠습니다.


city_t1 <- rcity01 %>% select(city)

city01 <- setdiff(city, city_t1)

str(city01)


> city01 <- setdiff(city, city_t1)

> str(city01)

'data.frame': 14196 obs. of  1 variable:

 $ city: chr  "Fier-Çifçi" "Luau" "Mercedes" "Concepción del Uruguay" ...


경도 얻기에 성공한 데이터의 도시명만 [city_t1]에 담아 'dplyr' 패키지에서 제공하는 'setdiff' 함수를 이용하여 모든 도시명이 담긴 [city] 데이터에서 [city_t1]을 제거했습니다. 한마디로 차집합을 구한 것입니다. 결과 16,225개에서 14,196개로 작업 대상이 줄어들었음을 알 수 있었습니다. 


# Trial 2


1번 시도에서 했던 과정과 동일하게 진행하였습니다. 첫 번째 시도에 만들어진 차 집합을 자료를 이용하는 부분이 있으니, 두 번째 시도 까지는 기록하고 이후는 생략하겠습니다.


gcity02 <- head(city01, 2500)

gcode02 <- geocode(gcity02$city, source="google")

table(is.na(gcode02))


> gcity02 <- head(city01, 2500)

> gcode02 <- geocode(gcity02$city, source="google")


> table(is.na(gcode02))


FALSE  TRUE

 786  4214


rcity02 <- gcity02 %>%

           mutate(lon=gcode02$lon, lat=gcode02$lat) %>%

           filter(!is.na(lat) & !is.na(lon))

table(is.na(rcity02))


> table(is.na(rcity02))


FALSE

1179


head(rcity02)


> head(rcity02, 10)

city lon lat

1 Lintong 109.2142 34.36727 2 Linping 120.3064 30.42061 3 Linhai 121.1312 28.84544 4 Linfen 111.5190 36.08801 5 Xishan 102.6644 25.03830 6 Liaocheng 115.9855 36.45703 7 Lianran 102.4710 24.91699 8 Wuwei 102.6382 37.92827 9 Liangxiang 116.1411 39.75284 10 Leshan 103.7657 29.55212


rcity <- rbind(rcity01, rcity02)

write.csv(rcity, file='rcity.csv')


> rcity <- rbind(rcity01, rcity02)

> write.csv(rcity, file='rcity.csv')


두 번째 시도 후, 처음 얻었던 자료와 합쳐 저장하기 위해 'rbind'를 사용하여 두 데이터 프레임을 하나의 데이터 프레임으로 만들었습니다.


city_t2 <- rcity02 %>% select(city)

city02 <- setdiff(city01, city_t2)

str(city02)


> str(city02)

'data.frame': 13792 obs. of  1 variable:

 $ city: chr  "Dubai" "Dibba Al-Hisn" "Al Fujayrah" "Abu Dhabi" ...




# 2-3. 데이터 최종 확인 및 정리


result01 <- inner_join(country_city, rcity, by="city")

result02 <- result01 %>% select(city, lat, lon, country, city_ko, country_ko, city_ja, country_ja)

write.csv(result02, "geocode_city.csv")

head(result02)


> result01 <- inner_join(country_city, rcity, by="city")

> result02 <- result01 %>% select(city, lat, lon, country, city_ko, country_ko, city_ja, country_ja)

> write.csv(result02, "geocode_city.csv")

> head(result02)

          city lat     lon country              city_ko country_ko city_ja        country_ja

1 les Escaldes     42.51008 1.538786 Andorra            레에스칼데스 안도라 <NA>          アンドラ

2 Andorra la Vella 42.50632 1.521835 Andorra             안도라라벨라 안도라 アンドララベリャ アンドラ

3 Umm al Qaywayn    25.52048 55.713391 United Arab Emirates 음알케이웨이 아랍 에미리트 <NA>           アラブ首長国連邦

4 Ras al-Khaimah 25.67413 55.980417 United Arab Emirates 라스알카이마 아랍 에미리트 ラスアルカイマ アラブ首長国連邦

5 Dubai             25.20485 55.270783 United Arab Emirates 두바이        아랍 에미리트 ドバイ          アラブ首長国連邦

6 Dibba Al-Fujairah 25.59112 56.259403 United Arab Emirates 디바알푸자이라 아랍 에미리트  <NA>          アラブ首長国連邦


도시, 국가명이 있던 원본 파일에 위, 경도를 'inner_join'으로 연결하여 최종 파일을 만들었습니다. 


원래는 국제 공항 이름을 리스트업하여 이전 단계에서 진행한 것 처럼 위도, 경도를 얻어오려 했으나, ICAO(국제 민간 항공 기구)에서 위도, 경도가 포함된 리스트를 제공하고있었습니다. 이에 사용 중인 도시명 데이터의 이름과 비교하여 수정 작업만 조금 거쳐 그대로 사용하였습니다. 데이터 위치와 다운로드 방법은 아래를 참고해주세요.



※ ICAO(국제 민간 항공 기구) (바로가기)



ICAO 홈페이지 메인입니다. 여기서 아래 사진의 노란 버튼(Information Resources > ICAO iSTARS)을 클릭해주세요.




왼쪽에서 ICAO가 유, 무료로 제공하는 다양한 데이터를 확인할 수 있는 API Data Service로 들어갑니다.




데이터를 얻어오기위해 사용자 등록을 하고 API Key를 받아옵니다. 발급 과정은 어렵지 않으니 생략하겠습니다. 등록하실 때 입력하는 메일로 데이터를 받아볼 수 있는 점 참고해주세요.





전세계 모든 공항 리스트의 경우 유료이지만 국제 공항에 한정된 데이터는 유료입니다. Full Download 버튼을 클릭하여 원하는 자료형(CSV/JSON)을 선택해주세요. 수 초 안에 API Key 발급 시 등록한 메일로 아래와 같은 메일을 받을 수 있는데 링크를 클릭하면 바로 다운로드가 시작됩니다.


+ Recent posts