본문 바로가기
[업무 지식]/Crawling

[Selenium] 경기도 주유소 데이터

by 에디터 윤슬 2024. 11. 24.

참고 도서

https://m.yes24.com/Goods/Detail/57670268

 

파이썬으로 데이터 주무르기 - 예스24

독특한 예제를 통해 배우는 데이터 분석 입문이 책은 누구나 한 권 이상 가지고 있을 파이썬 기초 문법책과 같은 내용이 아닌, 데이터 분석이라는 특별한 분야에서 초보를 위해 처음부터 끝까지

m.yes24.com

 

라이브러리 호출

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pyperclip
import time

 

1. 경기도 주유소 가격 정보 얻기

  • 데이터 추출 홈페이지

https://www.opinet.co.kr/searRgSelect.do

 

싼 주유소 찾기 오피넷

 

www.opinet.co.kr

 

  • webdriver 실행
# ChromeDriver 경로 설정
chrome_driver_path = 'user_path'

# Service 객체 생성
service = Service(chrome_driver_path)

# WebDriver 실행
driver = webdriver.Chrome(service=service)

# URL 선언
url = 'https://www.opinet.co.kr/searRgSelect.do'

# 페이지 열기
driver.get(url)

 

  • 홈페이지 이미지
    • 싼 주유소찾기 탭: 지역(서울, 경기 etc. -- 금천구, 안양시 etc. ----)

  • 시별 select 태그 찾기
from selenium.webdriver.common.by import By

# ID로 <select> 태그 찾기
list_raw = driver.find_element(By.ID, "SIDO_NM0")
gu_list_raw = driver.find_element(By.ID, "SIGUNGU_NM0")

# <select> 내부의 모든 <option> 태그 가져오기
gu_list = gu_list_raw.find_elements(By.TAG_NAME, 'option')

# 드롭다운 항목 텍스트 출력
for option in gu_list:
    print(option.text)

 

  • 명칭 리스트형으로 변환
# 구 명칭 리스트형으로 반환
gu_names = [option.get_attribute('value') for option in gu_list]
gu_names.remove('')
gu_names

 

  • 요소 찾기 및 클릭
# 요소 찾기 (ID로)
element = driver.find_element(By.ID, "SIGUNGU_NM0")
element.send_keys(gu_names[2])

# 조회 버튼 클릭
xpath = """//*[@id="searRgSelect"]"""
element_sel_gu = driver.find_element(By.XPATH, xpath).click()

 

  • 엑셀 파일 저장
import time
from tqdm import tqdm

for gu in tqdm(gu_names):
    element = driver.find_element(By.ID, 'SIGUNGU_NM0')
    element.send_keys(gu)
    time.sleep(2)
    
    # 조회 버튼 클릭
    xpath = """//*[@id="searRgSelect"]"""
    element_sel_gu = driver.find_element(By.XPATH, xpath).click()
    time.sleep(2)
    
    # 엑셀 파일 저장
    xpath1 = """//*[@id="templ_list0"]/div[7]/div/a"""
    element_get_excel = driver.find_element(By.XPATH, xpath1).click()
    time.sleep(2)

 

  • 드라이버 종료
driver.close

2. 시별 주유 가격에 대한 데이터 정의

  • 라이브러리 호출
# 라이브러리 호출 및 설치

from glob import glob
!pip3 install xlrd

 

  • 내려받은 엑셀 파일 불러오기
stations_files = glob('/Users/t2023-m0089/python/3. practice_python/python_joomooloogi/self_oil/oil_excel/지역*.xls')
stations_files

 

  • 병합
tmp_raw = []

for file_name in stations_files:
    tmp = pd.read_excel(file_name, header = 2)
    tmp_raw.append(tmp)
    
station_raw = pd.concat(tmp_raw)

header 매개변수의 역할
header는 엑셀 파일에서 컬럼 이름(헤더)이 위치한 행 번호를 지정합니다.
기본값은 header=0으로, 첫 번째 행(인덱스 0)을 컬럼 이름으로 사용합니다.
header=2는 세 번째 행(인덱스 2)을 컬럼 이름으로 사용하겠다는 의미입니다.

 

  • 데이터 확인
