EDA 과제1 | 스타벅스와 이디야 매장 위치 비교하기

소리·2023년 11월 17일
0
🔎LEARNING POINT🔎
- HTML을 읽고 원하는 정보가 있는 위치 태그를 파악
- BeautifulSoup와 Selenium을 활용한 웹크롤링 방법
- googlemap을 활용하여 지도를 불러와 위치를 표시하는 방법
- 자료로 그래프를 그려 의견 제시

0. 세팅

#필요한 모듈 임포트 및 설치
import pandas as pd
import numpy as np
import time
from bs4 import BeautifulSoup

!pip install selenium
from selenium import webdriver
from selenium.webdriver.common.by import By

%config Completer.use_jedi = False

1) 서울시 내 스타벅스 매장의 이름과 주소, 구 이름을 pandas dataframe으로 정리

[ IN ]

driver = webdriver.Chrome() #셀레니움이 업데이트 되어 크롭 드라이버를 다운받지 않아도 됨
driver.maximize_window()
driver.get('https://www.starbucks.co.kr/store/store_map.do?disp=locale')

time.sleep(2)

#클릭
sido_cd_btn = driver.find_element(By.CSS_SELECTOR, '#container > div > form > fieldset > div > section > article.find_store_cont > article > article:nth-child(4) > div.loca_step1 > div.loca_step1_cont > ul > li:nth-child(1) > a')
sido_cd_btn.click()

time.sleep(2)

gugun_cd_btn = driver.find_element(By.XPATH, '//*[@id="mCSB_2_container"]/ul/li[1]/a' )
gugun_cd_btn.click()

time.sleep(2)

html = driver.page_source
soup = BeautifulSoup(html, "html.parser")

locations = soup.select('#mCSB_3_container > ul > li')

#웹 크롤링 데이터 적재
names = []
address = []
gu_add = []
data_lat = []
data_long=[]

for i in range(len(locations)):
    name = locations[i].select_one("strong").text
    names.append(name)
    
    addr = locations[i].select_one("p").text
    address.append(addr)
    
    gu = locations[i].select_one("p").text.split(" ")[1]
    gu_add.append(gu)
    
    lat = locations[i]['data-lat']
    data_lat.append(lat)
    
    long = locations[i]['data-long']
    data_long.append(long)

time.sleep(3)

driver.quit()

#pandas dataframe으로 스타벅스 매장 정보 가져오기
starbucks = pd.DataFrame({'스타벅스 매장 이름' : names,
                  '스타벅스 매장 주소' : address,
                  '구 이름' : gu_add})

print(starbucks)

time.sleep을 중간중간에 넣지 않으면, 프로그램마다 속도가 달라서 에러가 뜬다. 코드에서 이상을 못 찾았는데, 자꾸 에러를 떠서 고생하다 time 넣고 다 해결..
생각했던 것보다 자주 넣어야했다. (물론 위 식은 내가 혹시나 해서 과하게 넣은 거일 수도 ㅎㅎ)

[ OUT ]

    스타벅스 매장 이름                                      스타벅스 매장 주소 구 이름
0    역삼아레나빌딩                  서울특별시 강남구 언주로 425 (역삼동)1522-3232  강남구
1     논현역사거리                 서울특별시 강남구 강남대로 538 (논현동)1522-3232  강남구
2    신사역성일빌딩                 서울특별시 강남구 강남대로 584 (논현동)1522-3232  강남구
3     국기원사거리                 서울특별시 강남구 테헤란로 125 (역삼동)1522-3232  강남구
4    대치재경빌딩R               서울특별시 강남구 남부순환로 2947 (대치동)1522-3232  강남구
..         ...                                             ...  ...
601     사가정역                        서울특별시 중랑구 면목로 3101522-3232  중랑구
602      상봉역                  서울특별시 중랑구 망우로 307 (상봉동)1522-3232  중랑구
603       묵동    서울특별시 중랑구 동일로 952 (묵동, 로프트원 태릉입구역) 1층1522-3232  중랑구
604      양원역                서울특별시 중랑구 양원역로10길 3 (망우동)1522-3232  중랑구
605      중화역                       서울특별시 중랑구 봉화산로 35 1522-3232  중랑구

[606 rows x 3 columns]

2) 서울시 내 이디야 매장의 이름과 주소, 구 이름을 pandas dataframe으로 정리

이디야는 직접 이름을 입력해야 하므로 스타벅스 구 이름을 가져와 사용한다. 이때 중구를 입력할 경우 결과 값이 많아 에러창이 뜨므로, 서울 중구로 검색한다.

[ IN ]

gu_list = list(starbucks['구 이름'].unique())

