EDA 과제2 풀이 메모

김민지·2023년 4월 20일
0
  • 셀프 주유소가 정말 저렴한지 알아보기
  1. https://www.opinet.co.kr/searRgSelect.do
  • 사이트 구조 확인
  • 목표 데이터
    : 주유소 이름, 주소, 브랜드(상호명),
    휘발유 가격, 경유 가격,
    셀프 여부, 세차장 여부, 충전소 여부, 경정비 여부, 편의점 여부, 24시간 운영 여부,
    구, 위도, 경도
  1. selenium을 활용한 크롤링
  • 페이지 접근 -> 서울 선택 -> 구별 선택 -> 서울시 구별 주유소 데이터 수집
    -> 전체 데이터 수집 -> 데이터 프레임 저장

2-1. 페이지 접근

from selenium import webdriver
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome("../driver/chromedriver.exe")
driver.get(url)

2-2. 서울 선택(고정)

from selenium.webdriver.common.by import By
sido_list_raw = driver.find_element(By.CSS_SELECTOR, "#SIDO_NM0")
sido_list = sido_list_raw.find_elements(By.CSS_SELECTOR, "option")
sido_names = [option.get_attribute('value') for option in sido_list]
sido_names = sido_names[1:]
sido_list_raw.send_keys(sido_names[0])  # 서울 선택

OR

seoul = driver.find_element(By.CSS_SELECTOR, "#SIDO_NM0 > option:nth-child(2)")
seoul.click()

2-3. 구별 선택

gu_list_raw = driver.find_element(By.CSS_SELECTOR, "#SIGUNGU_NM0")
gu_list = gu_list_raw.find_elements(By.CSS_SELECTOR, "option")
gu_names = [option.get_attribute('value') for option in gu_list if option.get_attribute('value')] # 공백 제거

OR

# 2부터 강남구 시작
driver.find_element(By.CSS_SELECTOR, "#SIGUNGU_NM0 > option:nth-child(2)").text
# 반복문 테스트
# 서울 고정
# 구별 순환
# "#SIGUNGU_NM0 > option:nth-child(2)" -> 강남구 (시작)
# "#SIGUNGU_NM0 > option:nth-child(26)" -> 중랑구 (끝)

import time

for i, v in enumerate(gu_names):    # enumerate는 index, value를 가져옴
	gu_selector = f'#SIGUNGU_NM0 > option:nth-child({i+2})'
    driver.find_element(By.CSS_SELECTOR, gu_selector).click()

	# 목적에 맞는 데이터를 가져오는지 확인
    totCnt = driver.find_element(By.CSS_SELECTOR, "#totCnt").text # 표시된 주유소 개수
    gu_totalCnt = len(driver.find_elements(By.CSS_SELECTOR, "#body1 > tr")) # 직접 주유소 개수 구하기  -> 두 데이터를 비교해서 정확히 가져오는지 확인
    
    print(i, v, totCnt, gu_totalCnt)
    time.sleep(0.5)
    

2-4. 서울시 구별 주유소 데이터 수집

# 데이터 수집 첫 번째 시도
# 하나의 구에서 데이터 수집 테스트

body1 = driver.find_element(By.CSS_SELECTOR, "#body1")

