In [1]:
from selenium import webdriver
- 현재 최신 크롬드라이버의 명령 중 일부가 다른 버전과 다른듯 합니다. 본 Github에서 배포하는 driver를 사용하시기 바랍니다.
In [2]:
from bs4 import BeautifulSoup
4-2 서울시 구별 주유소 가격 정보 얻기¶
In [9]:
import time
from selenium.webdriver.support.ui import Select
driver = webdriver.Chrome('../driver/chromedriver.exe')
driver.get("http://www.opinet.co.kr")
driver.get("http://www.opinet.co.kr/searRgSelect.do")
# 한 번에 searRgSelect.do 페이지로 넘어가지 않는 경우
# http://www.opinet.co.kr 에서 들어가게 설정하면 해결됨
In [ ]:
area = driver.find_element_by_xpath('//*[@id="SIDO_NM0"]')
area.send_keys('서울')
# Selenium WebDriverException: unknown error: call function result missing 'value' while calling sendkeys method
# chromedriver 와 chrome browser 간 버전 호환 차이에 의한 에러.
# 참조 : https://stackoverflow.com/questions/49219594/selenium-webdriverexception-unknown-error-call-function-result-missing-value/49219750
# chromedriver 가 예전 버전이고, 본인 컴퓨터의 크롬이 최신버전인 경우 종종 발생.
# 크롬 브라우저와 chromedriver 를 둘 다 최신버전으로 사용하면 사라짐.
- Opinet은 사용자가 접속한 지역에 따라 지역을 잡아주는 기능이 있습니다.
- 이 기능을 배려하지 않고 코드가 짜여졌습니다.
- 원본은 이 기능이 구현되어 있지 않아, 아래 코드로 구현완료
- 지역에 서울이라고 나타나지 않으면 크롬 드라이버에서 손으로 서울로 잡아주세요.
In [10]:
# page_source 를 가져와 beautifulsoup 으로 엘리먼트 찾는 것으로 해결
html = driver.page_source
bs1 = BeautifulSoup(html)
bs2 = bs1.find('select', id = 'SIGUNGU_NM0')
bs3 = bs2.find_all('option')
gu_names = list(map(lambda val : val.text, bs3))[1:]
# lambda val : val.text 는 bs3 리스트에 들어있는 것들을 .text 하여 텍스트만 가져오기 위함
# map() 은 bs3 리스트를 람다 함수에 매핑하기 위함
# list() 는 매핑 한 결과를 리스트로 담아두기 위함
# [1:] 는 리스트의 0 번째 인덱스가 시/군/구 라는 이름이기 때문에 이를 제외하기 위함.
gu_names
Out[10]:
In [11]:
element = driver.find_element_by_id("SIGUNGU_NM0")
element.send_keys(gu_names[0])
In [12]:
xpath = """//*[@id="searRgSelect"]"""
element_sel_gu = driver.find_element_by_xpath(xpath).click()
In [13]:
xpath = """//*[@id="glopopd_excel"]"""
element_get_excel = driver.find_element_by_xpath(xpath).click()
In [14]:
import time
from tqdm import tqdm_notebook
# tqdm_notebook은 진행 상태 바를 나타내기 위한 용도.
# for 문 iteration 수행시 마다 진행 상태가 표기됨.
# tqdm_notebook(gu_names)은 iteration 마다 yield로 gu_names 안의 정보를 반환하는 제너레이터이다.
# gu_names 변수는 지역구의 이름들이 들어있는 리스트이고
# 반복문 수행시 gu 라는 변수에 할당됨
for gu in tqdm_notebook(gu_names):
element = driver.find_element_by_id("SIGUNGU_NM0")
element.send_keys(gu)
# 드롭다운 메뉴에서 구 이름을 선택한다.
time.sleep(2)
# time.sleep 이 있는 이유는 HTML 을 전부 불러오기 전에
# 코드가 실행된다면 대상 element 를 찾을 수 없는 에러가 종종 발생하기 때문.
xpath = """//*[@id="searRgSelect"]"""
element_sel_gu = driver.find_element_by_xpath(xpath).click()
time.sleep(1)
xpath = """//*[@id="glopopd_excel"]"""
element_get_excel = driver.find_element_by_xpath(xpath).click()
# 해당 구의 모든 주유소 정보를 다운로드하는 링크를 클릭한다.
time.sleep(1)
In [15]:
driver.close()
4-5. 구별 주유 가격에 대한 데이터의 정리¶
In [16]:
import pandas as pd
from glob import glob
In [19]:
glob('../data/지역_위치별(주유소)*xls')
# 작성자는 애플 os 로 한글을 작성하였는데, 자음과 모음이 분리되는 특색이 있다.
# 지운 후, 다시 지역_위치별(주유소) 로 대상 파일명을 바꿔준다.
# glob 모듈은 해당 경로에 위치한 파일/폴더를 리스트로 뽑아주는 기능이 있다.
# glob 에서 * 은 아무거나 좋다는 의미이고 모든 파일을 선택하고자 한다면 *.* 을 넣어준다.
# glob 에서 만약 확장자가 .csv 라는 파일만 전부 읽고자 한다면 *.csv 를 넣어주면 된다.
# 지금같은 경우에는 (주유소) 와 xls 사이에 * 을 붙여 주었는데 중간에 아무거나 와도 상관없는 조건이다.
Out[19]:
In [21]:
stations_files = glob('../data/지역_위치별*xls')
# 그렇게 불러온 파일명을 리스트로 저장해 stations_files 변수에 할당한다.
stations_files
Out[21]:
In [49]:
tmp_raw = []
# 이 파일은 3번째 row 가 컬럼명이고 데이터는 4번째 row 부터 시작이기 때문에
# pd.read_excel 수행시 header=2 옵션을 준다.(3번째 row 는 인덱싱 기준 2 이므로)
for file_name in stations_files:
tmp = pd.read_excel(file_name, header=2)
tmp_raw.append(tmp)
# 이렇게 불러온 엑셀파일을 일괄 리스트로 넣어준 이후
# pd.concat 을 이용하여 열 단위로 붙여준다(컬럼이 같으므로 아래로 이어붙인다.)
station_raw = pd.concat(tmp_raw)
In [50]:
station_raw.info()
In [51]:
station_raw.head()
Out[51]:
In [52]:
# station_raw 에는 지역, 상호, 주소, 상표, 전화번호, 셀프여부, 고급휘발유, 휘발유, 경유, 실내등유가 컬럼으로 존재.
# 여기에서 상호, 주소, 휘발유, 셀프여부, 상표 정보만 잘라내어 stations 변수에 넣고자 한다.
stations = pd.DataFrame({'Oil_store':station_raw['상호'],
'주소':station_raw['주소'],
'가격':station_raw['휘발유'],
'셀프':station_raw['셀프여부'],
'상표':station_raw['상표'] })
# 위 방법보다 더 쉬운 길이 있다.
# 컬럼 이름만 변경하지 않는다면 2 줄짜리 코드로 쉽게 구현 가능함.
'''
interested = ['상호', '주소', '휘발유', '셀프여부', '상표']
new_col_name = ['Oil_store', '주소', '가격', '셀프', '상표']
stations = station_raw[interested]
stations.columns = new_col_name
'''
stations.head()
Out[52]:
In [53]:
# 리스트 컴프리헨션이 사용되었다.
# .split 으로 띄어쓰기 기준 문자열을 잘라내고 [1] 인덱싱을 이용하여 1번째 인덱스인 구 정보만 추출
stations['구'] = [eachAddress.split()[1] for eachAddress in stations['주소']]
stations.head()
Out[53]:
In [54]:
# 구를 선별하기 위해 띄어쓰기 기반으로 일괄 처리하였으나 예외가 있어 서울특별시, 특별시와 같은 정보도 들어가버렸다.
stations['구'].unique()
Out[54]:
In [55]:
# 구 컬럼의 값이 서울특별시인 경우를 보아 성동구임을 확인하였다.
stations[stations['구']=='서울특별시']
Out[55]:
In [56]:
# 구 컬럼의 값이 서울특별시인 경우를 성동구로 직접 전처리 해 집어넣었다.
stations.loc[stations['구']=='서울특별시', '구'] = '성동구'
stations['구'].unique()
Out[56]:
In [57]:
# 구 컬럼의 값이 특별시인 경우를 보아 도봉구임을 확인하였다.
stations[stations['구']=='특별시']
Out[57]:
In [58]:
# 구 컬럼의 값이 특별시인 경우를 도봉구로 직접 전처리 해 집어넣었다.
stations.loc[stations['구']=='특별시', '구'] = '도봉구'
stations['구'].unique()
Out[58]:
In [59]:
# 가격이 나와있지 않은 경우를 제외하기 위해 가격을 - 로 하는 row 를 확인하였다.
stations[stations['가격']=='-']
Out[59]:
In [60]:
# 가격이 - 가 아닌 row 만 선별하여 stations 변수를 업데이트 하였다.
stations = stations[stations['가격'] != '-']
stations.head()
Out[60]:
In [34]:
# 가격이 현재 int 로 되어 있거나 mixed-type 일 수 있기 때문에 일괄 float 캐스팅하여 가격 컬럼을 업데이트하였다.
stations['가격'] = [float(value) for value in stations['가격']]
In [63]:
# 위에서 row를 지우는 작업을 하였기 때문에 index를 재설정하기 위한 코드
stations.reset_index(inplace=True)
del stations['index']
In [36]:
stations.info()
In [37]:
stations.head()
Out[37]:
4-4 셀프 주유소는 정말 저렴한지 boxplot으로 확인하기¶
In [38]:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
import platform
path = "c:/Windows/Fonts/malgun.ttf"
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
else:
print('Unknown system... sorry~~~~')
In [39]:
# 가격 컬럼에 대하여 셀프 여부에 따라 boxplot 을 그리는 코드
# 판다스에서 기본 지원되는 boxplot 메소드를 사용한다.
stations.boxplot(column='가격', by='셀프', figsize=(12,8));
In [40]:
# 위의 경우와는 다르게 이번에는 seaborn의 boxplot 메소드를 사용한다.
# seaborn 에서는 x, y, hue 를 사용하는데 hue 는 seaborn 에서만 있는 파라미터이다.
plt.figure(figsize=(12,8))
sns.boxplot(x="상표", y="가격", hue="셀프", data=stations, palette="Set3")
plt.show()
In [41]:
plt.figure(figsize=(12,8))
sns.boxplot(x="상표", y="가격", data=stations, palette="Set3")
sns.swarmplot(x="상표", y="가격", data=stations, color=".6")
plt.show()
4-5 서울시 구별 주유 가격 확인하기¶
In [42]:
import json
import folium
import googlemaps
import warnings
warnings.simplefilter(action = "ignore", category = FutureWarning)
In [43]:
# 서울 구별 휘발유값이 높은 주유소만 보기 위한 코드
stations.sort_values(by='가격', ascending=False).head(10)
Out[43]:
In [44]:
# 서울 구별 휘발유값이 낮은 주유소만 보기 위한 코드
stations.sort_values(by='가격', ascending=True).head(10)
Out[44]:
In [45]:
# pivot_table 을 이용해 구를 기준으로 인덱스를 잡고 가격을 np.mean 이용하여 평균값으로 계산.
import numpy as np
gu_data = pd.pivot_table(stations, index=["구"], values=["가격"],
aggfunc=np.mean)
gu_data.head()
Out[45]:
In [46]:
geo_path = '../data/02. skorea_municipalities_geo_simple.json'
geo_str = json.load(open(geo_path, encoding='utf-8'))
# 한국의 geo_json 지역정보를 불러오기 위한 코드.
map = folium.Map(location=[37.5502, 126.982], zoom_start=10.5,
tiles='Stamen Toner')
# folium data 항목에 gu_data 를 넣어주면 gu_data의 index인 구 이름을
# folium 내부에서 key 로 인식하여 해당 지역의 영역에 gu_data의 값을 전달한다.
# fill_color 에 설정한 옵션에 따라 gu_data로부터 전달받은 값이 반영되어 색깔로 보여지게 된다.
map.choropleth(geo_data = geo_str,
data = gu_data,
columns=[gu_data.index, '가격'],
fill_color='PuRd', #PuRd, YlGnBu
key_on='feature.id')
map
Out[46]:
In [ ]:
'[입문] 데이터 사이언스? 그게 뭔가요?' 카테고리의 다른 글
의사결정나무 기반 알고리즘 논문 리딩자료 (1) | 2020.08.21 |
---|---|
파이썬으로 데이터 주무르기 2장 코드에 주석을 달았다는 내용의 제목 (0) | 2019.09.07 |
[뉴스 정보] 조금 진지한 크롤링, selenium / beautifulsoup (0) | 2019.08.21 |
[뉴스 정보] 데이터 수집/저장 입문_2 (0) | 2019.08.21 |
[환율 정보] 데이터 수집/저장 입문_1 (0) | 2019.08.21 |