gu_list.remove('중구')
gu_list.append('서울 중구') #중구를 서울 중구로 변경
gu_list

[ OUT ]

['강남구',
 '강북구',
 '강서구',
 '관악구',
 '광진구',
 '금천구',
 '노원구',
 '도봉구',
 '동작구',
 '마포구',
 '서대문구',
 '서초구',
 '성북구',
 '송파구',
 '양천구',
 '영등포구',
 '은평구',
 '종로구',
 '강동구',
 '구로구',
 '동대문구',
 '성동구',
 '용산구',
 '중랑구',
 '서울 중구']

NEXT STEP 웹 크롤링으로 이디야 정보 가져오기

[ IN ]

#접속
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://www.ediya.com/contents/find_store.html")
time.sleep(2)

driver.find_element(By.CSS_SELECTOR, "#contentWrap > div.contents > div > div.store_search_pop > ul > li:nth-child(2) > a").click()

names2 = []
address2 = []
gu_add2 = []
data_lat2 = []
data_long2= []
    

for i in range(len(gu_list)):
    
    #구 이름 집어넣고 클릭하는 동작
    kw = driver.find_element(By.CSS_SELECTOR, "#keyword")
    kw.clear()
    kw.send_keys(gu_list[i])
    btn = driver.find_element(By.CSS_SELECTOR, "#keyword_div > form > button")
    btn.click()
    
    time.sleep(2)

    html = driver.page_source
    soup = BeautifulSoup(html, "html.parser")

    locations2 = soup.select('#placesList > li > a')
    time.sleep(2)
	
    #주유소 하나씩 정보 스크랩
    for i in range(len(locations2)):
               
        name2 = locations2[i].select_one("dt").text
        names2.append(name2)

        add2 = locations2[i].select_one("dd").text
        address2.append(add2)

        gu = locations2[i].select_one("dd").text.split(" ")[1]
        gu_add2.append(gu)       

        lat = locations2[i]['onclick'].split('\'')[3]
        data_lat2.append(lat)
    
        long = locations2[i]['onclick'].split('\'')[1]
        data_long2.append(long)
    

driver.quit()


#pandas dataframe으로 이디야 매장 정보 가져오기
ediya = pd.DataFrame({'이디야 매장 이름' : names2,
                  '이디야 매장 주소' : address2,
                  '구 이름' : gu_add2})

print(ediya)

[ OUT ]

       이디야 매장 이름                       이디야 매장 주소 구 이름
0        강남YMCA점                      서울 강남구 논현동  강남구
1    강남구청역아이티웨딩점     서울 강남구 학동로 338 (논현동, 강남파라곤)  강남구
2        강남논현학동점         서울 강남구 논현로131길 28 (논현동)  강남구
3          강남대치점      서울 강남구 역삼로 415 (대치동, 성진빌딩)  강남구
4          강남도산점         서울 강남구 도산대로37길 20 (신사동)  강남구
..           ...                             ...  ...
631         태평로점         서울 중구 세종대로11길 26 (서소문동)   중구
632       퇴계로2길점             서울 중구 퇴계로2길 1 (남창동)   중구
633        한국은행점        서울 중구 남대문로 29-2 (남대문로3가)   중구
634      황학롯데캐슬점  서울 중구 청계천로 400 (황학동, 롯데캐슬베네치아)   중구
635     회현SK리더스점    서울 중구 퇴계로 72 (회현동1가, 리더스뷰남산)   중구

[636 rows x 3 columns]

❗ 스타벅스와 다르게 반복문을 중첩해서 활용해야하며, 그 과정에서 처음에 크롤링할 정보의 위치 정보를 정확하게 찾는 것에 어려움을 느꼈다. 특히 li:nth-child(2) 이런 부분 2를 i로 치환해서 for문을 돌리는 헛발질을 많이 했다.
❗클릭 등 동적인 경우는 셀레니움을 사용하고 정보를 크롤링할 때는 beautifulsoup을 사용하는 게 정석인가보다. 이 두 개를 초반에 비슷하게 여겨서 select_one을 쓸 지, find_element를 쓸 지 시행착오가 많았다.

3) 문제 1,2 번의 결과를 가지고 이디야 커피는 스타벅스 커피 매장 근처에 있는지를 분석, 지도의 위치를 표시하여 시각적으로 확인하기 위한 작업을 하였다.

[ IN ]

! pip install googlemaps

import folium
import json
import googlemaps

gmaps_key = 'AIzaSyCiHXsM5pgR02fjYgLbDPhqE3Je4p1PBQg '
gmaps = googlemaps.Client(key=gmaps_key)

m = folium.Map(location = [37.56653641975829, 126.98185562167849], zoom_start = 12)

