brunch

You can make anything
by writing

C.S.Lewis

by JK의 계단 밑 연구실 Apr 06. 2018

<문과의 파이썬>
셀레니움(selenium)-(1)

셀레니움 설치하고 스타벅스 매장 정보 가져오기

requests와 bs4로만 웹 스크래핑을 하시다 보면 처음에는 거침없이 다 가져오는 모습에 만족하시겠지만 이내 문제에 직면하게 됩니다. 분명 브라우저 상 보이는 화면 & 코드인데 파이썬에서는 없다고 에러가 발생합니다. 예를 들어 다음과 같이 스타벅스 매장 찾기 사이트에서 서울 지역의 모든 매장 정보를 가져오고 싶은 경우를 생각해 봅시다. (https://www.istarbucks.co.kr/store/store_map.do)

위의 화면은 '매장 찾기 -> 지역 검색 -> 서울'을 선택하면 나오는 화면입니다. 이를 '요소 검사(크롬에서는 shift + ctrl + i)'로 코드를 보면 다음과 같습니다.

오예~! 'ul' 태그 밑에 'li' 태그로 깔끔하게 리스트 형태로 매장 정보가 나타나 있습니다. 이거 bs4의 'find_all'함수로 쉽게 가지고 올 수 있을 거 같습니다. 마침 class 명도 'quickResultLstCon'로 존재하니 'find_all('li', class_='quickResultLstCon')'로 서울 전 매장 정보를 가져와 보도록 하겠습니다.

import bs4
import requests
stbuck = requests.get('https://www.istarbucks.co.kr/store/store_map.do')
st_bs = bs4.BeautifulSoup(stbuck.text, 'lxml')
st_bs.find_all('li', class_='quickResultLstCon')

어...?! 비어있네요;;;; 어떻게 된 일일까요? 이유는 간단합니다. 파이썬이 request 해서 받은 정보는 우리가 '매장 찾기 -> 지역 검색 -> 서울' 버튼을 누르기 전 정보입니다. 때문에 현재 파이썬이 가지고 있는 코드에는 'li', class_='quickResultLstCon'가 존재하지 않는 거죠. 우리가 앞에서와 같이 버튼을 누르면 뒤늦게 브라우저가 그 부분 정보를(서울 지역 매장 정보만) 가지고 와서 코드를 변경하기 때문에 단순히 request 해서 받은 정보로는 우리가 원하는 정보를 가져올 수 없습니다.


자, 이럴 때 쓸 수 있는 카드 중 하나가 셀레니움(selenium)입니다. 셀레니움은 원래 브라우저를 조정해서 웹을 테스트하는 용도로 쓰입니다. 브라우저를 조정한다는 것은 브라우저 화면을 조정하여 브라우저 화면에 나타난 버튼을 눌러보거나 아이디, 암호 등을 입력하고 로그인을 하거나 스크롤 바를 내리고 올리고 하는 모든 행위를 뜻합니다. 여기서 잠깐! 버튼을 누른다고요?! 네, 브라우저 화면에 나타난 버튼을 누를 수 있습니다. 우리가 직면한 문제에 써먹을 수 있을 거 같습니다.


이를 우리의 문제에 써먹어 봅시다. 큰 스토리를 말하자면 셀레니움을 이용하여 브라우저가 위의 url(https://www.istarbucks.co.kr/store/store_map.do)의 정보를 가져오게 합니다. 브라우저가 정보를 가져오면  '매장 찾기 -> 지역 검색 -> 서울' 버튼을 차례로 누르게 시키는 겁니다. 그럼 결과적으로 브라우저에 위와 같은 화면이 생성되게 되고, 이와 동시에 브라우저는 위 사진에서 본 것과 같은 'li', class_='quickResultLstCon' 코드를 가지고 있게 됩니다. 이 코드를 브라우저에게 달라고 해서 분석을 하면 문제 해결입니다. (ㅎㅎ 언제나 그렇듯이 말은 쉽습니다.)


자 우리의 스토리를 코드로 구현해 봅시다. 


1. 셀레니움을 설치합니다.

셀레니움은 파이썬 내장 모듈도 아니고 아나콘다 내장 모듈도 아니기 때문에 새로 설치를 해야 합니다. pip install selenium 명령어를 이용하여 셀레니움 모듈을 설치합시다. (pip install 사용법이 익숙하지 않은 분들은 저의 모듈 설치 포스트를 참고하세요!)


2. 웹 드라이버를 다운로드합니다.

셀레니움은 '웹 드라이버'라는 녀석을 통해서 브라우저를 조정합니다. 크롬 브라우저를 조정하고 싶다면 '크롬 웹 드라이버'를 다운로드합니다. 파이어폭스 브라우저를 조정하고 싶다면 '파이어 폭스 웹 드라이버'를 다운로드하면 됩니다. 여기서는 '크롬 웹 드라이버'를 이용해 실습을 진행하겠습니다. 여기서(https://sites.google.com/a/chromium.org/chromedriver/downloads) 다운 받으세요!

녹색으로 빛나는 'ChromeDriver 2.37' 클릭!

자신의 os에 맞는 압축 파일 다운로드! 

zip 파일을 받아 풀어주고 chromedriver라는 파일을 저장하면 됩니다. 여기서 중요한 것은 chromedriver파일의 위치입니다. 저 같은 경우 'C:\Users\JK\Desktop\ChromeWebDriver'에 위의 파일이 있죠.


3. 셀레니움을 통한 브라우저 제어를 시도합니다.

먼저 webdriver를 import 하여 앞에서 받은 ChromeWebDriver와 연결시켜 줍니다. 아래와 같이 Chrome 함수에 chromedriver 파일이 있는 위치를 문자열로 입력하여 연결합니다. 결과로 webdriver 객체가 생성이 되는데 이를 'driver' 변수에 저장하게 습니다.

from selenium import webdriver

chromedriver_dir = r'C:\Users\JK\Desktop\ChromeWebDriver\chromedriver.exe'
driver = webdriver.Chrome(chromedriver_dir)

다시 한번 말씀드리자면 chromedriver_dir = '당신의 chromedriver.exe 가 저장된 경로\chromedriver.exe'입니다.


이제 webdriver객체 하나(지금 코드에서 driver)를 얻었으니 이를 이용해서 스타벅스 매장 찾는 사이트를 열어 봅시다. 

driver.get('https://www.istarbucks.co.kr/store/store_map.do')

위의 코드를 실행시키면 아래와 같이 크롬 브라우저가 하나 자동으로 실행되는 것이 보이 실 겁니다. (일단 브라우저 제어 성공!)

셀레니움은 web driver객체를 통해 여러가지 메서드를 제공하는 데, get() 메서드는 브라우저가 url로 get 요청을 할 수 있게 해주는 메서드입니다. requests.get(url)이랑 비슷한 데, requests는 파이썬에서 url 주소로 get요청을 했다면 driver.get(url)은 파이썬이 브라우저를 시켜서 url로 get 요청을 한 것이라 보시면 됩니다.


4. 브라우저에서 버튼 찾아 누릅시다.



먼저 브라우저에서 '지역 검색' 버튼을 눌러야 합니다. 요소 검사로 들어가 '지역 검색' 버튼의 코드를 찾아봅시다.  

 






오오! 찾았습니다. 오른쪽과 같이 header 태그의 'loca_search' 클래스를 찾으면 되겠네요! 셀레니움의 웹 드라이버 객체에는 element (태그 한 단락을 element라고 합니다. <header class='loca_search'> ..... </header>가 하나의 엘레멘트이죠!)를 가져오는 방법이 여러 가지 있는데 여기서는 class 이름을 이용하여 가져오도록 하겠습니다.

loca = driver.find_element_by_class_name('loca_search')
loca.click()

find_element_by_class_name()은 메서드 이름만 봐도 어떤 메서드인지 감이 오실 겁니다. 바로 class를 기준으로 element를 가지고 오는 거죠. element를 가지고 와서 click() 합니다.

오오 결과가 원하는 데로 나왔습니다. 이제 '서울' 버튼을 눌러 브라우저가 서울 지역의 매장 정보를 가져오게 합시다. 이번에도 변한 브라우저에서 요소 검사로 코드를 살펴봅니다. '지역 검색' 버튼을 눌렀기 때문에 시/도를 선택할 수 있는 코드 부분이 추가가 되었습니다.


'ul 태그' 안의 'li 태그'들이 각 시도 정보를 포함하고 있습니다. 서울은 화면에 첫 번째에 나오더니 li 리스트에도 첫 번째로 있네요. 


일단 서울 버튼을 누르고 싶으니 첫 번째 li 엘레멘트를 가져와야 하는 데 class 이름도 따로 없고 li 태그 명으로 검색해 봐야 li가 코드 안에 너무 많기 때문에 우리가 원하는 결과를 얻기 힘들 거 같습니다. 


때문에 좀 더 크게 보고 전략적으로 먼저 <ul class='sido_area_box'> 엘레멘트를 먼저 가져옵시다. 그 안에 li들은 다 시도에 관한 li들이니 ul 엘레멘트에서 li 태그를 다시 검색하면 될 거 같습니다. 정리하자면

<ul class='sido_area_box'> 엘레멘트를 검색 -> 검색 결과 안에서 li 엘레멘트들을 모두 가져오면 그중 첫 번째 엘레멘트가 서울!

이 되겠네요. 코드로 쳐 보겠습니다.

sido = driver.find_element_by_class_name('sido_arae_box')
li = sido.find_elements_by_tag_name('li')
li[0].click()

먼저 <ul class='sido_area_box'> 엘레멘트를 클래스 명으로 가져옵니다. driver 객체는 앞에서 지역검색 버튼을 click 했으니 이제 해당 코드를 가지고 있습니다. ul 엘레멘트에서 이제는 태그명 'li'로 검색해서 모조리 가져옵니다. element에서 복수형 elements로 메서드를 바꾸면 모든 엘레멘트에 접근하게 됩니다. 여기서 첫 번째 li 엘레멘트를 찾아 클릭합니다. 

오예!!! 또다시 제대로 바뀌었네요. 잘 되고 있습니다. 강동구, 강남구 일일이 눌러가면서 각 구의 매장 정보를 가져올 필요 없이 고맙게도 '전체' 버튼이 있습니다. 다른 거 생각할 거 없이 '전체' 버튼만 누르면 끝입니다. 자, 앞에서 했던 거 또 하면 되겠죠? 이번에는 바로 코드를 보여드리겠습니다.

gugun = driver.find_element_by_class_name('gugun_arae_box')
guli = gugun.find_element_by_tag_name('li')
guli.click()


구, 군의 정보가 담긴 ul태그 클래스 이름이 'gugun_arae_box' 입니다. 이 ul안의 첫 번째 li가 '전체' 버튼튼이죠. (스타벅스 홈페이지 만드신 분 네이밍 센스도 함께 알아갑니다ㅎㅎ) 여기까지 잘 따라오시면 브라우저가 오른쪽과 같이 바뀌었을 겁니다. 이제 마지막으로 이 브라우저에서 코드 정보를 가져와 평소 하던 대로 bs4를 이용하시면 됩니다.


5. 동적으로 변한 브라우저에서 소스 정보 가져옵시다.

현재 조정하고 있는 웹 드라이버에서 코드 정보를 가져오는 것은 다음과 같이 간단합니다.

source = driver.page_source

웹 드라이버 객체의  'page_source'에 코드가 저장이 되어 있는 셈이죠. 이를 다시 bs4를 이용하여 파싱해 보겠습니다. 마지막으로 변한 브라우저에서 코드를 보면 매장 정보가 아래와 같은 ul태그 밑에 li태그에 리스트 형태로 쭉 저장되어 있음을 알 수 있습니다. 

자, bs4를 사용하실 줄 아신다면 쉽죠? 저는 아래와 같이 가져왔습니다.

import bs4
source = driver.page_source
bs = bs4.BeautifulSoup(source, 'lxml')
entire = bs.find('ul', class_='quickSearchResultBoxSidoGugun')
li_list = entire.find_all('li')

마지막으로 각 li 태그에 저장이 되어 있는 주소 정보를 for문으로 print 한 번 해보겠습니다.

네, 원하는 대로 잘 나오네요 : )


자, 정리하자면 bs4로 동적으로 변하는 웹의 정보를 가져오고 싶지만 제약이 있었습니다. 우리는 이 제약을 셀레니움을 이용하여 해결하였습니다. 셀레니움은 파이썬을 통하여 웹 브라우저를 자동으로 동작할 수 있게 도와주는데, 이 특징을 이용하여 동적인 정보들을 브라우저에 먼저 받아 놓고 브라우저의 코드를 파싱해서 정보를 가져온 겁니다. 

여기서는 동적으로 생성되는 정보를 얻기 위해 버튼만 엄청 눌렀는데 조금만 응용하면 파이썬을 이용하여 자동으로 로그인하여 로그인을 해야만 볼 수 있는 정보들을 가져올 수 도 있습니다. 다음 시간에는 이것을 해 보겠습니다. 감사합니다 : )


마지막으로 전체 코드 입니다. 중간에 time.sleep(5)으로 5초 동안 강제로 멈춘 부분이 있는데, 이는 스타벅스 매장 정보 사이트가 로딩 시간이 있어서 모든 정보들이 브라우저에 나타날 때까지 기다리기 위해 넣어 준 코드입니다. 버튼을 누르고 브라우저가 리다이렉트 되는 시간보다 코드가 실행되는시간이 빠르기 때문에 에러가 발생하는 것이지요. 5초나 준 이유는 해보시면 알겠지만 이 싸이트가 생각보다 느려요 ;;;

from selenium import webdriver
import bs4
import time

chromedriver_dir = r'C:\Users\JK\Desktop\ChromeWebDriver\chromedriver.exe'
driver = webdriver.Chrome(chromedriver_dir)
driver.get('https://www.istarbucks.co.kr/store/store_map.do')
time.sleep(5)

loca = driver.find_element_by_class_name('loca_search')
loca.click()
time.sleep(5)

loca = driver.find_element_by_class_name('sido_arae_box')
li = loca.find_elements_by_tag_name('li')
li[0].click()
time.sleep(5)

gugun = driver.find_element_by_class_name('gugun_arae_box')
guli = gugun.find_element_by_tag_name('li')
guli.click()
time.sleep(5)

source = driver.page_source
bs = bs4.BeautifulSoup(source, 'lxml')
entire = bs.find('ul', class_='quickSearchResultBoxSidoGugun')
li_list = entire.find_all('li')

for infor in li_list:
    print(infor.find('p').text)
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari