셀레늄(selenium)

Selenium은 웹 브라우저의 기능을 하도록하는 프로그램입니다. 브라우저를 직접 실행하지 않고 selenium 메소드들을 이용해서 웹 브라우저 기능을 대신할 수 있게 합니다. Selenium은 Selenium 2(Selenium WebDriver), Selenium 1(Selenium RC), Selenium IDE, Selenium-Grid 툴로 구성됩니다. 우리가 사용하는 것은 Selenium 2(Selenium WebDriver)이다. 이것은 프로그래밍 언어(Java, C#, Python, Javascript등)에 맞는 인터페이스를 제공하여 프로그래밍을 이용하여 사용하기 편리합니다.

Selenium 2를 이용하기 위해서는 웹 브라우저에 맞는 드라이버를 다운로드 해야 합니다. 드라이버는 크롬, 파이어폭스, PhantomJS등이 있습니다. 브라우저를 실행시키지 않고 사용할 수 있는 크롬 headless 드라이버를 이용할 수도 있습니다. 파이썬에서 사용하는 selenium에 대한 문서는 http://selenium-python.readthedocs.io/index.html을 참고합니다. 더 자세한 사용법은 Selenium 파이썬 웹드라이버 API를 참조합니다.

설치

아나콘다 명령창에서 다음과 같이 입력합니다.

conda install selenium

드라이버 다운로드

셀레늄을 파이썬에서 사용하기 위해서는 브라우저에 맞는 드라이버를 다운로드해야 합니다. 여기서는 크롬 브라우저를 사용할 것이기 때문에 크롬 드라이버를 인터넷으로부터 다운받아 작업 디렉토리 아래 drivers 폴더에 넣습니다.

In [1]: import urllib.request
   ...: import os
   ...: 
   ...: driver_dir = 'drivers'
   ...: driver_ver = '2.42'
   ...: if not os.path.exists(driver_dir):
   ...:   os.makedirs(driver_dir)
   ...: 
   ...: url = 'https://chromedriver.storage.googleapis.com/' + driver_ver + '/chromedriver_win32.zip'
   ...: _, zip_file = os.path.split(url)
   ...: 
   ...: zip_path = os.path.join(driver_dir, zip_file)
   ...: 
   ...: if not os.path.exists(zip_path):
   ...:   urllib.request.urlretrieve(url, zip_path)
   ...: 

압축해제

다운받은 파일을 압축해제합니다.

In [2]: import zipfile
   ...: 
   ...: if os.path.exists(zip_path):
   ...:   zip_ref = zipfile.ZipFile(zip_path, 'r')
   ...:   for fname in zip_ref.namelist():
   ...:     fpath = os.path.join(driver_dir, fname)
   ...:     if not os.path.exists(fpath):
   ...:       zip_ref.extract(fname, driver_dir)
   ...:   zip_ref.close()
   ...: 

간단한 사용법

설치된 웹드라이버 경로를 설정합니다. 웹드라이버는 웹 브라우저에 해당하는 것이라고 생각할 수 있습니다. 여기서는 크롬 드라이버를 사용합니다. 드라이버 경로 설정은 아래와 같이 드라이버의 위치를 직접 지정해 주는 방법도 있고 웹드라이버가 운영체제의 실행 경로에 위치하도록 설정해도 됩니다.

In [3]: chrome_path = os.path.join(driver_dir, 'chromedriver.exe')

셀레늄에서 드라이버를 생성하기 위한 모듈인 webdriver를 import하고 크롬 드라이버를 이용해서 웹드라이버 객체를 생성합니다. 웹드라이버 객체가 브라우저 역할을 하게 됩니다.

In [4]: from selenium import webdriver
   ...: driver = webdriver.Chrome(chrome_path)
   ...: 

웹드라이버의 get() 메소드를 이용해 파이썬 홈페이지에 접속합니다.

In [5]: driver.get("http://www.python.org")

find_element_by_name은 속성(attribute)이 name인 성분(element)을 찾아 반환합니다. 성분이란 HTML의 태그들이라고 생각할 수 있습니다. 다음은 name='q'인 성분을 elem 변수에 지정하고 elem.clear() 메소드를 이용해서 입력(input)란을 모두 지웁니다.

In [6]: elem = driver.find_element_by_name("q")
   ...: elem.clear()
   ...: 

elem.send_keys() 메소드는 입력란에 문자를 입력합니다.

In [7]: elem.send_keys("pycon")

Keys.RETURN은 키보드 리턴키에 해당하는 것으로 엔터를 치는 효과를 냅니다.

In [8]: from selenium.webdriver.common.keys import Keys
   ...: elem.send_keys(Keys.RETURN)
   ...: 

이것을 실행하면 크롬 브라우저가 뜨고 파이썬 홈페이지에 접속해서 pycon을 검색합니다.

driver.close()를 사용하여 활동 브라우저 탭을 닫거나 driver.quit()을 이용하여 활동 브라우저 창을 종료할 수 있습니다.

주유소 가격

한국석유공사에서 제공하는 사이트 오피넷(opinet)을 이용해서 전국 주유소의 가격을 얻어와 봅니다.

In [9]: driver = webdriver.Chrome(chrome_path)

지역별 주유소 찾기 페이지로 이동합니다.

In [11]: driver.get("http://www.opinet.co.kr/searRgSelect.do")

시도 선택 성분을 찾습니다.

In [12]: sido = driver.find_element_by_css_selector("#SIDO_NM0")

시도 선택 리스트를 모두 찾습니다. 여기서 주의해야 할 것은 find_elements_*로 복수로 되어 있다는 것이다. 단수로 되어 있으면 첫번째 것을 반환하고 복수로 되어 있으면 모든 것을 찾아 리스트로 반환합니다.

In [13]: sido_options = sido.find_elements_by_tag_name('option')

시도 선택 리스트를 만듭니다. value 값이 없는 것은 포함시키지 않습니다.

In [14]: sido_list = [option.get_attribute('value') for option in sido_options if option.get_attribute('value')]
   ....: sido_list
   ....: 

서울특별시를 선택합니다.

In [15]: from selenium.webdriver.support.select import Select
   ....: sido_select = Select(sido)
   ....: sido_select.select_by_value(sido_list[0])
   ....: 

시군구 선택 성분을 찾습니다.

In [16]: sigg = driver.find_element_by_css_selector("#SIGUNGU_NM0")
   ....: sigg.tag_name
   ....: 
Out[16]: 'select'

시군구 리스트를 만듭니다.

In [17]: sigg_list = [option.get_attribute('value') for option in sigg_options \
   ....: if option.get_attribute('value')]
   ....: sigg_list
   ....: 
Out[17]: 
['강남구',
 '강동구',
 '강북구',
 '강서구',
 '관악구',
 '광진구',
 '구로구',
 '금천구',
 '노원구',
 '도봉구',
 '동대문구',
 '동작구',
 '마포구',
 '서대문구',
 '서초구',
 '성동구',
 '성북구',
 '송파구',
 '양천구',
 '영등포구',
 '용산구',
 '은평구',
 '종로구',
 '중구',
 '중랑구']

첫번째로 나오는 강남구를 선택합니다.

In [18]: sigg_select = Select(sigg)
   ....: sigg_select.select_by_value(sigg_list[0])
   ....: 

조회 버튼 성분을 찾고 클릭을 합니다.

In [19]: submit = driver.find_element_by_css_selector("#searRgSelect")
   ....: submit.click()
   ....: 

엑셀 파일 저장하기

만일 헤드리스 크롬 드라이버로 파일을 저장하려면 다음과 같은 함수를 실행한 후 다운로드를 실행해야 합니다. 실행할 때 인자로 웹드라이버 객체와 다운로드 디렉토리 문자열을 넘깁니다.

def enable_download_in_headless_chrome(driver, download_dir):
  # add missing support for chrome "send_command"  to selenium webdriver
  driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')
  params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': download_dir}}
  command_result = driver.execute("send_command", params)

아래에서 download_dir은 적당한 경로를 설정해 주어야 합니다.

dir_path = os.path.dirname(os.path.realpath(__file__))
download_dir = os.path.join(dir_path, 'data')
enable_download_in_headless_chrome(driver, download_dir)
save_excel = driver.find_element_by_css_selector("#glopopd_excel")
save_excel.click()

모든 자치구에 대해서 일괄적으로 실행을 합니다.

In [21]: import time
   ....: for gu in sigg_list:
   ....:   sigg = driver.find_element_by_css_selector("#SIGUNGU_NM0")
   ....:   sigg_select = Select(sigg)
   ....:   sigg_select.select_by_value(gu)
   ....:   time.sleep(1)
   ....:   save_excel = driver.find_element_by_css_selector("#glopopd_excel")
   ....:   save_excel.click()
   ....:   time.sleep(3)
   ....: