티스토리 뷰
[Selenium] Selenium으로 scraping할때 time.sleep이 아닌 WebDriverWait를 사용해서 효율적으로 처리하는 방법 (with Python)
progrunman 2022. 1. 18. 23:50개요
python에서 웹크롤링이나 웹스크래핑을 할때 보통 requests, BeautifulSoup, Selenium, Scrapy 등을 사용한다.
최근 웹페이지는 Vue, React 등 CSR(Client Side Rendering)을 사용하는 웹front 기술이 대세이기 때문에
페이지의 script처리 또는 lazy loading되는 리소스들이 완료된 이후 페이지 요소에 접근할 수 있도록
python의 내장함수인 time.sleep()으로 일정시간 대기 후 스크래핑 하도록 처리하기도 한다.
하지만, sleep()을 사용하면 이미 페이지 요소들이 전부 loaded 되었더라도 지정된 시간동안 무조건 대기하기 때문에 반복처리되는 로직에서는 시간효율이 매우 떨어지게 된다.
이때문에 selenium에서는 WebDriverWait라는 클래스를 통해 특정 condition이 만족할때까지 대기할 수 있는 방법을 제공한다.
https://www.selenium.dev/documentation/webdriver/waits/
대기 방식에는 크게 아래와 같이 두가지로 구분된다. (FluentWait의 경우 Explicit wait에 옵션을 조정하는 방법)
Implicit wait
sleep()과 비슷하게 최대 대기시간을 설정하지만, 그전에 condition을 만족할 경우 바로 처리하는 방식이다.
세션당 WebDriver에 한번만 설정해주면 세션이 유지되는동안 모든 처리에 적용된다.
아래 예제에선 DOM에 id가 myDynamicElement인 요소가 포착되면 즉시 해당 요소가 반환된다. 만약 요소가 포착되지 않았다면 설정된 시간(10초)동안 500ms마다 검사를 계속하고, 10초가 되면 예외를 반환한다. (default 폴링 간격인 500ms는 수정 가능)
보통 복잡하지 않은 케이스의 경우 아래 방식으로 처리가 가능하다.
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = Chrome()
driver.implicitly_wait(10)
driver.get("http://somedomain/url_that_delays_loading")
my_dynamic_element = driver.find_element(By.ID, "myDynamicElement")
단점은 세션동안 유지되기 때문에 상황에 따라 sleep처럼 비효율적인 waiting이 생기게 된다.
Explicit wait
Implicit 방식의 단점을 좀 더 세세하게 컨트롤 하기위해 Explicit wait를 사용할 수 있다.
주의할점은 Implicit와 Explicit 방식은 혼합해서 사용될 경우 의도치 않은 waiting이 생길 수 있기 때문에 한가지 방식만 사용하는것이 권장된다는것이다.
Explicit wait 방식은 Selenium에서 기본 제공하는 condition을 사용하거나, lambda식을 통해 직접 condition을 만들어 적용할 수 있다.
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("some_url")
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "some_selector"))
)
아래 람다식과 동일하다.
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("some_url")
element = WebDriverWait(driver, 10).until(
lambda x: x.find_element(By.CSS_SELECTOR, "some_selector")
)
Conditions
- 기본제공 conditions : https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions
- 자주쓰는 conditions
- presence_of_element_located : 해당 요소가 표시되지 않아도 DOM에 존재하면 해당 element를 반환
- visibility_of_element_located : 해당 요소가 표시되면 해당 element를 반환
- text_to_be_present_in_element : 해당 요소에 지정한 텍스트가 존재하는지 확인
- frame_to_be_available_and_switch_to_it : 해당 iFrame이 사용 및 전환 가능한지 확인
- element_to_be_clickable : 해당 요소가 클릭할 수 있는 상태인지 확인
- staleness_of : 해당 요소가 DOM에 연결되지 않을때까지 대기
예시
만약, lazy loading 되는 image의 주소인 "src"를 가져오려는데 base64 인코디드된 backgroud image가 추출된다면 아래와 같이 해당 요소의 src값이 url로 바꼈을때 긁어오는 방법을 생각해 볼 수 있다.
물론 url이 "http"로 시작한다는 보장이 없으니 적절하게 처리할 필요가 있지만 대략적인 흐름은 아래와 같다.
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("some_url")
WebDriverWait(driver, 10).until(
lambda x: x.find_element(By.CSS_SELECTOR, "some_selector").get_attribute("src")[0:4] == "http"
)
element = driver.find_element(By.CSS_SELECTOR, "some_selector")
time.sleep()를 사용해서 처리해도 무방하나 네트워크 속도에 따른 효율적인 처리를 할 수 없다.
Selenium의 wait를 사용하면 네트워크 속도에 맞게 유동적인 스크래핑이나 크롤링이 가능해진다.
- Total
- Today
- Yesterday
- firestore
- Custom Package
- Scraping
- selenium
- .net
- 환경설정
- C#
- initialize
- git
- Debug
- vscode
- coroutine
- logging
- github
- Singleton
- 비동기
- 싱글톤
- Addressables
- 닷넷
- 유니티
- VS2022
- await
- RuntimeInitializeOnLoadMethod
- Python
- Visual Studio Code
- framework
- async
- 코루틴
- gcp
- unity
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |