📌 최초 논제
📌 스타벅스 매장 위치 크롤링


: 스타벅스 홈페이지에서 매장 찾기 -> 지역 검색 -> 서울시로 스타벅스 매장 위치 크롤링
import re
import time
import pandas as pd
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
def fetch_starbucks():
starbucks_url = "https://www.starbucks.co.kr/index.do"
driver = webdriver.Chrome()
driver.maximize_window() # 화면 최대화
driver.get(starbucks_url)
time.sleep(1)
# 여러 액션을 연결하여 사용
action = ActionChains(driver)
first_tag = driver.find_element(By.CSS_SELECTOR, "#gnb > div > nav > div > ul > li.gnb_nav03 > h2 > a")
second_tag = driver.find_element(By.CSS_SELECTOR, "#gnb > div > nav > div > ul > li.gnb_nav03 > div > div > div > ul:nth-child(1) > li:nth-child(3) > a")
action.move_to_element(first_tag).move_to_element(second_tag).click().perform()
seoul_tag = WebDriverWait(driver,10).until(
EC.element_to_be_clickable(
(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")
)
)
seoul_tag.click()
WebDriverWait(driver,5).until(EC.presence_of_all_elements_located((By.CLASS_NAME,"set_gugun_cd_btn")))
gu_elements = driver.find_elements(By.CLASS_NAME, "set_gugun_cd_btn")
WebDriverWait(driver, 5).until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "#mCSB_2_container > ul > li:nth-child(1) > a")
)
)
gu_elements[0].click()
WebDriverWait(driver, 5).until(EC.presence_of_all_elements_located((By.CLASS_NAME,"quickResultLstCon")))
req = driver.page_source
soup = BeautifulSoup(req, "html.parser")
stores = soup.find("ul","quickSearchResultBoxSidoGugun").find_all("li")
store_list = []
addr_list = []
lat_list = []
lng_list = []
for store in stores:
store_name = store.find("strong").text
store_addr = store.find("p").text
# 정규 표현식 개념
store_addr = re.sub(r"\d{4}~\d{4}$", "", store_addr).strip()
# (숫자면서 4자리 - 숫자면서 4자리)을 제거한 뒤 앞뒤 공백 지우기
store_lat = store["data-lat"]
store_lng = store["data-long"]
store_list.append(store_name)
addr_list.append(store_addr)
lat_list.append(store_lat)
lng_list.append(store_lng)
df = pd.DataFrame({
"store" : store_list,
"addr" : addr_list,
"lat" : lat_list,
"lng" : lng_list
})
time.sleep(3)
driver.quit()
return df
starbucks_df = fetch_starbucks()
starbucks_df.to_csv("starbucks_seoul.csv", index=False, encoding="utf-8-sig")
print("데이터가 starbucks_seoul.csv 파일로 저장되었습니다.")
📌 데이터 소스
: 공공데이터포털에서 서울에 있는 소상공인 시장진흥공단 상가(상권) 정보 데이터를 받아왔음
📌 데이터 전처리
import pandas as pd
df_starbucks = pd.read_csv("/content/drive/MyDrive/컴퓨터비전/3. 데이터 분석/data/starbucks_seoul.csv")
df_starbucks = df_starbucks.set_axis(["지점명", "지점주소", "지점위도", "지점경도"], axis=1).reset_index(drop=True)
df = pd.read_csv("/content/drive/MyDrive/컴퓨터비전/3. 데이터 분석/data/소상공인시장진흥공단_상가(상권)정보_서울_202503.csv", low_memory=False)
shop = ["이디야", "이디아", '이디야커피', "이디아커피", "올리브영", "컴포즈커피", "빽다방", "백다방", "메가커피", "메가엠지씨", "메가MGC"]
df_shop = df.copy()
# 이디야|이디아|올리브영|컴포즈커피|빽다방|메가커피|메가엠지씨|메가MGC
# extract(): 특정 문자열을 포함하고 있으면 그 문자열을 반환하고, 포함하지 않으면 NaN을 반환
df_shop["상호명"] = df_shop["상호명"].str.extract("({})".format("|".join(shop)))
df_shop = df_shop.dropna(subset=["상호명"]).iloc[:, [0,1,14,37,38]].reset_index(drop=True)
df_shop["상호명"] = df_shop["상호명"].str.replace("메가엠지씨", "메가커피", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("메가MGC", "메가커피", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디아", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디야커피", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디아커피", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("백다방", "빽다방", regex=False)
print(df_shop.shape)
print(df_starbucks.shape)
df_cross = df_shop.merge(df_starbucks, how="cross")
📌 하버사인 공식
Haversine 공식은 구체(구면) 위의 두 점(위도와 경도로 표시됨) 사이의 최단 거리(대원 거리, great-circle distance)를 계산하는 방식
![]()
pip install haversine
from haversine import haversine
df_cross["거리"] = df_cross.apply(lambda x : haversine([x["위도"], x["경도"]], [x["지점위도"], x["지점경도"]], unit="m"), axis=1)
df_dis= df_cross.groupby(["상가업소번호","상호명"])["거리"].min().reset_index()
df_dis.groupby("상호명")["거리"].mean()
# agg() : 다중 집계작업을 간단하게 해주는 함수
df_dis.groupby(["상호명"])["거리"].agg(["mean","count"])
def distance(x):
dis = df_dis['거리'] <= x
return df_dis[dis].groupby(['상호명'])['거리'].agg(['mean', 'count'])
distance(100)
📌 차트 시각화
pip install pandasecharts
df_100 = distance(100).reset_index()
import IPython
from pandasecharts import echart
df_100.echart.pie(x='상호명', y='count', figsize=(600, 400),
radius=['20%', '60%'], label_opts={'position':'outer'},
title='커피 프렌차이즈의 입점전략은 과연 스타벅스 옆인가?',
legend_opts={'pos_right':'0%', 'orient':'vertical'},
subtitle='100m 이내 매장수', init_opts={'bg_color': 'white'}).render()
IPython.display.HTML(filename='render.html')

from pyecharts.charts import Timeline, Grid
tl = Timeline({'width':'600px', 'height':'400px'})
for i in [1000, 100, 50, 30]:
df_d = distance(i).reset_index()
pie1 = df_d.echart.pie(x='상호명', y='count', figsize=(600, 400),
radius=['20%', '60%'], label_opts={'position':'outer'},
title='커피 프렌차이즈의 입점전략은 과연 스타벅스 옆인가?',
legend_opts={'pos_right':'0%', 'orient':'vertical'},
subtitle='{}m 이내 매장수'.format(i), init_opts={'bg_color': 'white'})
tl.add(pie1, '{}m'.format(i)).render()
IPython.display.HTML(filename='render.html')