station_raw.info(

 

  • 원하는 컬럼 데이터프레임화
stations = pd.DataFrame({
    'Oil_store': station_raw['상호'],
    '주소': station_raw['주소'],
    '셀프': station_raw['셀프여부'],
    '휘발유_가격': station_raw['휘발유'],
    '경유_가격': station_raw['경유'],
    '상표': station_raw['상표']
})
stations.head()

 

  • 시 명칭만 추출하여 컬럼
stations['시'] = [si_name.split()[1] for si_name in stations['주소']]
stations.head()

 

  • 데이터 추출 확인
stations['시'].unique()

array(['이천시', '군포시', '동두천시', '안산시', '과천시', '하남시', '안양시', '수원시', '고양시',
       '포천시', '부천시', '의정부시', '구리시', '성남시', '양주시', '의왕시', '의왕', '오산시',
       '용인시', '양평군', '여주시', '여주군', '연천군', '김포시', '평택시', '시흥시', '안성시',
       '광주시', '광주', '광명시', '화성시', '파주시', '파주', '남양주시', '남양주'],
      dtype=object)

 

  • 이상 데이터 변환
replace_val = ['남양주', '파주', '광주', '의왕']

for val in replace_val:
    new_name = f'{val}시'
    stations.loc[stations['시'] == val, '시'] = new_name
    
stations['시'].unique()

array(['이천시', '군포시', '동두천시', '안산시', '과천시', '하남시', '안양시', '수원시', '고양시',
       '포천시', '부천시', '의정부시', '구리시', '성남시', '양주시', '의왕시', '오산시', '용인시',
       '양평군', '여주시', '여주군', '연천군', '김포시', '평택시', '시흥시', '안성시', '광주시',
       '광명시', '화성시', '파주시', '남양주시'], dtype=object)

 

  • 가격 데이터 문자 -> 숫자 변환
numeric_col = ['휘발유_가격', '경유_가격']

for col in numeric_col:
    stations[col] = pd.to_numeric(stations[col], errors = 'coerce')
    
    
stations.info()

 

  • null값 제거
stations.dropna(inplace=True)
stations.info()

 

3. 셀프 주유소는 저렴한가

  • 셀프 컬럼을 기준으로 가격 분포 확인
# 셀프 컬럼을 기준으로 가격 분포 확인

y_col = ['휘발유_가격', '경유_가격']

for col in y_col:
    plt.rcParams['font.family'] = 'AppleGothic'
    plt.rcParams['axes.unicode_minus'] = False
    plt.figure(figsize = (10, 6))
    sns.boxplot(data = stations, x = '셀프', y = col, hue = '셀프')
    sns.set_style('whitegrid')
    plt.ylabel(None)
    plt.title(f'{col}')
    plt.show()

 

  • 상표 컬럼을 기준으로 가격 분포 확인
# 상표 컬럼을 기준으로 가격 분포 확인

y_col = ['휘발유_가격', '경유_가격']

for col in y_col:
    plt.rcParams['font.family'] = 'AppleGothic'
    plt.rcParams['axes.unicode_minus'] = False
    plt.figure(figsize = (10, 6))
    sns.boxplot(data = stations, x = '상표', y = col, hue = '셀프', palette='Set3')
    sns.set_style('whitegrid')
    plt.ylabel(None)
    plt.title(f'{col}')
    plt.show()

 

4. 시별 주유 가격 확인하기

import json
import folium
import googlemaps
import warnings
warnings.simplefilter(action = 'ignore', category = FutureWarning)

 

  • 시별 평균 가격 피벗테이블
si_data = pd.pivot_table(stations, index = '시', values=['경유_가격', '휘발유_가격'], aggfunc = np.mean).round(2)
si_data

 

 

  • 경기도 json 데이터 로드
# GeoJSON 데이터 로드
geo_path = '경기도.geojson'
with open(geo_path, encoding='utf-8') as f:
    geo_data = json.load(f)
    
# GeoJSON 파일의 구조 확인
if 'features' in geo_data:
    print(geo_data['features'][0]['properties'])  # 첫 번째 feature 출력하여 구조 확인
else:
    print("GeoJSON 데이터에 'features' 키가 없습니다.")
    
{'OBJECTID': 1156, 'adm_nm': '경기도 수원시장안구 파장동', 'adm_cd': '3101154', 'adm_cd2': '4111156000', 'sgg': '41111', 'sido': '41', 'sidonm': '경기도', 'sggnm': '수원시장안구'}

 

  • 시 이름 변경
# GeoJSON의 시 이름 정리 (sggnm에서 시 이름 추출)
for feature in geo_data['features']:
    sggnm = feature['properties']['sggnm']  # 예: "수원시장안구"
    if '구' in sggnm or '군' in sggnm:  # "구" 또는 "군" 제거
        feature['properties']['sggnm'] = sggnm.split('시')[0] + '시'
        
# GeoJSON 파일의 구조 확인
if 'features' in geo_data:
    print(geo_data['features'][0]['properties'])  # 첫 번째 feature 출력하여 구조 확인
else:
    print("GeoJSON 데이터에 'features' 키가 없습니다.")
    
{'OBJECTID': 1156, 'adm_nm': '경기도 수원시장안구 파장동', 'adm_cd': '3101154', 'adm_cd2': '4111156000', 'sgg': '41111', 'sido': '41', 'sidonm': '경기도', 'sggnm': '수원시'}

 

  • Folium 지도 설정
# Folium 지도 생성 (경기도 중심 좌표 설정)
map_gyeonggi = folium.Map(location=[37.5, 127.2], zoom_start=9)

# Choropleth 추가 (예제 데이터)
folium.Choropleth(
    geo_data=geo_data,  # 필터링된 경기도 GeoJSON 데이터
    data=si_data['경유_가격'],    # 주유소 가격 데이터
    columns=[si_data.index, si_data['경유_가격']],  # 매핑할 컬럼 (시 이름과 가격)
    key_on='feature.properties.sggnm',  # GeoJSON의 시 이름 속성 (sggnm 필드)
    fill_color='OrRd',        # 색상 팔레트
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='경기도 지도'
).add_to(map_gyeonggi)

# 지도 출력
map_gyeonggi

# 지도 html 저장
map_gyeonggi.save('gyeonggi_map.html')

 

5. 경기도 주유 가격 상하위 50개씩 지도에 표기

  • top 50
oil_price_top10 = stations.sort_values(by = '경유_가격', ascending = False).head(50).reset_index()
oil_price_top10

 

  • bottom 50
oil_price_bottom10 = stations.sort_values(by = '경유_가격', ascending = True).head(50).reset_index()
oil_price_bottom10

 

  • top 50 위도, 경도 정보 google maps 활용하여 추출
  • bottom 50 동일
# API 코드 선언
gmaps_key = '*******'
gmaps = googlemaps.Client(key = gmaps_key)

from tqdm import tqdm

lat = []
lng = []

for n in tqdm(oil_price_top10.index):
    try:
        tmp_add = str(oil_price_top10['주소'][n]).split('(')[0]
        tmp_map = gmaps.geocode(tmp_add)
        
        tmp_loc = tmp_map[0].get('geometry')
        lat.append(tmp_loc['location']['lat'])
        lng.append(tmp_loc['location']['lng'])
        
    except:
        lat.append(np.nan)
        lng.append(np.nan)
        print('Here is nan')
        
oil_price_top10['lat'] = lat
oil_price_top10['lng'] = lng
oil_price_top10

 

  • top50, bottom 50 지도 시각화
import folium

# 지도 생성
map = folium.Map(location=[37.5, 127.2], zoom_start=10.5)

# 상위 10개 주유소 마커 추가 (핑크색, 투명도 적용)
for n in oil_price_top10.index:
    if pd.notnull(oil_price_top10['lat'][n]):
        folium.CircleMarker(
            location=[oil_price_top10['lat'][n], oil_price_top10['lng'][n]],
            radius=7,
            color='#DA5A2A',           # 테두리 색상
            fill_color='#DA5A2A',      # 내부 색상
            fill_opacity=0.5,          # 내부 색상 투명도
            opacity=0.3                # 테두리 투명도
        ).add_to(map)

# 하위 10개 주유소 마커 추가 (파란색, 투명도 적용)
for n in oil_price_bottom10.index:
    if pd.notnull(oil_price_bottom10['lat'][n]):
        folium.CircleMarker(
            location=[oil_price_bottom10['lat'][n], oil_price_bottom10['lng'][n]],
            radius=7,
            color='#3B1877',           # 테두리 색상
            fill_color='#3B1877',      # 내부 색상
            fill_opacity=0.5,          # 내부 색상 투명도
            opacity=0.3                # 테두리 투명도
        ).add_to(map)

# 지도 출력
map