Wait 을 쓰는 이유는 웹 페이지의 로드 가 다 수행되지 않은 상태에서 css 선택 코드가 작동하면 오류를 일으키기 때문에 로딩이 완료될 때까지의 시간을 일부러 주는 것이다.
implicitly wait : 웹 요소가 로딩될 때까지 시간을 주는 것. 주로 웹 사이트 주소와 find_element 사이에서 동작한다. 주소를 주고, 해당 주소에서 css 요소를 찾는 데 까지 주는 시간을 말한다.
그런데 중요한 것은 요소를 찾앗음에도 오류가 날 수 있는데, (로그인 버튼 같은 것이 클릭할 수 없는 상태에) 메서드가 실행 불가능한 상태일 경우 그렇다. 예컨대
driver.find_element_by_css_selector('a.top-nav__login-link').click()
위 코드의 class 를 잘 찾았음에도 클릭이 불가능한 상태라면 오류가 난다.
이러한 문제를 해결하기 위한 것이 time.sleep 이다.
time.sleep() : 전달된 초만큼 코드를 멈추는 것.
즉 implicitly wait 을 먼저 실행해보고 나서 추가적으로 오류가 나는 곳에 time.sleep 코드를 써주면 된다. 보통 웹 페이지의 코드가 로드되거나 바뀌는 부분이다.
Explicitly wait 을 구현하기 위해서는 임포트를 몇 개 해줘야 한다.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
역시나 하고자 하는 목표는 링크나 버튼 같은 것이 존재하지만 클릭하지 못하는 상태를 클릭할 수 있을 때까지 기다려주는 것이다. 코드가 길지만 꽤나 직관적인 메서드들을 사용한다.
login_link = WebDriverWait(driver,3).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
하나씩 뜯어보자.
WebDriverWait(driver,3)
WebDriverWait 은 단순히 만드는 메서드이고, 드라이버를 최대 3초 동안 기다리겠다는 것이다. 무엇을?
.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
요소가 클릭가능할때까지 기다리겠다는 것이고, 앞에는 어떤 선택자인지와 뒤에는 어떤 요소 인지가 들어간다.
wait = WebDriverWait(driver, 3)
login_link = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
login_link.click()
기다리는 시간이 같다면 저렇게 새로운 변수로 뺄 수 있다. 다 선택된다면 클릭을 그제야 수행한다. 전체 코드를 보자!
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get('https://workey.codeit.kr/costagram/index')
wait = WebDriverWait(driver, 3)
login_link = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
login_link.click()
id_box = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login-container__login-input')))
id_box.send_keys('codeit')
pw_box = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login-container__password-input')))
pw_box.send_keys('datascience')
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.login-container__login-button')))
login_button.click()
driver.quit()
나머지는 다 같지만 id_box 와 pw_box 에는 요소가 클릭 가능할 때까지가 아닌 visibility_of_element 라 되어있는데, 요소가 눈에 보일 때까지라는 것이다. 보통 요소가 눈에 보이면 키를 보내는 것이 가능하다.
element_to_be_clickable() 웹 요소가 클릭 가능한 상태일 때까지 기다림.
visibility_of_element_located() 웹 요소가 실제로 보일 때까지 기다림.
text_to_be_present_in_element() 웹 요소 안에 텍스트가 로딩될 때까지 기다림.
invisibility_of_element_located() 웹 요소가 안 보일 때까지 기다림.
until() 에 전달해주면 되는 wait 의 조건들이다.
이제까지 배운 것은 클릭 과 키보드 입력 이었으나 실제로는 그 이외의 동작도 많다. 액션체인은
✅ 연결하는 방식
from selenium.webdriver.common.action_chains import ActionChains
# 사용자 동작에 필요한 웹 요소들 찾기
(ActionChains(driver)
# 사용자 액션 정의
.perform())
웹 요소를 미리 다 찾아두고, 체인을 만든다음, .perform() 으로 실행시키는 것이다. 깔끔하게 보이기 위해 코드를 다음줄로 쳐서 만드는 과정에서 아예 감싸는 괄호가 하나 더 추가된다. 코드를 보자.
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.implicitly_wait(3)
driver.get('https://workey.codeit.kr/costagram/index')
# 로그인 링크 클릭
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)
# 아이디 박스, 비밀번호 박스, 로그인 버튼 찾아놓기
id_box = driver.find_element_by_css_selector('.login-container__login-input')
pw_box = driver.find_element_by_css_selector('.login-container__password-input')
login_button = driver.find_element_by_css_selector('.login-container__login-button')
# 액션 실행
(ActionChains(driver)
.send_keys_to_element(id_box, 'codeit')
.send_keys_to_element(pw_box, 'datascience')
.click(login_button)
.perform())
driver.quit()
하나 조심해야 할 것은 아이디, 비번, 로그인 요소가 찾아지는 건 그 전에 로그인 링크를 클릭하는 명령이 수행되어서이다. 로그인 링크를 클릭하지 않고 요소만 먼저 찾으라는 명령은 오류가 난다.
모두 찾아놓고 각각의 메서드들로 메서드.(요소, '필요시 값') 을 적어주는 것이다.
✅ 나열하는 방식
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.implicitly_wait(3)
driver.get('https://workey.codeit.kr/costagram/index')
# 로그인 링크 클릭
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)
# 아이디 박스, 비밀번호 박스, 로그인 버튼 찾아놓기
id_box = driver.find_element_by_css_selector('.login-container__login-input')
pw_box = driver.find_element_by_css_selector('.login-container__password-input')
login_button = driver.find_element_by_css_selector('.login-container__login-button')
# 액션 실행
actions = ActionChains(driver)
actions.send_keys_to_element(id_box, 'codeit')
actions.send_keys_to_element(pw_box, 'datascience')
actions.click(login_button)
actions.perform()
driver.quit()
actions 라는 하나의 액션 체인 변수를 생성하고 그 변수로만 코드를 실행하는 것이다.
클릭
.click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 클릭.
더블 클릭
.double_click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 더블 클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 더블 클릭.
우클릭
.context_click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 우클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 우클릭.
드래그 앤 드롭
.drag_and_drop(source, target)
source 웹 요소를 클릭해서 target 웹 요소까지 드래그한 다음, 드롭한다.
마우스 이동
.move_to_element(element)
element 웹 요소까지 마우스를 움직입니다.
키보드 입력
.send_keys(keys)
.send_keys_to_element(element, keys)
send_keys()는 현재 선택된 웹 요소에, send_keys_to_element()는 element 요소에 키보드 입력을 보낸다.
동작 중지
.pause(seconds)
seconds초 동안 동작을 멈춘다.
앞서 학습한 Beautiful Soup 과 Selenium 의 웹 스크래핑 메서드들을 비교하면 다음과 같다.

