Post

Selenium

1
2
3
4
5
6
7
8
9
<목차>

0. 시작 전
1. 사이트 분석 및 접근
 1-1) 사이트 분석
 1-2) 접근
2. 자료 확보(원큐에)
3. 자료 정리
4. 시각화



0. 시작 전

이 글은 제로베이스의 강의를 토대로 작성합니다.
이번 시간에는 Selenium을 활용해 셀프주유소 가격 분석을 해볼겁니다.
아래 항목들은 우리에게 필요한 자료입니다


실습 전 잠깐 Selenium에 대해 간략하게 알아봅시다!

♠다운로드 받을 것:      모듈,   크롬 드라이버

1
2
3
4
5
6
7
8
9
<Beautiful Soup로 해결 안되는 것>
- 접근할 웹 주소를 알 수 없을 때
- 자바스크립트를 사용하는 웹페이지의 경우
- 웹 브라우저로 접근하면 안될 때

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


전 이 경로에서 pip install selenium 설치했습니다

1
C:\Users\withj\Desktop\python1\Lib\site-packages>



1. 사이트 분석 및 접근

1-1) 사이트 분석

Desktop View



위와 같은 창을 순서대로 클릭하면 아래와 같이 필터링 할 수 있는 페이지로 넘어갑니다
Desktop View

우리가 눈으로 확인이 가능한 스크롤바, 각 체크항목, 그리고 아래에는 엑셀 저장도 있습니다


참고로 이 페이지는 우리가 필터링할 때마다 보여지는 자료는 바뀌지만, 주소는 그대로기에 정적페이지를 웹크롤링하는 Beautifulsoup는 당연히 안됩니다.

'이때 selenium이 빛을 발한다'


1-2) 접근

한큐에 원하는 페이지를 생성해봅시다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 1회 주소접근시 홈페이지로 이동
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("webdriver.chrome.driver=C:/Users/withj/Desktop/chromedriver-win64/chromedriver.exe")
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome(options=chrome_options)
driver.get(url)

# 너무 빠른 실행시 오류 발생이라 3초 휴식하고 다음 동작 실행
time.sleep(3)   

# 2회 접근하면 원하는 url로 접근가능
driver.get(url)

Desktop View



F12(검사) 활용

다시 이 페이지로 돌아와서 코드를 짜봅시다

Desktop View


1번 버튼을 먼저 눌러야 왼쪽에 아무 버튼이나 갖다대도 좌측의 저런 상세내용이 우측 검사 칸에서 반응한다

Desktop View

사진에서 id값들은 각각 1개니 저 태그만 선택하면 되서 작업하기가 매우 편하고,
노란색을 보면 class = “hidden”인데 이건 속성값이 1개일지 n개일지 알 수 없스습니다.

이것 외에도 각각의 항목들을 저 1번버튼 켜고 검사해보면 조회, 기름 종류, 엑셀 저장 등 아무거나 싹 확인 가능합니다


시/도

1
2
3
4
5
6
7
from selenium.webdriver.common.by import By
sido_list_raw = driver.find_element(By.ID, "SIDO_NM0")
sido_list = sido_list_raw.text.split('\n')

# 시/도 리스트 출력
for sido in sido_list:
    print(sido)

Desktop View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 시/ F12 눌러서 보니 option 태그로 이렇게 뜨네
<option value="서울특별시">서울</option>
<option value="부산광역시">부산</option>
<option value="대구광역시">대구</option>
~           ~               ~
-----------------------------------------------------------------

# 인덱스 7번째 추출
sido_list = sido_list_raw.find_elements(By.TAG_NAME, 'option')
sido_list[7].get_attribute("value")
#➡️ 울산광역시

----------------------------------------------------------------------

# 시/도 전부 나열
sido_names = [] 
for option in sido_list:
    sido_names.append(option.get_attribute("value"))
sido_names

Desktop View

1
2
3
4
5
6
7
8
9
10
# 5개만 가져와보자
sido_names[:5]
#➡️ ['', '서울특별시', '부산광역시', '대구광역시', '인천광역시']

------------------------------------------------------------

# 공백 제거해주자
sido_names = sido_names[1:]
sido_names[:5]
#➡️ ['서울특별시', '부산광역시', '대구광역시', '인천광역시', '광주광역시']


1
2
# 키 전송해서 `울산`으로 바꾸기
sido_list_raw.send_keys(sido_names[6])

