목표 : 서울시 주유소 중에서 셀프 주유소가 저렴할까? 확인해보자
사이트 살펴보기
메인사이트
https://www.opinet.co.kr/user/main/mainView.do

아래 사이트에서 데이터를 가져온다.
https://www.opinet.co.kr/searRgSelect.do

우리는 위 사진처럼 싼 주유소찾기에서 지역별을 선택해서 나오는 페이지에서 크롤링을 진행한다. 페이지에서 주유소 지역을 변경하더라도 url값은 변화가 없다. 따라서 BeautifulSoup만으로 크롤링하기는 어렵고, Selenium을 이용하여 웹 페이지에 접근할 것이다.
from selenium import webdriver
#페이지 접근
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome()
driver.get(url)
#에러가 발생한다면 Chrome() 괄호안에 경로를 넣어주면 됨,
경로가 들어갔을 때, 에러가 발생해서 뺐음
만약 위 url 주소로 접속이 안되고, 이 사이트의 메인페이지에 접속되고 팝업창이 뜬다면 아래 코드로 실행한다.
import time
#셀레니움을 연달아 실행하면 속도가 늦기 때문에 각 실행코드별로 시간 텀을 둔다
def main_get():
#페이지 접근
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome()
driver.get(url)
time.sleep(3)
# 팝업창으로 전환
driver.switch_to_window(driver.window_handles[-1])
#팝업창 닫아주기
driver.close()
time.sleep(3)
#메인화면 창으로 전환
driver.switch_to_window(driver.window_handles[-1])
#접근 URL 다시 요청
driver.get(url)
main_get()
지역(시/도 - 시/군/구)을 선택하면 자동으로 조회가 되고, 엑셀저장 버튼을 클릭하면 엑셀파일로 저장된다.
셀리니움에서 시/도, 시/군/구를 선택하고, 엑셀저장 버튼을 클릭하는 것까지 진행한다.
# 지역 : 시/도
from selenium.webdriver.common.by import By
sido_list_raw = driver.find_element(By.ID, 'SIDO_NM0')
sido_list = sido_list_raw.find_elements(By.TAG_NAME, 'option')
len(sido_list), sido_list[17].text #잘 나왔는지 확인!
#시/도 이름만으로 구성된 시/도 리스트
#방법1
sido_name = [sido.text for sido in sido_list]
sido_name = sido_name[1:] #첫번째 값이 시/도 이므로 이를 삭제함
#방법2
sido_names = [option.get_attribute("value") for option in sido_list]
sido_names = sido_names[1:]
#시를 서울로 지정
#sido_list_raw.send_keys(sido_name[0])
sido_list_raw.send_keys(sido_names[0])
#구
gu_list_raw = driver.find_element(By.ID, 'SIGUNGU_NM0')
gu_list = gu_list_raw.find_elements(By.TAG_NAME, 'option')
len(gu_list), gu_list[18].text # 잘 나왔는지 확인!
# 시/군/구 이름만으로 구성된 시/군/구 리스트
#방법1
gu_name = [gu.text for gu in gu_list]
gu_name = gu_name[1:] #시/군/구 항목 제거
gu_names[0] #확인
#방법2
gu_names = [option.get_attribute("value") for option in gu_list]
gu_names = gu_names[1:] #시/군/구 항목 제거
gu_names[0] #확인
#시/군/구 선택하는 방법
gu_list_raw.send_keys(gu_name[인덱스])
#엑셀저장 버튼 클릭
#방법1
driver.find_element(By.CSS_SELECTOR, '#glopopd_excel').click() # css_selector는 '#id' 형태
#방법2
driver.find_element(By.XPATH, '//*[@id="glopopd_excel"]').click()
#방법3
driver.find_element(By.ID, 'glopopd_excel')
#구별 데이터를 받아서 엑셀로 저장
import time
from tqdm import tqdm_notebook
for gu in tqdm_notebook(gu_names):
element = driver.find_element(By.ID, 'SIGUNGU_NM0')
element.send_keys(gu)
time.sleep(3) #시간 텀을 주기 위해
driver.find_element(By.XPATH, '//*[@id="glopopd_excel"]').click()
time.sleep(3)