#크롤링한 스타벅스 매장의 위도 경도를 데이터 프레임에 입력
starbucks['lat'] = data_lat
starbucks['long'] = data_long
print(starbucks)
print(starbucks.isnull().sum())

[ OUT ]

    스타벅스 매장 이름                                      스타벅스 매장 주소 구 이름  \
0    역삼아레나빌딩                  서울특별시 강남구 언주로 425 (역삼동)1522-3232  강남구   
1     논현역사거리                 서울특별시 강남구 강남대로 538 (논현동)1522-3232  강남구   
2    신사역성일빌딩                 서울특별시 강남구 강남대로 584 (논현동)1522-3232  강남구   
3     국기원사거리                 서울특별시 강남구 테헤란로 125 (역삼동)1522-3232  강남구   
4    대치재경빌딩R               서울특별시 강남구 남부순환로 2947 (대치동)1522-3232  강남구   
..         ...                                             ...  ...   
601     사가정역                        서울특별시 중랑구 면목로 3101522-3232  중랑구   
602      상봉역                  서울특별시 중랑구 망우로 307 (상봉동)1522-3232  중랑구   
603       묵동    서울특별시 중랑구 동일로 952 (묵동, 로프트원 태릉입구역) 1층1522-3232  중랑구   
604      양원역                서울특별시 중랑구 양원역로10길 3 (망우동)1522-3232  중랑구   
605      중화역                       서울특별시 중랑구 봉화산로 35 1522-3232  중랑구   

                   lat                long  
0            37.501087          127.043069  
1            37.510178          127.022223  
2           37.5139309         127.0206057  
3            37.499517          127.031495  
4            37.494668          127.062583  
..                 ...                 ...  
601          37.579594          127.087966  
602           37.59689           127.08647  
603          37.615368          127.076633  
604   37.6066536267232    127.106359790053  
605  37.60170912407773  127.07841136432036  

[606 rows x 5 columns]
스타벅스 매장 이름    0
스타벅스 매장 주소    0
구 이름          0
lat           0
long          0
dtype: int64

[ IN ]

#크롤링한 이디야 매장의 위도 경고를 데이터 프레임에 입력
ediya['lat'] = np.nan
ediya['long'] = np.nan


for idx, rows in enumerate(ediya['이디야 매장 주소']):
    tmp = gmaps.geocode(rows, language='ko')
    
    if tmp:
        lat = tmp[0].get('geometry')['location']['lat']
        long = tmp[0].get('geometry')['location']['lng']
        ediya.loc[idx, 'lat'] = lat
        ediya.loc[idx, 'long'] = long
        
    else:
        print(idx, rows)
        
        
ediya

[ OUT ]

94 서울 관악구 남부순환로 1369 (신림동, 관악농협농산물백화점)
243 서울 마포구 신촌로 66 (노고산동, 농협중앙회)
254 서울 서대문구 수색로 100 (북가좌동, DMC래미안e편한세상)
277 서울 서초구 서초대로 108 (방배동, 삼보빌딩)
326 서울 송파구 충민로 66 (문정동, 가든파이브라이프)
358 서울 송파구 송파대로 567 (잠실동, 잠실주공아파트)
393 서울 영등포구 63로 40 (여의도동, 라이프오피스텔)

이디야 매장 이름	이디야 매장 주소	구 이름	lat	long
0	강남YMCA점	서울 강남구 논현동	강남구	37.513679	127.031712
1	강남구청역아이티웨딩점	서울 강남구 학동로 338 (논현동, 강남파라곤)	강남구	37.516551	127.040139
2	강남논현학동점	서울 강남구 논현로131길 28 (논현동)	강남구	37.515190	127.027554
3	강남대치점	서울 강남구 역삼로 415 (대치동, 성진빌딩)	강남구	37.501434	127.052328
4	강남도산점	서울 강남구 도산대로37길 20 (신사동)	강남구	37.522282	127.031480
...	...	...	...	...	...
631	태평로점	서울 중구 세종대로11길 26 (서소문동)	중구	37.563828	126.975206
632	퇴계로2길점	서울 중구 퇴계로2길 1 (남창동)	중구	37.560929	126.978763
633	한국은행점	서울 중구 남대문로 29-2 (남대문로3가)	중구	37.561443	126.978964
634	황학롯데캐슬점	서울 중구 청계천로 400 (황학동, 롯데캐슬베네치아)	중구	37.571219	127.021300
635	회현SK리더스점	서울 중구 퇴계로 72 (회현동1가, 리더스뷰남산)	중구	37.559230	126.980664
636 rows × 5 columns

googlemap으로 가져오지 못한 정보가 있어서 하드코딩 진행

#결측치 확인
ediya[ediya.isna().any(axis=1)]

