EDA Project2 [셀프주유소 /일반주유소 가격분석]

솔비·2024년 1월 18일
1
post-thumbnail
post-custom-banner

🌟 목표
셀프주유소와 일반주유소의 가격을 비교해보고,
셀프주유소가 저렴한지 확인해보자



1. 주유사이트 웹크롤링을 통한 정보가져오기


📌 미션
사이트 데이터를 불러와 서울시 구별 데이터 정보를 pandas data frame으로 정리하자

  • 필요컬럼 | 주유소명, 주소, 브랜드, 휘발유 가격, 경유 가격, 셀프 여부, 세차장 여부, 충전소 여부, 경정비 여부, 편의점 여부, 24시간 운영 여부, 구, 위도, 경도
  • 사이트링크 | https://www.opinet.co.kr/user/main/mainView.do



  • 사이트접근
from selenium import webdriver
from selenium.webdriver.common.by import By

url = "https://www.opinet.co.kr/user/main/mainView.do"
driver = webdriver.Chrome("C:/Users/solbi/OneDrive/Documents/ds_study/driver/chromedriver.exe")
driver.get(url)

  • 싼 주유소 찾기 마우스이동
from selenium.webdriver import ActionChains
move_tag = driver.find_element(By.CSS_SELECTOR,"#header > div > ul > li:nth-child(1) > a")
ActionChains(driver).move_to_element(move_tag).perform()

동적 페이지이기 때문에 해당 버튼에 마우스를 올려야 숨은 코드가 보였다.
그래서 ActionChains을 통해 해결 !


  • 지역별 클릭
driver.find_element(By.CSS_SELECTOR,"#header > div > ul > li:nth-child(1) > ul > li:nth-child(1) > a").click()

  • 세차장, 경정비, 편의점, 24시간 체크박스 클릭
# 세차장클릭
driver.find_element(By.CSS_SELECTOR,"#CWSH_YN").click()

# 경정비클릭
driver.find_element(By.CSS_SELECTOR,"#MAINT_YN").click()

# 편의점클릭
driver.find_element(By.CSS_SELECTOR,"#CVS_YN").click()

# 24시간클릭
driver.find_element(By.CSS_SELECTOR,"#SEL24_YN").click()


  • 시/도 내 서울 입력
# 시/도 접근
sido_raw = driver.find_element(By.CSS_SELECTOR,"#SIDO_NM0")
sido = sido_raw.find_elements(By.TAG_NAME,"option")

sido[0].text # 시/도
sido = sido[1:]
sido[0].text # 서울

#서울입력
sido_raw.send_keys(sido[0].text)

  • 구 리스트 확보
gu_raw = driver.find_element(By.CSS_SELECTOR,"#SIGUNGU_NM0")
gu = gu_raw.find_elements(By.TAG_NAME,"option")

gu[0].text # 시/군/구
gu = gu[1:]
gu[0].text	# 강남구

gu_list = []

from tqdm import tqdm_notebook

for option in tqdm_notebook(gu) :
    gu_list.append(option.get_attribute("value"))

gu_list

  • 시/군/구에 구를 차례대로 입력 후 필요한 데이터 수집
data = []
from bs4 import BeautifulSoup
import time

