[데이터분석] Self Oil Station Price

·2023년 4월 8일
0
post-thumbnail

🔎 셀프 주유소 가격 분석


1. Selnium Basic

셀레니움

  • 웹 브라우저를 원격 조작하는 도구
  • 자동으로 URL을 열고 클릭 등이 가능
  • 스크롤, 문자의 입력, 화면 캡쳐 등

셀레니움 설치

  • 윈도우, mac(intel)
    conda install selenium
  • mac(m1)
    pip install selenium

크롬 드라이버 설치


📌 1-1. selenium webdriver 사용하기

  • 새로운 크롬창을 통해 지정한 웹 사이트 접근

from selenium import webdriver
driver=webdriver.Chrome("../driver/chromedriver.exe")
driver.get("https://pinkwink.kr/")

  • 창 크기 조절


# 화면 최대 크기 설정 
driver.maximize_window()
# 화면 최소 크기 설정 
driver.minimize_window()
# 화면 크기 설정
driver.set_window_size(600,600)
  • 기본 기능

# 새로고침
driver.refresh()
# 뒤로 가기
driver.back()
# 앞으로 가기
driver.forward()
  • Find an element

# 클릭
from selenium.webdriver.common.by import By
#copy selector
first_content=driver.find_element(By.CSS_SELECTOR,"#content > div.cover-masonry > div > ul > li:nth-child(1)") 
first_content.click()

# 새로운 탭 생성
driver.execute_script("window.open('')")

# 탭 이동 
driver.switch_to.window(driver.window_handles[0])

# 현재 열린 탭 닫기 
driver.close()

📌1-2. 화면 스크롤

# 스크롤 가능한 높이 가져오기 
driver.execute_script('return document.body.scrollHeight')

# 화면 스크롤 하단 이용 
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')

# 현재 보이는 화면 스크린샷 저장 
driver.save_screenshot('./last_height.png')

# 화면 스크롤 상단 이용 
driver.execute_script('window.scrollTo(0,0)')

# 특정 태그 지점까지 스크롤 이동 
from selenium.webdriver import ActionChains
some_tag=driver.find_element(By.CSS_SELECTOR,"#content > div:nth-child(2) > div > h2")
action=ActionChains(driver)
action.move_to_element(some_tag).perform()

driver.quit()

📌 1-3. 검색어 입력

- CSS_SELECTOR

from selenium import webdriver
from selenium.webdriver.common.by import By

driver=webdriver.Chrome("../driver/chromedriver.exe")
driver.get("https://www.naver.com/")

keyword=driver.find_element(By.CSS_SELECTOR,"#query") #검색창 
keyword.send_keys("파이썬")

# 검색어 새로 입력 
keyword.clear()
keyword.send_keys("딥러닝 ")

# 검색창 클릭 
search=driver.find_element(By.CSS_SELECTOR,"#search_btn")
search.click()

- XPATH

'''
'//' : 최상위
'*' : 자손 태그 
'/' : 자식 태그 
 
'''

driver.find_element(By.XPATH,'//*[@id="query"]').send_keys('xpath')
driver.find_element(By.XPATH,'//*[@id="search_btn"]').click()
driver.quit()

📌 1-4. selenium + beautifulsoup

# 현재 화면의 html코드 가져오기 
driver.page_source

from bs4 import BeautifulSoup

req=driver.page_source
soup=BeautifulSoup(req,'html.parser')