다운로드 폴더에 25개 엑셀파일이 저장됨
마지막 중랑구까지 저장되었는지 확인
앞에서 저장한 엑셀파일에 담긴 데이터를 읽어온다
glob : 파일의 목록을 읽어오고, 정리해주는 모듈
import pandas as pd
from glob import glob
# 파일 목록 한번에 가져오기
#"지역_"으로 시작하는 xls형태의 모든 파일을 가져옴
glob("../data/지역_*.xls")
# 파일명 저장
stations_files = glob("../data/지역_*.xls")
stations_files[:5] #확인
# 하나만 읽어보기
# 그냥 읽어보니 엑셀파일에 있던 의미 없는 컬럼이 있어서 이를 처리하기 위해
#인덱스2 컬럼부터 읽으라는 의미 -> header = 2
tmp = pd.read_excel(stations_files[0], header=2)
tmp.tail(2) #확인
#구별로 저장된 파일을 가져와서 리스트에 담아준다
tmp_raw = []
for file_name in stations_files:
tmp = pd.read_excel(file_name, header=2)
tmp_raw.append(tmp)
#구별 데이터를 합친다
#concat() : 형식이 동일하고 연달아 붙이기만 하면 될 때 사용
stations_raw = pd.concat(tmp_raw)
stations_raw #확인
#합친 데이터 확인
#합친 데이터들의 인덱스가 33까지만 계속 반복되므로 수정필요
stations_raw.info()
#필요한 값들로만 df 생성
#컬럼 조회
stations_raw.columns
#df 생성
stations = pd.DataFrame({
"상호":stations_raw["상호"],
"주소":stations_raw["주소"],
"가격":stations_raw["휘발유"],
"셀프":stations_raw["셀프여부"],
"상표":stations_raw["상표"]
})
stations.tail() #확인
#컬럼에 구이름 추가
stations["구"] = [eachAddress.split()[1] for eachAddress in stations["주소"]]
#구가 맞게 들어갔는지 종류, 개수 확인
stations["구"].unique(), len(stations["구"].unique())
stations
# 가격 연산을 위해 데이터형 변환 object -> float
# 가격 정보 없는 주유소가 있으면 빼고
stations[stations["가격"] == '-']
# 가격 정보가 있는 주유소만 사용
stations = stations[stations["가격"] != "-"]
stations.tail()
# 가격 연산을 위해 데이터형 변환 object -> float
stations["가격"] = stations["가격"].astype("float")
#가격 데이터형 변환 확인
stations.info()
# 인덱스 재정렬
stations.reset_index(inplace=True)
stations.tail()
#원래 있던 index 컬럼 제거
del stations["index"]
stations.tail()

## 주유 가격 정보 시각화
import matplotlib.pyplot as plt
import seaborn as sns
import platform
from matplotlib import font_manager, rc
get_ipython().run_line_magic("matplotlib", "inline")
# %matplotlib inline
rc("font", family="Malgun Gothic")
stations.boxplot(column="가격", by="셀프", figsize=(12,8))

모든 셀프 주유소가 일반 주유소보다 저렴하다고 할 수는 없지만,
대체로 셀프 주유소가 더 저렴하다고 볼 수는 있다.
박스 안에 데이터의 50%가 포함되어 있다. 박스 전체 길이를 IQR이라고 부른다. 이 IQR의 1.5배를 넘는 데이터를 outlier라 하고, outlier가 있다면 하나씩 그 데이터를 표기해준다.
아래 boxplot을 참고해보자
plt.figure(figsize=(12,8))
sns.boxplot(x="셀프", y="가격", data=stations, palette="Set2")
plt.grid(True)
plt.show

셀프 주유소와 일반 주유소의 중간값만 비교해보면 셀프 주유소의 중간값이 더 낮다. outlier는 셀프 주유소가 더 많은 것 같다. 그리고 일반 주유소를 보면 중간값은 1800보다 낮은 가격대에 있는데, 평균값은 그 보다 더 높은 값이다.
가격은 대체로 중간값 지점에 모여있는데, 특별하게 높은 가격대들이 있기 때문에 평균값은 더 높은 값이다.
plt.figure(figsize=(12, 8))
sns.boxplot(x="상표", y="가격", hue="셀프", data=stations, palette="Set3")
plt.grid(True)
plt.show()

셀프 주유소끼리 비교한다면, 서울시에서 SK에너지 셀프 주유소가 제일 비싸고, 알뜰주유소가 제일 저렴하다. 전체적으로는 GS칼텍스와 SK에너지가 비싼 편이다
import json
import folium
import warnings #빨간색 경고문구 무시
warnings.simplefilter(action="ignore", category=FutureWarning)
#index="구", values="가격"으로 df 만들기
import numpy as np
gu_data = pd.pivot_table(data=stations, index="구", values="가격", aggfunc=np.mean)
gu_data.head()
#경계선 json 파일을 이용한 지도시각화
geo_path = "../data/02. skorea_municipalities_geo_simple.json"
geo_str = json.load(open(geo_path, encoding="utf-8"))
my_map = folium.Map(location=[37.5502, 126.982], zoom_start=10.5, tiles="Stamen Toner")
my_map.choropleth(
geo_data=geo_str,
data=gu_data,
columns=[gu_data.index, "가격"],
key_on="feature.id",
fill_color="PuRd"
)
my_map

서울시에서 용산구가 가장 주유 가격이 비싸고, 그 다음으로 중구, 종로구, 강남구, 성동구가 비싸다.
<결론>


결론
위 시각화 자료들을 종합해보면 대체적으로 셀프 주유소가 일반 주유소보다 저렴하다.
또, 가장 비싼 주유소 top10은 모두 일반 주유소였고 가장 값싼 주유소 top10 중에서 9등까지가 셀프 주유소였다는 점을 봤을 때, 셀프 주유소가 더 저렴하다는 분석이 타당하다고 생각한다.
정보에 감사드립니다.