본문 바로가기
Scraping

[파이썬으로 웹스크래핑] 셀레니움 기능탐구(2) 기다려! 대기하기

by 돈민찌 2021. 8. 11.
반응형

앞서 실행한 스크래핑 코드들을 보면, 프로그래밍한 코드가 동작할 때 사람의 눈보다 훨씬 빠르게 데이터를 읽어오기 때문에, 페이지가 데이터를 불러오기도 전에 당장 눈 앞에 보이는 껍데기 html 구조만 가져오거나 하는 참사가 발생할 수 있었습니다. 혹은 엘리먼트를 선택하려고 하는데 아직 그 엘리먼트가 클릭가능한 상태가 아닌데 클릭하려고 해서 에러가 발생하고 드라이버가 종료될 수도 있지요. 자 이 부분을 어떻게 해결하면 좋을지 고민해 봅시다. 

1. 파이썬 내장 모듈 time 사용하기

가장 간단한(무식한) 방법이지만 성능은 확실한 친구입니다. sleep()이라는 간단한 메소드를 사용할 수 있는데, 괄호 안에 파라미터로 숫자를 넘기면, n초 동안 아무 동작을 하지 않고 기다립니다. 셀레니움 외에도 어떤 코드를 쓸 때도 사용할 수 있는 내장 모듈이라 만만한 녀석입니다. 코드도 간단하고 읽기도 쉽습니다.

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get("   어떤 URL    ")
time.sleep(5)
.
.
.

직관적이죠? 따로 설명할 것도 없겠지만, time.sleep()이 등장하면 다음코드는 어떤 일이 있더라도 n초 후에 실행됩니다. 예를 들어 5초만 기다리게 하더라도, 반복문으로 여러 사이트를 탐색하는 코드에서는 5*n초나 아무것도 실행되지 않는 시간이 생기는 거죠. 조금 비효율적입니다. 

2. selenium의 webdriver가 제공하는 implicitly_wait() 메소드 사용하기 (암시적인 대기)

잘만 쓰면 다른 wait 메소드를 거의 사용하지 않아도 되고 한번 선언하면 webdriver가 동작하는 동안 내내 적용되는 간단한 메소드입니다. time.sleep()처럼 입력한 n초의 시간 동안 기다립니다. 그런데 어떤 웹엘리먼트를 찾으려는데 아직 그 요소가 페이지에 렌더되기 전이거나 할 때, 그것이 가능해질 때까지 최대 n초동안 기다립니다. 물론 n초 내에 조건이 충족되면 바로 넘어가는 거죠. 훨씬 유연하고 다루기 쉬운 것 같죠? 사용법은 역시 간단합니다. 따로 import 할 것도 없습니다.

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(3)

driver.get('https://www.selenium.dev/ko/documentation/webdriver/waits/')
root_element = driver.find_element_by_css_selector('.root')
.
.
.

이렇게 한번만 선언해주면, 드라이버는 n초의 시간만큼의 인내심을 가진 웹드라이버가 됩니다. 그 다음엔 자유롭게 코드를 작성하면 됩니다. 코드를 잘 작성하기만 한다면, implicitly_wait()에 30초 이상을 설정하더라도, 정해진 시간 안에 빠르게 동작합니다. 단, 코드에 실수가 있거나 코드 작성 후 페이지의 구성이 달라지거나 하여 특정 요소를 찾는데 실패한다면, 그 실패를 받아들이는데에도 n초 만큼의 시간이 듭니다. 30초로 설정했다면 "어? 그 요소가 왜 안보이지? (뒤적뒤적)" 하면서 30초 동안 나름의 시도를 해보는 겁니다. 그리고 에러가 발생하겠죠.. 그러니 이미지, 영상 요소 등이 많아 렌더링이 늦은 페이지가 아니라면 너무 긴 시간을 설정하지 않는 것을 추천드립니다. 어차피 못 찾을 거면 시도해보고 빠르게 아 안되는구나 하고 알려주는게 개발자 입장에선 더 편할 수 있으니까요.

 

