EDA - 유가 분석

BlackLabel·2023년 10월 18일
0

EDA, 크롤링 기초 

목록 보기
8/13

selenium 기초

Selenium

  • 웹 브라우저를 원격 조작하는 도구
  • 자동으로 URL을 열고 클릭 등이 가능
  • 스크롤, 문자의 입력, 화면 캡처 등등
  • 기본적으로 속도가 빠르지 않다
  • 그래서 주로 BeautifulSoup이랑 같이 사용한다

Beautiful Soup만으로 해결할 수 없는 것

  • 접근할 웹 주소를 알 수 없을 때
  • 자바스크립트를 사용하는 웹 페이지의 경우
  • 웹 브라우저로 접근하지 않으면 안될 때

Selenium 시작하기

from selenium import webdriver
# 크롬 드라이버 경로 지정
driver = webdriver.Chrome('../driver/chromedriver.exe')
# get 명령으로 접근하고 싶은 주소 지정
driver.get('https://www.naver.com')
# 종료 필수
driver.quit()
  • 새로 열린 창을 닫을 때는 꼭 driver.quit()으로 닫는다
  • webdriver.Chrome 명령으로 크롬 드라이버의 경로 지정
  • get 명령으로 접근하고 싶은 주소 지정
  • 새로운 크롬이 나타나면서 지정한 웹 주소에 접근함

동작

  • 크롬 드라이버 경로 지정
  • get 명령으로 접근하고 싶은 주소 지정
  • quit 명령으로 종료
# 크롬 드라이버 경로 지정
driver = webdriver.Chrome('../driver/chromedriver.exe')
# get 명령으로 접근하고 싶은 주소 지정
driver.get('https://velog.io/@skarb4788/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%98%81%ED%99%94-%ED%8F%89%EC%A0%90')

현재 브라우저 창 크기 확인

  • get_window_size() : 현재 창의 {가로, 세로} 반환

  • get_window_position() : 현재 창의 위치 좌표 {x, y} 반환

  • get_window_rect() : size, position의 값 반환

  • 화면 크기가 중요한 이유

  • 보이는 화면에 태그가 존재해야지 에러가 발생하지 않는다

# 현재 브라우저 창 크기 확인
print(driver.get_window_size())
print(driver.get_window_position())
print(driver.get_window_rect())

# 브라우저 창 크기 조절
# 현재 보이는 화면에만 액션을 취할 수 있습니다.
driver.set_window_size(1052, 805)

# 화면 최대화
driver.maximize_window()
# 화면 최소화
driver.minimize_window()

스크롤


# 스크롤 높이 확인
# 자바 스크립트 코드 실행
last_height = driver.execute_script('return document.body.scrollHeight')
last_height
  • 현재 페이지의 스크롤 수를 가져온다
  • 무한 스크롤일 경우 스크롤 수가 갱신된다
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
  • 스크롤을 맨 아래로 보낸다(스크롤 높이만큼 아래로 보내줘)
driver.save_screenshot('./last_height.png')
  • 현재 화면 스크린샷(현재 위치에 파일명과 확장명)
driver.execute_script('window.scrollTo(0,0);')
  • 스크롤을 가장 위로 보낸다

ActionChains

  • 좀 더 복잡한 작업(hover over, drag and drop 등)을 수행할 수 있게 해주는 기능
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By


some_tag = driver.find_element(By.XPATH, '//*[@id="content"]/div[9]/nav/ul')

action = ActionChains(driver)
action.move_to_element(some_tag).perform()
  • xpath를 복사한 부분으로 화면을 이동시킨다
some_tag = driver.find_element_by_id('''gsc-i-id''')
some_tag.send_keys('data science')
  • id로 찾기
  • 검색창에 검색할 내용 채워넣기
  • send_keys() : 주로 검색이나 로그인할 때 사용한다
some_tag = driver.find_element_by_xpath(xpath).click()
  • 버튼 클릭