gas = body1.find_elements(By.CSS_SELECTOR, "td:nth-child(2)") # 휘발유값
diesel = body1.find_elements(By.CSS_SELECTOR, "#td:nth-child(3)) # 경유값
brand = body1.find_elements(By.CSS_SELECTOR, ".rlist img")
is_self = body1.find_elements(By.CSS_SELECTOR, "td.rlist span.ico")
name = body1.find_elements(By.CSS_SELECTOR, ".rlist > a")


gas[0].text, diesel[0].text, brand[0].get_attribute('alt'), is_self[0].text, name[0].text
# 이름이 길면 잘려서 나옴
# a 태그 데이터 가져오기
a_tag = body1.find_elements(By.CSS_SELECTOR, ".rlist a")
a_tag[0].get_attribute('href')
# 한글이 깨져나옴

-> 이 방법은 실패

# 데이터 수집 두 번째 시도
# 세부 페이지 클릭
driver.find_element(By.CSS_SELECTOR, "#body1 a").click()
driver.find_element(By.CSS_SELECTOR, ".header #os_nm").text  # 클래스 header 밑에 id값 지정

-> 화면상에서 글자가 가려져서 안 보이게 되면, ''공백을 가져옴.
-> 구글링해보기

driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")

-> 화면상 정보가 나타나지 않아도 데이터를 가져올 수 있음

# text와 'innerText, innerHTML, textContent'
# text는 화면에 안 보이면 데이터를 불러오지 못하지만, innerText 등은 화면에 보이지 않더라도 데이터를 가져올 수 있음

station_title_text = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").text
station_title_innerText = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")
station_title_text, station_title_innerText
# 반복문 테스트
# 세부 페이지에서 주유소 이름 가져오기

import time

for i, v in enumerate(gu_names):  # 구 선택
	gu_selector = f'#SIGUNGU_NM0 > option:nth-child({i+2})'
    driver.find_element(By.CSS_SELECTOR, gu_selector).click()
    time.sleep(1)
    print('--------------------')
    
    for idx in range(0, 3):   # 주유소 선택
    	detail_selector = f'#body1 > tr:nth-child({idx+1}) > td.rlist > a'
    	driver.find_element(By.CSS_SELECTOR, detail_selector).click()
        title = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")
        print(title)
# 테스트 코드
# 구별로 순회하면서, 세부 페이지 주유소 이름 수집

for i, v in enumerate(gu_names[:3]):
	gu_selector = f'#SIGUNGU_NM0 > option:nth-child({i+2})'
    driver.find_element(By.CSS_SELECTOR, gu_selector).click()
    station_items = driver.find_elements(By.CSS_SELECTOR, "#body1 > tr") # 주유소 목록
    print()
    print(f'============{v}에는 {len(station_items)}개의 주유소 존재===========')
	
    for idx in range(len(station_items))[:3]:
    	detail_selector = f'#body1 > tr:nth-child({idx+1}) > td.rlist > a'
    	driver.find_element(By.CSS_SELECTOR, detail_selector).click()
        title = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")
        print(f'{v} 주유소 이름 : ', title)
    time.sleep(0.5)
		    	
# 메인 데이터 수집

name = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")
gasoline = driver.find_element(By.CSS_SELECTOR, "#b027_p").get_attribute("innerText")
diesel = driver.find_element(By.CSS_SELECTOR, "#d047_p").get_attribute("innerText")
address = driver.find_element(By.CSS_SELECTOR, "#rd_addr").get_attribute("innerText")
brand = driver.find_element(By.CSS_SELECTOR, "#poll_div_nm").get_attribute("innerText")

name, gasoline, diesel, address, brand
# 부가정보 데이터 수집

cwsh_yn = driver.find_element(By.CSS_SELECTOR, ".service #cwsh_yn")
cwsh_yn.get_attribute('src').split('/')[-1] # '/'로 나누고, 마지막꺼를 가져옴
# 세차장 유무
'N' if '_off' in cwsh_yn.get_attribute('src').split('/')[-1] else 'Y'
# 충전소 유무
lpg_yn = driver.find_element(By.CSS_SELECTOR, ".service #lpg_yn")
'N' if '_off' in lpg_yn.get_attribute('src').split('/')[-1] else 'Y'
# 셀프 여부
try:
	driver.find_element(By.CSS_SELECTOR, "#self_icon").get_attribute("alt")
    is_self = 'Y'
except:
	is_self = 'N'
    
is_self

2-5. 전체 데이터 수집(구, 위도, 경도 포함)

  • maincode
# 1. 페이지 접근
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome("../driver/chromedriver.exe")
driver.get(url)
driver.get(url)
# 2. 서울 선택
seoul = driver.find_element(By.CSS_SELECTOR, "#SIDO_NM0 > option:nth-child(2)")
seoul.click()
# 3. 구별 선택
gu_list_raw = driver.find_element(By.CSS_SELECTOR, "#SIGUNGU_NM0")
gu_list = gu_list_raw.find_elements(By.CSS_SELECTOR, "option")
gu_names = [option.get_attribute('value') for option in gu_list if option.get_attribute('value')]
import pandas as pd
import time
import googlemaps

gmaps_key = "AIzaSyAc-8jqf4pKjDW91-dggnio0Sda7IdTZqA"
gmaps = googlemaps.Client(key=gmaps_key)

datas = []

for i in range(len(gu_names)):
	gu_selector = f'#SIGUNGU_NM0 > option:nth-child({i+2})'
    driver.find_element(By.CSS_SELECTOR, gu_selector).click()
	station_items = driver.find_elements(By.CSS_SELECTOR, "#body1 > tr")
    
    for idx in range(len(station_items)):
    	detail_selector = f'#body1 > tr:nth-child({idx+1}) > td.rlist > a'
    	driver.find_element(By.CSS_SELECTOR, detail_selector).click()
        
        name = driver.find_element(By.CSS_SELECTOR, ".header #os_nm").get_attribute("innerText")
		gasoline = driver.find_element(By.CSS_SELECTOR, "#b027_p").get_attribute("innerText")
		diesel = driver.find_element(By.CSS_SELECTOR, "#d047_p").get_attribute("innerText")
		address = driver.find_element(By.CSS_SELECTOR, "#rd_addr").get_attribute("innerText")
		brand = driver.find_element(By.CSS_SELECTOR, "#poll_div_nm").get_attribute("innerText")
        
        cwsh_yn = 'N' if '_off' in driver.find_element(By.CSS_SELECTOR, ".service #cwsh_yn").get_attribute('src').split('/')[-1] else 'Y'
        lpg_yn = 'N' if '_off' in driver.find_element(By.CSS_SELECTOR, ".service #lpg_yn").get_attribute('src').split('/')[-1] else 'Y'
        maint_yn = 'N' if '_off' in driver.find_element(By.CSS_SELECTOR, ".service #maint_yn").get_attribute('src').split('/')[-1] else 'Y'
        cvs_yn = 'N' if '_off' in driver.find_element(By.CSS_SELECTOR, ".service #cvs_yn").get_attribute('src').split('/')[-1] else 'Y'
        sel24_yn = 'N' if '_off' in driver.find_element(By.CSS_SELECTOR, ".service #sel24_yn").get_attribute('src').split('/')[-1] else 'Y'
        
        
        try:
			driver.find_element(By.CSS_SELECTOR, "#self_icon").get_attribute("alt")
    		is_self = 'Y'
		except:
			is_self = 'N'
        
        # address
        address = driver.find_element(By.CSS_SELECTOR, "#rd_addr").get_attribute("innerText")
        # gu
        gu = address.split()[1]
        
        # lat, lng
        tmp = gmaps.geocode(address, language='ko')
        lat = tmp[0].get('geometry')['location']['lat']
        lng = tmp[0].get('geometry')['location']['lng']
        
        datas.append({
        	'name' : name,
            'address' : address,
            'brand' : brand,
            'is_self' : is_self,
            'gasoline' : gasoline,
            'diesel' : diesel,
            'car_wash' : cwsh_yn,
            'charging_station' : lpg_yn,
            'car_maintenance' : maint_yn,
            'convenience_store': cvs_yn,
            '24_hour' : sel24_yn,
            'gu' : gu,
            'lat' : lat,
            'lng' : lng
        })
        
        time.sleep(0.2)
    time.sleep(0.5)
    
driver.quit()

df = pd.DataFrame(datas)
df.tail()
            
df.info()
# csv 파일 저장
import datetime

now = datetime.datetime.now()   # 현재날짜시각
nowDate = now.strftime('%Y%m%d')  # 형식 정해주기

df.to_csv(f'./oilstation_oneday_{nowDate}.csv', encoding='utf-8')
# csv 파일 읽기

stations = pd.read_csv('./oilstation_oneday_20220307.csv', encoding='utf-8', thousands=',', index_col=0)
stations

-> thousands=',' : 세자리수마다 있는 ,를 없애고 숫자형으로 가져옴

  1. 주유소 정보 시각화
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib import rc
rc("font", family="Malgun Gothic")
# 휘발유 boxplot (feat. seaborn의 boxplot)

plt.figure(figsize=(12, 8))
sns.boxplot(x='is_self', y='gasoline', data=stations, palette='Set1')
plt.grid(True)
plt.show()
# 휘발유 브랜드별 비교 (feat. seaborn의 boxplot)

plt.figure(figsize=(12, 8))
sns.boxplot(x='brand', y='gasoline', hue='is_self', data=stations, palette='Set1')
plt.grid(True)
plt.show()
# 휘발유 구별 비교 (feat. seaborn의 boxplot)

plt.figure(figsize=(18, 8))
sns.boxplot(x='gu', y='gasoline', data=stations, palette='Set1')
plt.grid(True)
plt.show()
# 경유 boxplot (feat. seaborn의 boxplot)

plt.figure(figsize=(12, 8))
sns.boxplot(x='is_self', y='diesel', data=stations, palette='Set1')
plt.grid(True)
plt.show()
# 경유 브랜드별 비교 (feat. seaborn의 boxplot)

plt.figure(figsize=(12, 8))
sns.boxplot(x='brand', y='diesel', hue='is_self', data=stations, palette='Set1')
plt.grid(True)
plt.show()
# 경유 구별 비교 (feat. seaborn의 boxplot)

plt.figure(figsize=(18, 8))
sns.boxplot(x='gu', y='diesel', data=stations, palette='Set1')
plt.grid(True)
plt.show()
  1. 지도 시각화
import json
import folium
# 가장 비싼 주유소 10개
stations[['gu', 'name', 'is_self', 'gasoline']].sort_values(by='gasoline', ascending=False).head(10).reset_index(drop=True)
# 가장 값싼 주유소 10개
stations[['gu', 'name', 'is_self', 'gasoline']].sort_values(by='gasoline', ascending=True).head(10).reset_index(drop=True)
# 지도 시각화용 데이터프레임

import numpy as np
gu_data = pd.pivot_table(data=stations, index='gu', values='gasoline', aggfunc=np.mean)
gu_data.head()
geo_path = '../data/02. skorea_municipalities_geo_simple.json'
geo_str = json.load(open(geo_path, encoding='utf-8'))
m = folium.Map(location=[37.5502, 126.982], zoom_start=10.5)

m.choropleth(
	geo_data=geo_str,
    data=gu_data,
    columns=[gu_data.index, 'gasoline'],
    key_one='feature.id',
    fill_color='PuRd'
)

m

<제로베이스 데이터 취업 스쿨>

0개의 댓글