soup.select('.post-item')
[<div class="post-item">
 <a href="/1420">
 <span class="thum">
 <img alt="" src="//i1.daumcdn.net/thumb/C264x200/?fname=https://blog.kakaocdn.net/dn/caNtI3/btr1UVcjBwM/AfYo8FZdds3KtvbaTzI8n1/img.png"/>
 </span>
 <span class="title">핑크랩이 carla, autoware, ROS2 관련 프로젝트를 시작합니다.</span>
 <span class="date">2023. 3. 4. 17:38</span>
 <span class="excerpt">저는 최근 긴기간 진행한 AI 로봇 기술관련 [해당회사에서는 로봇으로 제품을 출시하는 것이 아니어서^^] 프로젝트를 마치고 잠시 소강상태였다가 이번에 새롭게 짧은 기간으로 새로운 프로젝트를 진행합니다. carla는 자율주행 시스템용 시뮬레이터로 unreal 엔진 기반인데 요즘 관심있는 분들이 많으시죠. 이번 클라이언트는 무거운 이 도구들과 ROS2를 이용하여 원할한 시뮬레이션 환경을 회사내에 구축하고 싶어 합니다. 그래서 클라이언트와의 상담으로 저희 핑크랩은 인턴쉽을 핑크랩 내에서 진행하고 해당 인턴이 클라이언트가 원하는 산출물을 직접 개발하도록 유도하고 그 산출물과 함께 직원으로 해당 회사에 합류하도록 진행합니다. 매우 핑크랩과 어울리는 진행입니다.^^. CARLA는 처음부터 자율 주행 시스템의 개발,..</span>
 </a>
 </div>,
 <div class="post-item">
 <a href="/1418">
contents=soup.select('.post-item')
contents[1].text
'\n\n\n\n\n아트센터 나비에서 예술쪽 분들에게 아두이노 교육을 진행했습니다.\n2023. 2. 27. 08:49\n시작은 작년인 2022년 10월부터입니다. 어떤때는 일주에 한번, 또 어떤때는 2~3주 걸러 한 번씩 금요일마다 오후에 진행되었습니다. 처음 이 건을 의뢰해 주신 분은 한국로봇융합연구원이었습니다. 담당자님~ 감사합니다.^^. 의뢰의 내용은 공학쪽이 아닌 분들에게 아두이노를 이야기하고 이분들이 스스로 뭔가를 할 수 있도록 교육을 진행할 수 있는지였습니다. 일단, 아트센터 나비라는 이름만으로도 꽤 재미있겠다 싶었습니다. 초반의 교육은 수서에 있는 로봇리빙랩에서 진행했고, 후반 교육은 서울시 서린동에 있는 아트센터 나비에서 진행했습니다. 이번에는 나비를 소개하는 몇몇 사진과 또 그 중 한 팀의 결과를 소개하려고 합니다. 아트센터 나비의 정문입니다. 들어가자 마자 보이는 공간입니다. 뭔가 이것저것 많이 배치된것..\n\n'

2. Self Oil Station Price 데이터 가져오기

  • Self Oil Station Price 사이트에 접근

  • 팝업창을 닫아 준 후 페이지 다시 요청

  • 원하는 페이지 로딩

import time 
from bs4 import BeautifulSoup
from selenium import webdriver

def getMainPage(driver):
    driver.get(url)
    #모든 창이 로딩 될떄까지 기다려 줌 
    time.sleep(8)
    #팝업창 화면 전환 후 닫아주기
    driver.switch_to.window(driver.window_handles[-1])
    driver.close()
    time.sleep(3)
    # 접근 페이지 다시 요청
    driver.switch_to.window(driver.window_handles[-1])
    driver.get(url)
    
  • diver 을 함수 안에서 선언할 경우 데이터를 가져올때 에러 발생

    	:  Failed to establish a new connection: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))
  • driver 선언 후 인수로 넘겨줘서 에러 발생 방지한다

url="https://www.opinet.co.kr/searRgSelect.do"
driver=webdriver.Chrome('../driver/chromedriver.exe')
getMainPage(driver)
  • 시/도 데이터 가져오기

# 지역 : 시/도
sido_list_raw = driver.find_element_by_id("SIDO_NM0")
sido_list_raw.text
'            시/도\n            \n             \n              서울\n             \n             \n            \n             \n             \n              부산\n             \n            \n             \n             \n              대구\n             \n            \n             \n             \n              인천\n             \n            \n             \n             \n              광주\n             \n            \n             \n             \n              대전\n             \n            \n             \n             \n              울산\n             \n            \n             \n             \n              세종\n             \n            \n             \n             \n              경기\n             \n            \n             \n             \n              강원\n             \n            \n             \n             \n              충북\n             \n            \n             \n             \n              충남\n             \n            \n             \n             \n              전북\n             \n            \n             \n             \n              전남\n             \n            \n             \n             \n              경북\n             \n            \n             \n             \n              경남\n             \n            \n             \n             \n              제주\n             \n            \n           '
sido_list=sido_list_raw.find_elements_by_tag_name("option")
len(sido_list), sido_list[17].text
(18, '제주')
sido_list[1].get_attribute("value")
'서울특별시'
  • 시/도 리스트 생성