html 코드 가져오기

from bs4 import BeautifulSoup

req = driver.page_source
soup = BeautifulSoup(req, 'html.parser')
  • driver 가 현재 페이지
  • page_source가 현재 페이지의 소스 코드를 가져온다
result = soup.find_all('div','gsc-webResult gsc-result')
result[0]

안내사항

  • Selenium 문법 변경
    -기존 : find_element_by_css_selector('tag')
    -현재 : find_element(By.CSS_SELECTOR('tag'))
    -from selenium.webdriver.common.by import By 실행 필수
  • 실습 사이트 html 구조 변경(메뉴바 변경, 검색창 변경 등)

클릭

from selenium.webdriver.common.by import By

first_content = driver.find_element(By.CSS_SELECTOR, '#content > div.cover-masonry > div > ul > li:nth-child(1)')
first_content.click()

기타 기능

driver.refrech() # 새로고침
driver.back() # 뒤로 가기
driver.forward() # 앞으로 가기
driver.execute_script('window.open("https://www.naver.com")') # 새로운 탭 생성
driver.switch_to.window(driver.window_handles[0]) # 0번째 탭으로 이동
driver.close() # 현재 탭 닫기
  • execute_script : 자바스크립트를 사용하기 위한 명령어, ()안에 자바스크립트가 들어간다
  • driver.close : 탭 하나 닫기
  • driver.quit : 전체 닫기

검색어 입력

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.ID,'query')
keyword.send_keys('파이썬')
keyword.clear()
keyword.send_keys('딥러닝')
driver.find_element(By.CSS_SELECTOR,'#sform > fieldset > button').click()
  • 검색창을 찾아와서 그 곳에 검색하고 싶은 단어를 입력하고 검색 버튼을 찾아서 클릭하는 과정
  • 파이썬을 검색창에 입력하고 다른 검색어를 입력하면 누적된다
  • keyword.clear() 를 통해 한번 비워야한다

XPATH
'//' : 최상위(경로 중 최상위)
'*' : 자손 태그(하위 태그)
'/' : 자식 태그(바로 밑 태그)

driver = webdriver.Chrome('../driver/chromedriver.exe')
driver.get('https://pinkwink.kr')

find_elelmet

  • find_element_by_css_selector => find, select_one
  • find_elements_by_css_selector => find_all, select

현재 화면 html 코드 읽기

  • page_source 키워드 : 원하는 내용을 뷰티풀 수프를 활용하여 찾을 수 있다.
  • iframe : html 내부에 html을 사용한 것으로 기본적인 select, find로는 찾을 수 없다.
  1. 돋보기 버튼을 선택
  2. 검색어를 입력
  3. 검색 버튼 클릭
  • 1번이 잘 되지 않는다(ActionChains 활용)
    -동적 페이지라서
from selenium.webdriver import ActionChains

search_tag = driver.find_element(By.CSS_SELECTOR,'.search')
action = ActionChains(driver)
action.click(search_tag)
action.perform()

selenium으로 데이터 얻어오기

목표 : 셀프주유소가 저렴한지 확인
목표 데이터 : 브랜드, 가격, 셀프 주유 여부, 위치

from selenium import webdriver
import time

def main_get():
    url = 'https://www.opinet.co.kr/searRgSelect.do'
    driver = webdriver.Chrome('../driver/chromedriver.exe')

    driver.get(url)

main_get()
  • 페이지 접근
  • time을 import하는 이유
    -selemium의 경우 아직 갱신되지 않았는데 다음 작업을 수행하려고 하다가 에러가 발생할 수 있다
# 셀레니움은 느려서 한번에 요청하면 에러날 수 있어서 time으로 속도 조절해줘야 한다
# 페이지 접근
url = 'https://www.opinet.co.kr/searRgSelect.do'
driver = webdriver.Chrome('../driver/chromedriver.exe')