# 구 이름 넣기
for gu in tqdm_notebook(gu_list) :
    gu_raw = driver.find_element(By.CSS_SELECTOR,"#SIGUNGU_NM0")
    gu_raw.send_keys(gu)
    
    html = driver.page_source
    soup = BeautifulSoup(html,"html.parser")
    cnt = int(soup.find(id = "totCnt").text)


    for n in range(1,cnt+1) :
        driver.find_element(By.CSS_SELECTOR,f'#body1 > tr:nth-child({n}) > td.rlist > a').click()
        html = driver.page_source
        soup = BeautifulSoup(html,"html.parser")

        # 1. 주유소 명
        name = soup.find(id = "os_nm").text

        # 2. 주유소 주소
        address = soup.find(id = "rd_addr").text

        # 3. 브랜드
        brand = soup.find(id = "poll_div_nm").text

        # 4. 휘발유가격
        gasoline = soup.find(id = "b027_p").text

        # 5. 경유가격
        diesel = soup.find(id = "d047_p").text

        # 6. 셀프여부
        if soup.find('img', {'alt': '셀프주유소', 'class': 'bul'}):
            self = "Y"
        else:
            self = "N"

        # 7. 세차장여부
        if "off" in soup.find(id = "cwsh_yn").get("src")  :
            wash = "N"
        else:
            wash = "Y"

        # 8. 충전소여부
        if "off" in soup.find(id = "lpg_yn").get("src")  :
            charging = "N"
        else:
            charging = "Y"

        # 9. 경정비여부
        if "off" in soup.find(id = "maint_yn").get("src"):
            center = "N"
        else:
            center = "Y"

        # 10. 편의점여부
        if "off" in soup.find(id = "cvs_yn").get("src")  :
            store = "N"
        else:
            store = "Y"

        # 11. 24시여부
        if "off" in soup.find(id = "sel24_yn").get("src")  :
            night = "N"
        else:
            night = "Y"

        # 12. 구
        gu = gu

        time.sleep(0.2)
        data.append({
                'name' : name,
                'address' : address,
                'brand' : brand,
                'gasoline' : gasoline,
                'diesel' : diesel,
                'self' : self,
                'wash' : wash,
                'charging' : charging,
                'center' : center,
                'store' : store,
                'night' : night,
                'gu' : gu
                })
  1. 페이지에서 각 구별 주유소 수가 상단에 뜨기 때문에
    해당 데이터로 for문을 돌렸다.

    다행히 각 데이터마다 고유의 id를 가지고 있어서 데이터를 크롤링 하는게 어렵지는 않았으나,
    세차장, 충전소, 경정비, 편의점의 경우 데이터 텍스트가 존재하는것이 아닌,
    이미지의 색상으로 여부를 구별해야했고,
    src 이미지 링크 내 없다면 off로 되어있는것을 확인

    in 키워드를 사용해서 여부를 파악할 수 있었다.
    셀프주유소여부의 경우
    해당할 경우에만 상단에 이미지가 있는데,
    alt로 셀프주유소 코드가 존재해서 if문으로 확인하였다.
    (사실 저렇게 if문을 사용해도 되나 했지만, 얻어걸림 😅)

  • DataFrame화
import pandas as pd
oil_df = pd.DataFrame(data)

  • 구글맵 라이브러리로 위도 경도 불러오기
import googlemaps
gmaps_key = "개인구글키"
gmaps = googlemaps.Client(key = gmaps_key)

lat_list = []
lng_list = []

for adr in tqdm_notebook(oil_df["address"]) :
    tmp = gmaps.geocode(adr, language= "ko")

    lat = tmp[0].get("geometry")["location"]["lat"]
    lng = tmp[0].get("geometry")["location"]["lng"]

    lat_list.append(lat)
    lng_list.append(lng)
    
    
oil_df["lat"] = lat_list
oil_df["lng"] = lng_list

- 결과





2. 기름가격이 셀프주유소에서 정말 저렴한지 분석해보자


A. 셀프주유소와 일반 주유소의 평균 기름가격 비교

  • 컬럼 데이터 타입 변경

    object(+ 천단위 ,)이기 떄문에 ,를 지워주고 int로 변경해야한다. | 참고링크

import numpy as np

oil_df["gasoline"] = oil_df["gasoline"].str.replace(",","")
oil_df["gasoline"] = oil_df["gasoline"].astype("int64")

oil_df["diesel"] = oil_df["diesel"].str.replace(",","")
oil_df["diesel"] = oil_df["diesel"].astype("int64")

  • 한글폰트 깨짐 방지
from matplotlib import font_manager as fm
from matplotlib import pyplot as plt

#한글폰트 깨짐 해결
get_ipython().run_line_magic("matplotlib", "inline")
plt.rc('font', family = "Malgun Gothic")

#마이너스부호 깨짐 해결
import matplotlib as mpl
mpl.rcParams['axes.unicode_minus'] = False

  • 피봇으로 가격비교
import numpy as np
self_pivot = oil_df.pivot_table(index=["self"],values=["gasoline","diesel"],aggfunc=np.mean)
self_pivot


  • boxplot 시각화
import seaborn as sns

