[Python] Selenium을 활용한 동적 웹 크롤링

거친코딩·2021년 5월 31일
1
post-thumbnail

실시간 주유소 가격정보 수집과 분석

  • 동적 웹크롤링
  • 서울시의 주유소 가격정보 수집
  • 주유소 가격 엑셀 데이터 정리
  • 주유소 가격 정보 분석
    • 셀프 주유소가 정말 저렴할까?
    • 4대 주유 브랜드 별 가격 차이가 있을까?
    • 서울에서 가장 비싼 주유소와 저렴한 주유소 통계

🚩 Selenium

Selenium을 이용한 데이터 수집과 시각화

  • Beautiful soup을 이용한 스크래핑의 한계
    • Beautiful soup으로 접근할 수 있는 데이터는 정적 웹 페이지만 가능→ 수집의 한계
    • 자바 스크립트로 작성된 웹페이지
    • 자바스크립트를 이용하여 함수 호출이 되어야 웹 크롤링이 가능함
  • Selenium을 이용한 크롤링
    • 웹브라우즈의 원격조정이 가능한 라이브러리
    • 가상의 웹 브라우저를 띄우고 데이터 자동 수집
      - 크롤링하지 못하도록 막은 사이트도 데이터 수집 가능
    • 단순 반복되는 작업에 활용 → 업무 자동화에 활용

실시간 주유소 가격 비교 사이트 분석

오피넷

  • 오피넷에서는 매일 주유 데이터를 받아오는 것이 아니라, 고객들의 카드 정보를 통해서 데이터가 이 사이트에 쌓이고 반영된다.
  • 자바스크립트로 클라이언트 화면에서의 동작을 통해서만 데이터를 뿌려주는 경우(오피넷), 동적 크롤링을 사용해야 한다.


Selenium 라이브러리 설치

  • Selenium 모듈 추가
  • 브라우저 웹 드라이버 다운로드 http://chromedriver.chromium.org/downloads
    • 자신의 브라우저 버전과 동일한 파일 선택
      우측 상단 세개 점 선택 => 도움말 => chrome 정보
  • 웹 드라이버 실행파일 복사
    • 파이썬 프로그램 소스가 위치한 프로젝트에 복사

Selenium 테스트

  • 사이트 접속하기
# 라이브러리 불러오기
from selenium import webdriver
# 드라이버 연결
driver = webdriver.Chrome()
# 웹사이트 이동
driver.get('http://www.naver.com/')
# 브라우저 (드라이버) 종료
# driver.close()


오피넷 접속하기

  • 화면 자동 클릭 구현하기
import pyautogui # 화면 자동클릭을 위한 라이브러리 사용
from selenium import webdriver
driver = webdriver.Chrome() # 드라이버 연결
# 웹사이트 이동
driver.get('https://www.opinet.co.kr') # 직접 주유소 찾기 화면으로 갈수 없도록 되어 있음
print(pyautogui.position())
pyautogui.moveTo(1776, 893,1) # 절대 좌표로 1초 동안 이동
pyautogui.click()
  • 시간 지연처리
import pyautogui
from selenium import webdriver
import time
driver = webdriver.Chrome() # 드라이버 연결
# 웹사이트 이동
driver.get('https://www.opinet.co.kr') # 직접 주유소 찾기 화면으로 갈수 없도록 되어 있음
time.sleep(3)
# driver.close()
print(pyautogui.position())
pyautogui.moveTo(1776, 893,1) # 절대 좌표로 1초 동안 이동
pyautogui.click()
time.sleep(1)
  • 태그 분석

  • 구 선택 id 정보 얻기

driver.find_element_by_id("SIDO_NM0").send_keys('서울특별시') # 1st 리스트 박스를 찾아서 강원도 선택
second_list_raw = driver.find_element_by_id("SIGUNGU_NM0")
time.sleep(1) # 첫번째 리스트를 선택하고 대기 필수
second_list = second_list_raw.find_elements_by_tag_name("option")
# 2nd 리스트 박스에서 옵션리스트 뽑기

# 리스트 만들기
option_values = []
for option in second_list:
    option_values.append(option.get_attribute("value"))
print(option_values)
  • 빈 문자 제거하기
option_values.remove('') # 빈 문자 제거
print(option_values)
  • 시군구 옵션에 "강북구" 키 입력
second_list_raw.send_keys('강북구') # 키 입력을 강북구로 선택
# second_list_raw.send_keys(option_values[2]) # 키 입력을 강북구로 선택
time.sleep(3)
  • 다운로드 버튼 클릭하기