driver.get(url)
time.sleep(3)
# 문제가 발생할 경우

# 팝업창 화면 전환 후 닫아주기
# 팝업창으로 전환하기
driver.switch_to_window(driver.window_handels[-1])
팝업창으로 전환 후 닫아주기
driver.close()
time.sleep(3)
다시 원래로 전환해주기
driver.switch_to_window(driver.window_handles[-1])
# 다시 요청
driver.get(url)
  • 팝업창이 뜰 경우
  • 팝업창이 뜨지 않는다면 실행하지 않아도 무관
sido_list_raw = driver.find_element(By.ID,'SIDO_NM0')
sido_list_raw

sido_list = sido_list_raw.find_elements_by_tag_name('option')
len(sido_list), sido_list[1].text
sido_names = [option.get_attribute('value') for option in sido_list]
sido_names = sido_names[1:]
sido_names

sido_list_raw.send_keys(sido_names[0])
  • 지역에서 시/도 선택칸에 서울 선택해 넣기
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:]
gu_names
  • 지역에서 시/군/구 선택칸에 강남구 선택해 넣기
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(2)
    
    driver.find_element(By.ID, 'glopopd_excel').click()
    time.sleep(2)
  • 서울시의 각구에 대한 주유소 가격 데이터를 엑셀로 저장
  • 제목이 같으므로 제목 뒤에 숫자가 자동으로 붙어서 구별된다

주유소 가격 정보 정리하기

  • 서울시 25개 구에 해당하는 주유소 가격 데이터를 가지고 옴
import pandas as pd
from glob import glob

stations = glob('../data/지역_*.xls')
  • glob : 파일 목록을 읽어오고 정리해주는 모듈
  • 지역* : 지역로 시작하는 모든 파일
  • 25개 구 주유소 데이터 불러오기
tmp_raw = []

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

station_raw = pd.concat(tmp_raw)
station_raw

  • 25개 파일이 하나의 리스트에 append
  • 형식이 동일하고 연달아 붙이기만 하면 될 때는 concat명령을 사용
stations = pd.DataFrame({
    '상호':station_raw['상호'],
    '주소':station_raw['주소'],
    '가격':station_raw['휘발유'],
    '셀프':station_raw['셀프여부'],
    '상표':station_raw['상표']
})
stations.tail()
  • 컬럼 이름 수정
stations['구'] = [eachAddress.split()[1] for eachAddress in stations['주소']]
stations

  • 구 이름으로 구분해서 '구'라는 새 컬럼 생성
stations['가격'] = stations['가격'].astype('float')
  • 가격이 '-' 이런 데이터가 있어서 바뀌지 않아 에러 발생
  • 가격 정보 없는 주유소 데이터 삭제 후 형식 변환
stations[stations['가격'] == '-']
  • 가격 확인
stations = stations[stations['가격']!='-']
stations.info()
  • 가격 정보가 있는 주유소 데이터만 사용
stations.reset_index(inplace=True)
stations

  • index 재정렬

주유가격 시각화

데이터 시각화

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib import rc

%matplotlib inline
rc('font',family='Malgun Gothic')

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

  • 셀프 주유소와 일반 주유소의 가격 비교
plt.figure(figsize=(12,8))
sns.boxenplot(x='셀프',y='가격',data=stations, palette='Set3')
plt.grid(True)
plt.show()

  • seaborn이 제공하는 boxplot
plt.figure(figsize=(12,8))
sns.boxenplot(x='상표',y='가격',hue='셀프',data=stations, palette='Set3')
plt.grid(True)
plt.show()

  • 각 메이커 별 셀프 주유 여부를 포함해서 가격 분포 시각화

지도 시각화

import json
import folium
import warnings
warnings.simplefilter(action='ignore',category=FutureWarning) # 경고문 무시


gu_data = pd.pivot_table(data=stations, index='구', values='가격',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'))

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

profile
+database

0개의 댓글