Desktop View

시/군/구

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 대기 시간 설정 (최대 10초 기다림)
wait = WebDriverWait(driver, 10)

# 대기 시간동안 해당 ID의 요소를 찾음
gu_list_raw = wait.until(EC.presence_of_element_located((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:]
print(gu_names[:5], len(gu_names))
# ➡️['남구', '동구', '북구', '울주군', '중구'] 5

gu_list_raw.send_keys(gu_names[0])

Desktop View

엑셀 저장

1
2
3
4
5
6
7
8
9
10
11
------3가지  아무거나 ㄱㄱ (, 연결해서 한번에 재생하지말고 따로따로)--------------

# 1
driver.find_element(By.CSS_SELECTOR, "#glopopd_excel").click()

# 2
driver.find_element(By.XPATH, '//*[@id="glopopd_excel"]').click()

# 3
element_get_excel = driver.find_element(By.ID, "glopopd_excel")
element_get_excel.click()

Desktop View

2. 자료 확보 (원큐에)

반복문(모든 의 정보가 담긴 xlsx 다운 받읍시다)

1
2
3
4
5
6
7
8
9
10
11
12
# ['남구', '동구', '북구', '울주군', '중구']

import time 
from tqdm import tqdm_notebook

for gu in tqdm_notebook(gu_names):
    element = driver.find_element(By.ID, "SIGUNGU_NM0")
    element.send_keys(gu)
    time.sleep(3)
    
    element_get_excel = driver.find_element(By.ID, "glopopd_excel").click()
    time.sleep(3)

Desktop View

다운로드 다 끝났으면 chrome팝업창 닫기 ㄱㄱ

1
driver.close()

이로써 울산의 남구, 동구, 북구, 울주군, 중구의 주유소 xlsx 5개를 확보했습니다



3. 자료 정리

아까 다운받은 xlsx 파일들 정리합시다

1
2
3
4
5
6
import pandas as pd 
from glob import glob 
src = "C:/Users/withj/Desktop/eda-practice/web"

# 파일 목록 한 번에 가져오기 
glob(src + "/지역_*.xls")

Desktop View


참고
임의의 xlsx 하나 열었는데 header가 2칸 밀려있네??

Desktop View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 여러 엑셀 파일 경로 가져오기
oil_info = glob(src + "/지역_*.xls")

# 각 엑셀 파일을 읽어 DataFrame으로 저장한 후 리스트에 추가
box = []
for oil in oil_info:
    tmp = pd.read_excel(oil, header=2)
    box.append(tmp)

# 리스트에 있는 모든 DataFrame을 합치기
new_oil = pd.concat(box)

# 결과 확인
print(new_oil.tail())

Desktop View

참고:      pd.concat   VS   pd.merge

1
2
3
4
5
6
7
8
9
# 잠깐!

pd.concat
데이터를 단순히  이름이나 인덱스가 서로 달라도 이어붙인다
( 갯수는 짝이 맞아야 )

pd.merge
 DataFrame을 특정 열을 기준으로 병합하는  사용되며, 공통된 열을 기준으로 병합한다.
(//)


병합

1
2
oil_raw = pd.concat(box)
oil_raw

Desktop View

1
2
3
4
oil_raw.info()

# 행은 59개인데 인덱스도 222개로 이상하고 기름이 object인 것도 이상하네
# 아래가서 조치해보겠습니다

Desktop View

1
2
3
4
5
6
7
8
9
10
# 새로운 DF

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

Desktop View

1
2
3
4
# 평균적으로 주유비가 비싼 구를 확인하기 위해 추가

gas[""] = [eachAddress.split()[1] for eachAddress in oil_raw["주소"]]
gas

Desktop View

1
2
3
4
# 열을 추가하면서 이상한 건 없네요!

gas[''].unique()
#➡️ array(['동구', '북구', '울주군', '중구', '남구'], dtype=object)


아까 이상했던 인덱스들 다 초기화하기

1
2
3
4
# 번호 다시 매기자

gas.reset_index(inplace=True)
gas.head() 

Desktop View

맨 앞 컬럼 2개 제거

1
2
gas.drop(['level_0', 'index'], axis=1, inplace=True)
gas

Desktop View


4. 시각화

총 4개 준비했습니다!

  • matplotlib     가격&셀프
  • seaborn     가격&셀프
  • 브랜드&가격&셀프
  • folium
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 기본 코드

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

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

path = "C:/Windows/Fonts/malgun.ttf"

if platform.system() == "Darwin":
    rc("font", family="Arial Unicode MS")
elif platform.system() == "Windows":  # 수정된 부분
    font_name = font_manager.FontProperties(fname=path).get_name()  # 수정된 부분
    rc("font", family=font_name)
else:
    print("Unknown system. sorry~~")


1
2
3
# matplotlib  가격&셀프

gas.boxplot(column="가격", by="셀프", figsize=(12, 8));

Desktop View

이걸 보니 울산의 셀프주유소가 아닌 곳이 가격이 더 비싸고,
특히 셀프 주유소가 아닌 곳에서 가격이 1800원대 근처가 2곳이고,
셀프 주유소는 값 범위 안에 없는 제일 비싼 곳들이 1700원이네요


이번엔 seaborn으로 봅시다

1
2
3
4
5
6
# seaborn  가격&셀프

plt.figure(figsize=(12, 8))
sns.boxplot(x="셀프", y="가격", data=gas, palette="Set1")
plt.grid(True)
plt.show()

Desktop View

seaborn도 마찬가지다 matplotlib이랑 똑같네요!

이번엔 브랜드를 추가해봤습니다

1
2
3
4
5
6
# 브랜드&가격&셀프

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

Desktop View

전반적으로 알뜰주유소만 셀프주유소고 제일 싸다는 것을 알 수 있고,
GS칼텍스가 평균적으로 많이 비싼편이네요


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# folium

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

# 가장 비싼 주유소 10개 
gas.sort_values(by="가격", ascending=False).head(10)

# 가장 값싼 주유소 10개 
gas.sort_values(by="가격", ascending=True).head(10)

import numpy as np 
gu_data = pd.pivot_table(data=gas, index="", values="가격", aggfunc=np.mean)
gu_data.head()

Desktop View


json 파일을 이용해 시각화 연동

1
2
3
4
# csv 저장
import pandas as pd
gas.to_csv('gas.csv', index=False)
gas_csv = pd.read_csv('gas.csv')


전 제가 필터링한 조건 기준하에 울산 내 모든 주유소에 관한 경도, 위도를 추출하기 위해 gas.csv를 저장해 223개 주유소의 경도, 위도를 생성했으며, 그 정보가 없는 29번째 행이 있었는데 그것은 아래와 같이 지워줬습니다

1
2
3
# 이화주유소가 경도, 위도 안나오더라
gas_c = gas.copy()
gas_c[gas_c['가격'] == 1619] 

Desktop View

1
2
gas_c.drop(gas_c.index[28], inplace=True)
gas_c[gas_c['가격'] == 1619] 

Desktop View

여기까지 끝내고 json파일에 각 행정구역별로 222개의 주유소 경도, 위도를 작성해주었습니다

최종 시각화(실패)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import json
import folium

src = "C:/Users/withj/Desktop/source"
# geo_path1 = src + "/original_ulsan_geo.json"
# geo_str = json.load(open(geo_path1, encoding="utf-8"))

geo_path2 = src + "/reversed_ulsan_geo.json"
geo_str = json.load(open(geo_path2, encoding="utf-8"))

ulsan_coordinates = [35.5384, 129.3114]

my_map = folium.Map(location=ulsan_coordinates, zoom_start=10.5, tiles="Stamen Toner", attr="Stamen")

folium.Choropleth(
    geo_data=geo_str,
    data=gu_data,
    columns=[gu_data.index, "가격"],
    key_on="feature.id",
    fill_color="PuRd"
).add_to(my_map)

my_map

Desktop View


시각화하니 위와 같은 결과가 나왔습니다. 영 이상하네요ㅋㅋ

이런 저런 다양한 방법을 시도하다 도저히 해결이 되지 않아서 교수님께 여쭤보니 json 파일의 각 행정구역별 polygon을 다시 고쳐보라고 하셨지만, 들을 강의가 많이 남아있어 아쉽지만 시간 관계상 어쩔 수 없이 넘어가야할 것 같습니다.

참고

  1. 제로베이스 강의
  2. https://velog.io/@insung_na/Selenium과-주유소-가격-정보
This post is licensed under CC BY 4.0 by the author.
3D GIF