아직도 코딩 안배웠어? 5만원 깎아줄께 형아만 믿어

 

3. 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

driver = webdriver.Chrome()
wait = WebDriverWait(driver, timeout=5)

# EC, By 없이 사용하는 방법
element1 = wait.until(lambda d: d.find_element_by_tag_name("p"))

# EC, By와 함께 사용하는 방법
element2 = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'div.root')))

# EC: 특정한 조건이 충족될때까지 명시적으로 기다림
## 자주 쓰이는 조건
wait.until(EC.element_to_be_clickable(locator))  # 특정 요소가 클릭 가능한 상태가 될때까지
wait.until(EC.element_attribute_to_include(locator, attribute_))  # 특정 엘리먼트가 특정 속성을 가지게 될 때까지
wait.until(EC.element_to_be_selected(element))  # 특정 엘리먼트가 선택될 때까지
wait.until(EC.visibility_of_element_located(locator))  # 특정 엘리먼트가 표시될 때까지 (높이,너비가 0이면 안됨)
wait.until(EC.visibility_of_any_elements_located(locator))  # 페이지에 특정 엘리먼트들 중 하나라도 표시될때까지
wait.until(EC.visibility_of_all_elements_located(locator))  # 페이지에 특정 엘리먼트들이 모두 표시될때까지
wait.until(EC.url_contains(url))  # 페이지 url이 특정 문자열(url)을 포함할 때까지
wait.until(EC.title_contains(title))  # 페이지의 제목이 특정 문자열(title)을 포함할 때까지

4. FluentWait:: 지금 이해하기엔 너무 고오급..이지만 살짝 맛만 봅시다.

FluentWait 인스턴스는 조건을 대기하는 최대 시간(timeout) 및 조건을 확인하는 빈도(poll_frequency)를 정의합니다. 사용자는 대기 중에 특정 유형의 예외(ignored_exceotions=[]예: NoSuchElement)를 무시하도록 대기를 구성할 수 있습니다.페이지에서 요소를 검색할 때는 예외입니다.

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, ElementNotVisibleException, ElementNotSelectableException

driver = webdriver.Chrome()
driver.get("_____어떤_URL_____")
wait = WebDriverWait(driver, 10, 
               poll_frequency=1, 
               ignored_exceptions=[NoSuchElementException, ElementNotVisibleException, ElementNotSelectableException])
element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))

네 역시 좀 어렵죠? 셀레니움이 특정한 엘리먼트를 찾지 못했을 때 발생하는 에러들을 무시하고, 다시 그 엘리먼트의 상태를 확인하는 빈도를 정할 수 있는 유연한 방법이지만, exceptions에 대한 이해가 필요한 부분이라 지금 단계에서는 이해하기 힘들 것으로 보입니다. 다만 이런 것이 있다는 것만 알아두시면 좋을 것 같네요.

자, 그럼 얼렁뚱땅 셀레니움의 다양한 대기 방법에 대해서 알아보았습니다. 매끄러운 코드 동작을 위해선 이것들을 유연하게 잘 섞어 코드를 작성하는 것이 좋습니다. 일반적으로 time.sleep()는 지양하는 편이고, implicitly_wait()와 WebDriverWait을 함께 쓰는 것 역시 권장하지 않습니다. implicitly_wait을 적당한 시간으로 설정해두고 exceptions 핸들링을 꼼꼼하게 해두거나, 적정하게 explicitly_wait을 적재적소에 배치해서, 예를 들면 어떤 상품에 대한 정보가 로드될 때까지 기다릴때, 상품의 이미지가 가장 늦게 로드된다면, 상품의 이미지가 화면에 visible 상태가 될때까지 기다렸다가 상품의 이름, 가격, 수량 등을 스크랩한다면 에러가 발생할 확률도 적어지고 코드도 깔끔해지겠죠? 

연습을 하다보면 분명히 어느 순간 여러분의 것이 되어있을 것입니다. 그렇게 어려운 개념은 아니니까요. 한번 잘 숙지해보시기를 바랍니다.

반응형

댓글