# 엑셀 저장버튼 누르기, 저장확인
file_down = driver.find_element_by_id('glopopd_excel').click()
time.sleep(3)

# 강서구 다운로드 받아 보기
second_list_raw = driver.find_element_by_id("SIGUNGU_NM0")
second_list_raw.send_keys('강서구') # 또는 option_values[3]
time.sleep(3)
file_down = driver.find_element_by_id('glopopd_excel').click()
time.sleep(3)
  • 반복문으로 모든 구 데이터 다운로드하기
# 강서구 다운로드 받아 보기
second_list_raw = driver.find_element_by_id("SIGUNGU_NM0")
second_list_raw.send_keys('강서구') # 또는 option_values[3]
time.sleep(3)
file_down = driver.find_element_by_id('glopopd_excel').click()
time.sleep(3)

# 자동 반복 다운로드
for cnt in range(len(option_values)):
    second_list_raw = driver.find_element_by_id("SIGUNGU_NM0")
    second_list_raw.send_keys(option_values[cnt]) # 키 입력을 차례대로 선택
    time.sleep(3)
    file_down = driver.find_element_by_id('glopopd_excel').click()

🚩 데이터 분석 및 시각화

여러 개의 엑셀파일을 하나의 리스트에 담기

  • 파일 이름과 경로 확인
import pandas as pd
from glob import glob
print(glob('지역*.xls')) # '지역'으로 시작되는 모든 xls 파일명을 리스트에 담기
merged_list = glob('지역*.xls') # 새로운 리스트에 저장

list_tabel = [] # 엑셀 내용을 담을 리스트
for file_name in merged_list:
    tmp = pd.read_excel(file_name, header=2)
    list_tabel.append(tmp)

print(list_tabel) # 25개의 테이블이 저장된 리스트
total_gas_station = pd.concat(list_tabel) # 25개의 테이블을 하나의 리스트구조로 반환
print(total_gas_station)

필요한 정보를 DataFrame으로 가져오기

  • 분석에 필요한 테이블 재구성
gas_station = pd.DataFrame({'주유소명':
total_gas_station['상호'],\
'경유가격': total_gas_station['경유'],\
'셀프': total_gas_station['셀프여부'],\
'브랜드': total_gas_station['상표'],\
'주소': total_gas_station['주소']})
print(gas_station)

분석에 필요한 필드/데이터 가공

  • 경유 가격이 없는 데이터 삭제
print(gas_station.info())
gas_station = gas_station[gas_station['경유가격'] != '-']
print(gas_station.info())

  • 가격 정보를 실수형으로 변환
gas_station['경유가격'] = [float(value) for value in gas_station['경유가격']]
print(gas_station.info())

  • 1열 인덱스 번호 재지정
gas_station.reset_index(inplace=True)
print(gas_station)


한글 출력 설정

  • windows
# 한글 출력을 위한 폰트 설정
from matplotlib import font_manager, rc # 한글 시각화 패키지 설치
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
  • MAC
# 한글 출력을 위한 폰트 설정
from matplotlib import font_manager, rc # 한글 시각화
rc('font', family='AppleGothic')

시각화

  • 셀프 VS 비셀프 가격 비교
import matplotlib.pyplot as plt
gas_station.boxplot(column='경유가격', by='셀프')
plt.show()

비셀프는 셀프에 비해 가격 평균과 분산이 크고 outlier가 많이 존재한다. 즉, 비셀프의 경우 주유소가 가지고 있는 환경에 따라 가격이 심하게 차이가 날 수 있다는 추측을 해볼 수 있다.

  • 브랜드별 가격 분포
import seaborn as sns
sns.boxplot(x='브랜드', y='경유가격', hue='셀프’, data=gas_station)
plt.show()

우리가 익히 알고 있는 주류 주유 브랜드(GS칼텍스, SK에너지 S-OIL 등)의 가격 분포는 알뜰 주유소보다 더큰 가격 분포를 가지고 있으며, outlier또한 많이 존재한다. 그리고 특히 SK에너지가 가장 넓은 분포의 주유 값과 가장 큰 주유값의 주유소를 가지고 있다.

  • 브랜드별 셀프 VS 비 셀프 가격 비교
    sns.boxplot(x='셀프', y='경유가격', hue='브랜드', data=gas_station)
    plt.show()


서울지역 최저, 최고 가격 통계

  • 최고가격 10곳, 최저가격 10곳 출력
print(gas_station.sort_values(by='경유가격' , ascending=False).head(10))

print(gas_station.sort_values(by='경유가격', ascending=True).head(10))


profile
데이터 분석 유튜버 "거친코딩"입니다.

0개의 댓글