driver_execute_script 메서드를 실행하면 그 안에 자바스크립트 코드를 실행시킬 수 있다.
from selenium import webdriver
import time
driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()
driver.get('https://workey.codeit.kr/orangebottle/index')
time.sleep(3)
driver.execute_script("window.scrollTo(0,200);")
scroll_height = driver.execute_script('return document.body.scrollHeight')
print(scroll_height)
scrollTo 는 그 안에 있는 좌표만큼 움직이라는 것이다.
return document.body.scrollHeight 통해서는 문서 바디의 길이를 반환할 수 있다.JS 를 사용해 웹 페이지의 끝까지 스크롤하는 코드는 다음과 같다.
from selenium import webdriver
import time
driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()
driver.get('https://workey.codeit.kr/orangebottle/index')
time.sleep(3)
# 현재 scrollHeight 가져오기
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
# scrollHeight까지 스크롤
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 새로운 내용 로딩될때까지 기다림
time.sleep(0.5)
# 새로운 내용 로딩됐는지 확인
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
현재 길이를 가져오고 끝가지 스크롤한다. 새로운 길이에 다시 현재 길이를 가져오고 만약 현재 길이와 새로운 길이가 같아진다면 종료한다.
웹 사이트에서 동작이 필요한 웹 스크래핑 을 해보자.
from selenium import webdriver
import time
from openpyxl import Workbook
wb = Workbook(write_only=True)
ws = wb.create_sheet('플레이리스트')
ws.append(['제목', '해시태그', '좋아요 수', '곡 수'])
driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()
driver.get('https://workey.codeit.kr/music')
time.sleep(3)
# 현재 scrollHeight 가져오기
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
# scrollHeight까지 스크롤
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 새로운 내용 로딩될때까지 기다림
time.sleep(0.5)
# 새로운 내용 로딩됐는지 확인
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
play_list = driver.find_elements_by_css_selector('div.playlist__meta')
for playlist in play_list:
title = playlist.find_element_by_css_selector('h3.title').text
hashtag = playlist.find_element_by_css_selector('p.tags').text
like_count = playlist.find_element_by_css_selector('span.data__like-count').text
music_count = playlist.find_element_by_css_selector('span.data__music-count').text
ws.append([title, hashtag, like_count, music_count])
driver.quit()
wb.save('플레이리스트_정보.xlsx')
코드가 길긴 하지만 이전 코드에서 엑셀 파일을 만드는 코드와 이전에 배웠던 css 선택하고 담는 코드가 추가되었던 것 외에는 없다. 하나 주의해야할 건.. find_elements 와 element 를 조심하자. 여러 선택자를 element 로 끌고오면 타입에러가 나는데 발견하기도 힘들다.
코드가 길어지고 복잡해지면 사실 이 두 가지를 결합하는 것이 좋다. 각각의 역할은 다음과 같이 구분한다.
Selenium 은 웹 페이지에 들어가서 동작하는 코드를
BS 는 HTML 파싱하여 필요한 태그들을 가져오고 저장하는 코드를 작성한다.
코드를 결합하면 다음과 같다.
import time
from selenium import webdriver
from openpyxl import Workbook
from bs4 import BeautifulSoup
wb = Workbook(write_only=True)
ws = wb.create_sheet('플레이리스트')
ws.append(['제목', '태그', '좋아요 수', '노래 수'])
driver = webdriver.Chrome()
driver.implicitly_wait(3)
driver.get('https://workey.codeit.kr/music')
time.sleep(1)
# 페이지 끝까지 스크롤
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(0.5)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
### 스크롤 완료 ###
music_page = driver.page_source
driver.quit()
soup = BeautifulSoup(music_page, 'html.parser')
playlists = soup.select('.playlist__meta')
for playlist in playlists:
title = playlist.select_one('h3.title').get_text()
hashtags = playlist.select_one('p.tags').get_text()
like_count = playlist.select_one('span.data__like-count').get_text()
music_count = playlist.select_one('span.data__music-count').get_text()
ws.append([title, hashtags, like_count, music_count])
wb.save('플레이리스트.xlsx')
import time
from selenium import webdriver
from openpyxl import Workbook
wb = Workbook(write_only=True)
ws = wb.create_sheet('코스타그램')
ws.append(['이미지주소', '제목', '해시태그','좋아요 수', '댓글 수'])
# 웹 드라이버 설정
driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.get('https://workey.codeit.kr/costagram/index')
time.sleep(1)
# 로그인
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)
driver.maximize_window()
driver.find_element_by_css_selector('.login-container__login-input').send_keys('codeit')
driver.find_element_by_css_selector('.login-container__password-input').send_keys('datascience')
driver.find_element_by_css_selector('.login-container__login-button').click()
time.sleep(1)
# 페이지 끝까지 스크롤
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(0.5)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
# 모든 썸네일 요소 가져오기
posts = driver.find_elements_by_css_selector('.post-list__post')
image_urls = []
for post in posts:
# 썸네일 클릭
post.click()
time.sleep(0.5)
# 이미지 주소 가져오기
style_attr = driver.find_element_by_css_selector('.post-container__image').get_attribute('style')
image_path = style_attr.split('"')[1]
image_url = 'https://workey.codeit.kr/costagram/index' + image_path
content = driver.find_element_by_css_selector('.content__text').text.strip()
hashtag = driver.find_element_by_css_selector('.content__tag').text.strip()
like_count = driver.find_element_by_css_selector('.content__like-count').text.strip()
comment_count = driver.find_element_by_css_selector('.content__comment-count').text.strip()
ws.append([image_url, content, hashtag, like_count, comment_count])
# 닫기 버튼 클릭
driver.find_element_by_css_selector('.close-btn').click()
time.sleep(0.5)
driver.quit()
wb.save('코스타그램.xlsx')