self_pivot.boxplot(
    figsize=(12,8), column=["gasoline","diesel"], by = "self"
)

A - 인사이트


  • 경유의 경우 셀프주유소에서 평균 1492원 셀프가 아닌 주유소에서 1689원이고,
  • 휘발유의 경우 셀프주유소에서 평균 1566원 셀프가 아닌 주유소에서 1765원이므로
  • 서울 전체 평균을 따져본다면 셀프주유소가 아닌 주유소보다 더 저렴한 것을 알 수 있다.
  • (해당 수치를 boxplot으로 시각화 하였을 때 차이를 한눈에 볼 수 있음)



B. 브랜드별 가격 비교


  • 브랜드별 휘발유 가격 boxplot
plt.figure(figsize=(12,8))
sns.boxplot(
    x= "brand", y = "gasoline", hue = "self", data=oil_df, palette = "Set3"
)
plt.show()


  • 브랜드별 경유가격 boxplot
plt.figure(figsize=(12,8))
sns.boxplot(
    x= "brand", y = "diesel", hue = "self", data=oil_df, palette = "Set3"
)
plt.show()

B - 인사이트


  • 브랜드별 휘발유, 경유 가격을 시각화 하였을 때
  • 모든 브랜드에서 셀프주유소의 가격분포가 더 낮음을 확인 할 수 있다.
  • (추가로 알뜰주유소가 가격이 제일 저렴하다.)



C. 구별 가격 시각화


  • 구별 휘발유 가격 boxplot
plt.figure(figsize=(12,8))
sns.boxplot(
    x= "gu", y = "gasoline", hue = "self", data=oil_df, palette = "Set3"
)
plt.show()


  • 구별 경유 가격 boxplot
plt.figure(figsize=(12,8))
sns.boxplot(
    x= "gu", y = "diesel", hue = "self", data=oil_df, palette = "Set3"
)
plt.show()

C - 인사이트


  • 구별 각각의 기름가격을 셀프주유소와 아닌 주유소로 시각화하였을 때, 대체적으로 휘발유, 경유 모두 셀프주유소가 더 저렴한것을 확인 할 수 있었다.
    ( 추가로 강남구, 중구, 성동구 등에서 가장 큰 차이를 보임)
  • 다만, 양천구, 금천구, 송파구 등의 경우 차가 크지 않고 은평구의 경우 셀프 주유소보다 확연하게 저렴한 주유소도 존재하는 것으로 보아
    절대적으로 셀프 주유소가 저렴하다고는 볼 수없고, 대체적으로 저렴하다라고 볼 수 있다.



3. 결론


📌 인사이트정리

A. 인사이트 |

  • 서울 전체 주유소의 셀프주유소와 아닌 주유소의 평균 가격을 비교해보았을 때, 휘발유/경유 모두 셀프주유소의 가격이 더 저렴한것을 확인함
    B. 인사이트 |
  • 브랜드별 셀프주유소와 아닌 주유소의 가격분포를 시각화해 보았을 때, 모든 브랜드가 셀프주유소의 가격이 더 저렴한것을 확인함
    C. 인사이트 |
  • 구별 셀프주유소와 아닌 주유소의 가격분포를 시각화해 보았을때, 대체적으로 셀프 주유소가 더 저렴하며 그 차가 작은구와 큰 구가 존재하고,
  • 몇몇구에서는 셀프주유소보다 저렴한 주유소가 존재함

📌 최종결론

셀프주유소는 아닌 일반 주유소보다 '대체적'으로 저렴한 편이다.
셀프보다 더 저렴한 일반 주유소도 존재 하지만 그 수가 적고,
데이터를 시각화 해보았을 때 대부분의 셀프주유소 가격이 저렴한 것을 볼 수 있다.


Zero Base 데이터분석 스쿨
Daily Study Note
profile
Study Log
post-custom-banner

3개의 댓글

comment-user-thumbnail
2024년 1월 19일

안녕하세요 솔비님! 코드 중에
driver.find_element(By.CSS_SELECTOR,f'#body1 > tr:nth-child({n}) > td.rlist > a').click()
요 부분 도저히 해결이 되지 않아서 그런데 참고 해서 써도 되나요?🥲

1개의 답글