#결측치 보완
ediya.iloc[94, -2] = 37.48426
ediya.iloc[94, -1] = 126.93070

ediya.iloc[243, -2] = 37.55692
ediya.iloc[243, -1] = 37.55692

ediya.iloc[254, -2] = 37.57519
ediya.iloc[254, -1] = 126.90643

ediya.iloc[277, -2] = 37.49798
ediya.iloc[277, -1] = 127.02747

ediya.iloc[326, -2] = 37.47814
ediya.iloc[326, -1] = 127.12569

ediya.iloc[358, -2] = 37.51619
ediya.iloc[358, -1] = 127.09309

ediya.iloc[393, -2] = 37.51920
ediya.iloc[393, -1] = 126.93743

내 하드코딩 방법은 크롤링을 새로할 때마다 조금씩 바뀌는 이슈 있음

이후 맵 위에 위치를 찍는 방식으로 시각화를 진행했다.

#지도 시각화 

mapPoint = [37.517692, 126.989912]
comMap = folium.Map(location = mapPoint, zoom_start = 11)


#스타벅스 매장 위치 찍어보기
for idx, rows in starbucks.iterrows():
    folium.CircleMarker(
        location = [rows['lat'], rows['long']],
        radius=4,
        fill=True,
        color = 'green',
        fill_opacity = 0.5,
        tooltip = 'starbucks'
        
    ).add_to(comMap)


comMap

#이디야 매장도 찍어보자
for idx, rows in ediya.iterrows():
    folium.CircleMarker(
        location = [rows['lat'], rows['long']],
        radius=4,
        fill=True,
        color = 'blue',
        fill_opacity = 0.5,
        tooltip = 'ediya'
        
    ).add_to(comMap)

comMap

명확히 알아보기 어려워 그래프를 통해 정량적으로 확인 시도

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc

f_path = 'C:\Windows\Fonts\malgun.ttf'
font_manager.FontProperties(fname=f_path).get_name()
rc('font', family='Malgun Gothic')

#그래프를 만들어서 구마다 각 매장의 차이가 있는지 파악
plt.figure(figsize = (14,10))
starCnt = starbucks['구 이름'].value_counts()
ediyaCnt = ediya['구 이름'].value_counts()

plt.bar(starCnt.index, starCnt, color = 'green', label = '스타벅스', alpha = 0.5)
plt.bar(ediyaCnt.index, ediyaCnt, color = 'darkblue', label = '이디야', alpha = 0.5)

for i in range(len(starCnt)):
    plt.text(starCnt.index[i], starCnt.values[i], str(starCnt.values[i]), ha='center', va='bottom')

for i in range(len(ediyaCnt)):
    plt.text(ediyaCnt.index[i], ediyaCnt.values[i], str(ediyaCnt.values[i]), ha='center', va='top')

plt.title('구 별 매장 갯수')
plt.xticks(rotation = 45)
plt.xlabel('구 이름')
plt.ylabel('매장 수')
plt.legend()
plt.show()

도에서 스타벅스는 밀집된 지역이 많으나, 이디야는 고루 퍼져있는 형상을 보였다. 좀더 정확한 파악을 위해 지역 별 매장의 갯수를 표시하는 막대그래프를 위와 같이 그렸다.

초록색은 스타벅스, 보라색은 이디야 매장 수를 나타낸다. 해당 지역에서 매장 수가 더 많은 브랜드의 색깔이 그래프 상단에 보인다.

그래프를 살펴보면, [스타벅스]는 강남구, 중구, 서초구 등 특정 지역에 압도적으로 많은 수의 매장을 보유하고 있으며, 중랑구, 강북구, 도봉구에서는 6-8개의 적은 매장이 위치해 있다. 이는 가장 많은 매장을 보유한 강남구와 10배 이상의 차이가 난다.

그에 반하여 [이디야]는 지역별로 20개 지점 정도가 균일하게 분포되어있는 것을 확인하였다.

[결론]

'이디야 커피는 스타벅스 커피 매장 근처에 있다' 가설은 '참이 아님'을 데이터로 확인하였다. 만일 이디야가 스타벅스 매장 근처에 지점을 오픈하는 비즈니스 전략을 가지고 있었다면, 스타벅스 매장 수가 월등히 높은 강남구, 중구에서 이디야 매장 수 또한 이디야 지점 평균과 비교해 유의미한 차이를 보여야 하나, 그러지 않았다. 또한 스타벅스 매장이 10개 이하로 현저히 낮은 지점을 보유하고 있는 구에서 이디야 커피 매장의 수가 오히려 높은 경우도 찾아볼 수 있었다.(중랑구)

profile
데이터로 경로를 탐색합니다.

0개의 댓글