sido_names=[]
for idx, option in enumerate(sido_list):
    if idx > 0:
        sido_names.append(option.get_attribute('value'))
        
sido_names
['서울특별시',
 '부산광역시',
 '대구광역시',
 '인천광역시',
 '광주광역시',
 '대전광역시',
 '울산광역시',
 '세종특별자치시',
 '경기도',
 '강원도',
 '충청북도',
 '충청남도',
 '전라북도',
 '전라남도',
 '경상북도',
 '경상남도',
 '제주특별자치도']
  • List comprehension

# list comprehension
sido_names= [ option.get_attribute('value') for idx, option in enumerate(sido_list) if idx>0 ]
sido_names
  • 군/구 데이터 가져오기

# 구 
gu_list_raw = driver.find_element_by_id("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:]
len(gu_names) , gu_names
(25,
 ['강남구',
  '강동구',
  '강북구',
  '강서구',
  '관악구',
  '광진구',
  '구로구',
  '금천구',
  '노원구',
  '도봉구',
  '동대문구',
  '동작구',
  '마포구',
  '서대문구',
  '서초구',
  '성동구',
  '성북구',
  '송파구',
  '양천구',
  '영등포구',
  '용산구',
  '은평구',
  '종로구',
  '중구',
  '중랑구'])
  • 엑셀 저장

element_get_excel=driver.find_element_by_id("glopopd_excel")
element_get_excel.click()
  • 모든 과정 함수로 만들기

  • 도/시 선택 후 군/구 를 선택해야 주유소 데이터를 가져올 수 있다.

  • 도/시 선택과 군/구 클릭을 자동화 해서 엑셀로 단운해준다.

  • 코드는 모든 도/시를 다운되게 만들었지만 서울시 데이터만 사용할 예정이다.

from tqdm import notebook

# 모든 도시의 주유소 다운 
for sido in notebook.tqdm(sido_names):
    #StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
    sido_list_raw = driver.find_element_by_id("SIDO_NM0")
    sido_list_raw.send_keys(sido) # 도/시 클릭         
    time.sleep(3) 
    #시/군/구
    for sigungu in sigungu_names:
            #시/군/구 값 가져옴 
            sigungu_list_raw=driver.find_element_by_id("SIGUNGU_NM0")
            sigungu_list_raw.send_keys(sigungu) # 군/구 클릭 
            time.sleep(3)
            # 엑셀 저장 
            element_get_excel=driver.find_element_by_id("glopopd_excel")
            element_get_excel.click()


driver.quit()

3. Self Oil Station Price 주유소 데이터 정리하기

  • glob를 이용해 엑셀 데이터 목록 한번에 가져오기

import pandas as pd
from glob import glob 

# 파일 목록 한번에 가져오기 
glob("../data/지역_*")
['../data\\지역_위치별(주유소) (1).xls',
 '../data\\지역_위치별(주유소) (10).xls',
 '../data\\지역_위치별(주유소) (11).xls',
 '../data\\지역_위치별(주유소) (12).xls',
 '../data\\지역_위치별(주유소) (13).xls',
 '../data\\지역_위치별(주유소) (14).xls',
 '../data\\지역_위치별(주유소) (15).xls',
 '../data\\지역_위치별(주유소) (16).xls',
 '../data\\지역_위치별(주유소) (17).xls',
 '../data\\지역_위치별(주유소) (18).xls',
 '../data\\지역_위치별(주유소) (19).xls',
 '../data\\지역_위치별(주유소) (2).xls',
 '../data\\지역_위치별(주유소) (20).xls',
 '../data\\지역_위치별(주유소) (21).xls',
 '../data\\지역_위치별(주유소) (22).xls',
 '../data\\지역_위치별(주유소) (23).xls',
 '../data\\지역_위치별(주유소) (24).xls',
 '../data\\지역_위치별(주유소) (3).xls',
 '../data\\지역_위치별(주유소) (4).xls',
 '../data\\지역_위치별(주유소) (5).xls',
 '../data\\지역_위치별(주유소) (6).xls',
 '../data\\지역_위치별(주유소) (7).xls',
 '../data\\지역_위치별(주유소) (8).xls',
 '../data\\지역_위치별(주유소) (9).xls',
 '../data\\지역_위치별(주유소).xls']
  • self oil proce 파일 저장

stations_files=glob("../data/지역_*.xls")
# 하나만 읽어보기
tmp=pd.read_excel(stations_files[0],header=2)
tmp.tail()

  • 모든 엑셀 데이터 합치기


tmp_raw=[]
for file_name in stations_files:
    tmp=pd.read_excel(file_name,header=2)
    tmp_raw.append(tmp)
    
len(tmp_raw)

# 형식이 동일하고 연달아 붙이기만 할때 concat 사용 
stations_raw=pd.concat(tmp_raw)
stations_raw
  • 원하는 정보만 가져오기

stations=pd.DataFrame({
    "상호" : stations_raw['상호'],
    "주소" : stations_raw['주소'],
    "가격" : stations_raw['휘발유'],
    "셀프여부" : stations_raw['셀프여부'],
    "상표" : stations_raw['상표'],
    
})

stations.tail()

stations['주소']
0     서울특별시 강동구  천호대로 1246 (둔촌제2동)
1            서울 강동구 구천면로 357 (암사동)
2                 서울 강동구 천호대로 1168
3              서울 강동구 고덕로 39 (암사동)
4           서울 강동구 양재대로 1323 (성내동)
                  ...             
29        서울 강남구 남부순환로 3170 (일원2동)
30            서울 강남구 논현로 747 (논현동)
31                 서울 강남구 압구정로 426
32                  서울 강남구 언주로 716
33           서울 강남구 봉은사로 433 (삼성동)
Name: 주소, Length: 446, dtype: object
# 주소에서 동만 가져오기 
stations['구']=[ address.split(' ')[1] for address in stations['주소'] ]
# 가격 데이터 변환 object->float

stations['가격']=stations['가격'].astype(float)
stations['가격'].unique()

# 인덱스 재정렬

stations.reset_index(inplace=True)
stations.tail()

  • 인덱스 제거

del stations['index']
stations.head()

4. Self Oil Station Price 시각화

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

%matplotlib inline
# get_ipython().run_line_magic("matplotlib","inline")

path='C:/Windows/Fonts/malgun.ttf'
plt.rcParams['axes.unicode_minus'] = False

if platform.system()=="Darwin": #mac
    rc("font",family='Arial Unicodes MS')
elif platform.system()=="Windows":  #window
    font_name=font_manager.FontProperties(fname=path).get_name()
    rc("font",family=font_name) 
else:
    print('Unknown System')
    
  • boxplot - pandas

# boxplot - pandas 

stations.boxplot(column='가격',by='셀프여부',figsize=(12,8))

# boxplot - seaborn

plt.figure(figsize=(12,8))
sns.boxplot(x="셀프여부", y='가격', data=stations, palette='Set3')
plt.grid(True)
plt.show()

plt.figure(figsize=(12,8))
sns.boxplot(x='상표', y='가격', hue='셀프여부', data=stations, palette="Set3")
plt.grid(True)
plt.show()

  • 지도 시각화

# 지도시각화 ![](https://velog.velcdn.com/images/jyunxx/post/9f335313-46fd-41c3-a06c-854ae0392913/image.png)

import json
import folium
import warnings
import numpy as np
warnings.simplefilter(action='ignore', category=FutureWarning) # 실행 여부와 관련없는 경고문구 무시 


# 가장 비싼 주유소 10개 
high_price_stations=stations.sort_values(by='가격',ascending=False)[:10]
# 가장 싼 주유소 10개 
low_price_stations=stations.sort_values(by='가격')[:10]

gu_data=pd.pivot_table(data=stations, index='구', values='가격', aggfunc=np.mean) # 구별 평균 가격 
gu_data.head()

  • choropleth

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




✍️ Beautifulsoup + Selenium 실습 후기
두 라이브러리를 잘 활용하면 모든 웹사이트 데이터 크롤링은 다 가능할 것 같다.
실습 예제를 하면서 예상치못한 오류를이 있었는데 모든 답은 구글링에 있다 ^^,,
새로운 기능을 실습하면서 기초적인 오류들에서 헤매게 되는 경우가 많아진다. 특히 chromedriver 을 함수내에만 선언 후 다음 코드에서 웹사이트의 데이터를 가져올려 했을때 driver 연결이 계속 끊어져서 고생했다...
항상 꼼꼼하게 코드를 작성하고 에러 방지 코드 작성을 습관화하자
profile
개발하고싶은사람

0개의 댓글