관리 메뉴

log.Sehee

[데이터 취업 스쿨 스터디 노트] EDA 학습과제 2 / 웹데이터 크롤링 / 유가분석 본문

Zerobase DS School

[데이터 취업 스쿨 스터디 노트] EDA 학습과제 2 / 웹데이터 크롤링 / 유가분석

Sehe_e 2024. 8. 17. 15:54

 


 

주유소 웹데이터 크롤링 및 유가분석

 

웹데이터 주소

오피넷 (https://www.opinet.co.kr/user/main/mainView.do) -> 싼 주유소 찾기 -> 지역별

 

필요한 정보

주유소명, 주소, 브랜드, 휘발유 가격, 경유 가격, 셀프 여부, 세차장 여부, 충전소 여부, 경정비 여부, 편의점 여부, 24시간 운영 여부, 구, 위도, 경도

 

필요한 모듈 import

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from bs4 import BeautifulSoup

import time
from tqdm.notebook import tqdm

import warnings

options = Options()
options.add_argument("start-maximized")
service = Service("../driver/chromedriver")

warnings.filterwarnings(action="ignore")

 

오피넷 열기

# 오피넷 열기
url = 'https://www.opinet.co.kr/searRgSelect.do'

driver = webdriver.Chrome(service=service, options=options)
driver.get(url)

 

 

원래는 selenium의 webdriver가 업데이트 되면서 driver가 필요없이 자동으로 호환되는 드라이버를 찾아 쓰는 방식이였는데 이번에 크롬 업데이트(127.0.6533.120)가 되면서 드라이버를 바로 가져오지 못하는 듯한 오류가 났다.

 

NoSuchDriverException: Message: Unable to obtain driver for chrome; For documentation on this error
AttributeError: 'str' object has no attribute 'capabilities'

 

스택오버플로우에 검색하여 같은 오류를 발견했고, options와 driver설정을 하면 된다는 해결책에 따라했더니 해결되었다. 

 

서울시 검색

# 서울시 검색
sido_list_raw = driver.find_element(By.CSS_SELECTOR, '#SIDO_NM0')
sido_list_raw.send_keys('서울특별시')

 

서울시 지역구 리스트로 저장

# 서울시 지역구 리스트로 저장
gu_list_raw = driver.find_element(By.CSS_SELECTOR, '#SIGUNGU_NM0')
gu_list = gu_list_raw.find_elements(By.TAG_NAME, 'option')

gu_names = [option.get_attribute('value') for option in gu_list]
gu_names = gu_names[1:]

 

가져와야하는 상세사항 전부 체크

# 상세사항 클릭
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()
driver.find_element(By.CSS_SELECTOR, '#SEL24_YN').click()

 

가져와야 하는 정보 가져오기 테스트

# 구 선택, 주유소 상세정보 클릭 및 정보 가져오기 테스트
element = driver.find_element(By.CSS_SELECTOR, '#SIGUNGU_NM0')
element.send_keys('강남구')

oil_list = driver.find_element(By.CSS_SELECTOR, '#body1')
oils = oil_list.find_elements(By.TAG_NAME, 'a')
oils[7].click()
time.sleep(1)

req = driver.page_source
soup = BeautifulSoup(req, 'html.parser')
detail = soup.select_one('#os_dtail_info > .inner')

# 주유소 이름
station_name = detail.select_one('#os_nm').text.split()[-1]
# 주소
address = detail.select_one('#rd_addr').text.split(' (')[0]
# 브랜드
brend = detail.select_one('#poll_div_nm').text
# 휘발유 가격
gasoline = detail.select_one('#b027_p').text
# 경유 가격
diesel = detail.select_one('#d047_p').text

# 셀프 여부 
self = detail.select('#SPAN_SELF_VLT_YN_ID > #self_icon')
self_tf = True if self else False
# 세차장 여부
wash = detail.select_one('#cwsh_yn').get('src')
wash_tf = False if 'off' in self else True
# 충전소 여부
lpg = detail.select_one('#lpg_yn').get('src')
lpg_tf = False if 'off' in lpg else True
# 경정비 여부
maint = detail.select_one('#maint_yn').get('src')
maint_tf = False if 'off' in maint else True
# 편의점 여부
cvs = detail.select_one('#cvs_yn').get('src')
cvs_tf = False if 'off' in cvs else True
# 24시간 운영 여부
sel24 = detail.select_one('#sel24_yn').get('src')
sel24_tf = False if 'off' in sel24 else True

print(station_name, address, brend, gasoline, diesel, self_tf, wash_tf, lpg_tf, maint_tf, cvs_tf, sel24_tf)

 

정보 가져와서 리스트에 2차원 배열로 정리, item_check로 검색 정보와 가져온 정보 개수가 맞는지 체크

info = []
item_check = 0
brend_gas_diesel = ['#poll_div_nm', '#b027_p', '#d047_p']
options = ['#cwsh_yn', '#lpg_yn', '#maint_yn', '#cvs_yn', '#sel24_yn']

# 지역구 for문으로 전부 정보 저장
for gu in tqdm(gu_names):
    
    # 구 입력
    element = driver.find_element(By.CSS_SELECTOR, '#SIGUNGU_NM0')
    element.send_keys(gu)
    
    # 주유소 리스트
    oil_list_raw = driver.find_element(By.CSS_SELECTOR, '#body1')
    oil_list = oil_list_raw.find_elements(By.TAG_NAME, 'a')
    
    item_check += int(driver.find_element(By.CSS_SELECTOR, '#totCnt').text)
    
    # 주유소 상세페이지 조회 및 정보 가져오기
    for oil in oil_list:
        oil.click()
        time.sleep(1)
        
        req = driver.page_source
        soup = BeautifulSoup(req, 'html.parser')
        detail = soup.select_one('#os_dtail_info > .inner')
        data = []
        
        # 구
        data.append(gu)
        # 주유소 이름
        data.append(detail.select_one('#os_nm').text.split()[-1])
        # 주소
        data.append(detail.select_one('#rd_addr').text.split(' (')[0])
        # 브랜드, 휘발유, 경유
        for n in brend_gas_diesel:
            data.append(detail.select_one(n).text)
        # 셀프 여부 
        self = detail.select('#SPAN_SELF_VLT_YN_ID > #self_icon')
        data.append('Y' if self else 'N')
        # 옵션 여부
        for n in options:
            option = detail.select_one(n).get('src')
            data.append('N' if 'off' in option else 'Y')
        
        info.append(data)
print(len(info), item_check)

 

DataFrame으로 만들기

import pandas as pd

columns = [
    'gu',
    'name',
    'address', 
    'brend', 
    'gasoline', 
    'diesel', 
    'self', 
    'carwash', 
    'charging station', 
    'maintenance',
    'shop',
    'open_24'
]

oil_df = pd.DataFrame(info, columns=columns)
oil_df.info()

 

가져온 정보 전부 문제없으면 웹페이지 닫기

driver.quit()

 

휘발유와 경유 수치로 나타내기 위해 int형으로 변경

change = ['gasoline', 'diesel']
oil_df[change] = oil_df[change].replace(',', '', regex=True).astype(int)

 

위도, 경도 정보 저장

# 위도, 경도 저장
import googlemaps

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

for i, row in oil_df.iterrows():

    tmp = gmaps.geocode(row['address'], language='ko')
    lat = tmp[0].get('geometry')['location']['lat']
    lng = tmp[0].get('geometry')['location']['lng']
    
    oil_df.loc[i, 'lat'] = lat
    oil_df.loc[i, 'lng'] = lng
oil_df.head()

 

시각화 준비

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import platform
from matplotlib import font_manager, rc

rc('font', family='Arial Unicode MS')
get_ipython().run_line_magic('matplotlib', 'inline')

 

구별 셀프 주유소 여부와 휘발유 가격 분포

# 구별 셀프주유소 여부와 휘발유 가격 분포
plt.figure(figsize=(12, 16))
sns.boxplot(x='gasoline', y='gu', hue='self', data=oil_df, palette='Set3')
plt.title('구별 휘발유 가격 분포')
plt.grid(True)
plt.show()

 

위 그래프를 보아 휘발유의 가격은 셀프주유소가 저렴하며 일정하게 분포되어있고, 일반주유소는 가격의 대비가 심하며 대체로 1800 ~ 2000원 대에 분포되어있음을 알 수 있다.

 

 

구별 셀프 주유소 여부와 경유 가격 분포

# 구별 셀프주유소 여부와 경유 가격 분포
plt.figure(figsize=(12, 16))
sns.boxplot(x='diesel', y='gu', hue='self', data=oil_df, palette='RdBu')
plt.title('구별 경유 가격 분포')
plt.grid(True)
plt.show()

 

 

결론

: 휘발유와 경유 가격 전부 셀프주유소가 일반주유소보다 저렴하다.

일반 주유소는 최고가격과 최저가격의 격차가 500원이 넘는 구가 존재하는 반면, 셀프 주유소는 가격대가 일정하며 최저 가격대에 다수 분포해있다. 이를 보아 일반 주유소보다 셀프 주유소의 가격이 합리적이라고 할 수 있다.

 

 


내일의 학습 목표

SQL 기초 1 - 5

 

 

Comments