14. Python Selenium으로 브라우저 자동화하기 — 동적 웹 페이지 크롤링 완벽 가이드

처음에는 requests와 BeautifulSoup 조합만으로도 웬만한 페이지는 다 긁어올 수 있어서 크롤링이 정말 쉽다고 생각했어요. 그러다 어느 날, 평소처럼 코드를 돌렸는데 아무리 해도 빈 화면이나 에러 메시지만 출력되더라고요. 브라우저 창에는 뻔히 보이는 데이터가 왜 안 가져와지나 한참을 헤맸죠. 알고 보니 그 사이트가 리액트로 전면 개편되면서 데이터를 JavaScript로 동적 로딩하게 바뀐 거였습니다. 서버에서 받아온 초기 HTML에는 정말로 아무것도 없었던 거죠. 그때 처음으로 화면을 직접 제어해야겠다는 필요성을 느끼고 Selenium을 공부하기 시작했습니다.

이 글에서는 Selenium이 정확히 어떤 상황에서 필요한지, 설치와 기본 설정 방법, 브라우저를 코드로 제어하는 핵심 명령어, 그리고 실전 자동화 예제까지 단계별로 안내해 드리겠습니다.



Selenium이 필요한 이유 — requests가 못 하는 것

이전 글에서 배운 requests + BeautifulSoup 조합은 서버가 HTML을 통째로 내려주는 정적 페이지에서는 잘 동작합니다. 하지만 현대 웹사이트의 상당수는 다릅니다.

페이지를 열면 처음엔 빈 화면이었다가, JavaScript가 실행되면서 데이터가 채워지는 방식입니다. 로그인 후에만 보이는 페이지, 스크롤을 내려야 나타나는 콘텐츠, 버튼을 클릭해야 펼쳐지는 메뉴 — 이 모든 것이 requests로는 가져올 수 없습니다. HTML을 요청하는 순간엔 아직 데이터가 없기 때문입니다.

비유하자면 이렇습니다. requests는 식당에 전화로 메뉴판을 팩스로 보내달라고 요청하는 것입니다. Selenium은 직접 식당에 찾아가서 자리에 앉고, 메뉴를 펼치고, 음식을 주문하는 것입니다. 실제 손님처럼 브라우저를 조작하기 때문에 JavaScript가 실행된 이후의 완성된 페이지를 그대로 가져올 수 있습니다.

Selenium이 적합한 상황

  • 로그인이 필요한 페이지의 데이터 수집
  • JavaScript로 동적 로딩되는 콘텐츠 (무한 스크롤, 탭 전환 등)
  • 버튼 클릭, 드롭다운 선택 등 사용자 인터랙션이 필요한 경우
  • 웹 기반 반복 업무 자동화 (폼 제출, 파일 다운로드 등)

Selenium 설치 및 환경 설정

라이브러리 설치

pip install selenium

WebDriver 이해하기 — Selenium의 핵심 개념

Selenium은 Python 코드의 명령을 받아 실제 브라우저를 제어합니다. 이때 Python과 브라우저 사이의 통역사 역할을 하는 것이 WebDriver입니다.

과거에는 브라우저 버전에 맞는 WebDriver 파일을 직접 다운로드해서 경로 설정까지 해야 했습니다. 이 과정이 초보자에게 가장 큰 진입 장벽이었습니다. 지금은 selenium 4.6 이상부터 WebDriver Manager가 내장되어 이 과정이 자동화됩니다.

# selenium 최신 버전이면 별도 설치 불필요
pip install selenium --upgrade



브라우저 실행하기 — 첫 번째 자동화

Chrome 브라우저 열기

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 기본 실행 (브라우저 창이 실제로 열림)
driver = webdriver.Chrome()

# 특정 페이지 열기
driver.get("https://www.google.com")

print(f"현재 페이지 제목: {driver.title}")

# 브라우저 종료
driver.quit()

실행하면 Chrome 창이 자동으로 열리고 구글 홈페이지로 이동합니다. 마치 누군가 키보드 없이 마우스만으로 브라우저를 조작하는 것처럼 보입니다.


Headless 모드 — 창 없이 백그라운드 실행

서버나 자동화 배치 작업에서는 브라우저 창이 불필요합니다. Headless 모드를 사용하면 화면 없이 백그라운드에서 동작합니다.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")          # 창 없이 실행
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")

driver = webdriver.Chrome(options=options)
driver.get("https://www.google.com")
print(driver.title)
driver.quit()

핵심 명령어 — 페이지 요소 찾고 조작하기

Selenium에서 가장 많이 쓰는 작업은 요소를 찾고(find) → 조작하는(action) 두 단계입니다.

요소 찾기 — find_element

from selenium.webdriver.common.by import By

# ID로 찾기
element = driver.find_element(By.ID, "username")

# CSS 선택자로 찾기 (가장 범용적)
element = driver.find_element(By.CSS_SELECTOR, "input.search-box")

# XPath로 찾기 (복잡한 구조에서 유용)
element = driver.find_element(By.XPATH, "//button[@type='submit']")

# 여러 요소를 리스트로 찾기
elements = driver.find_elements(By.CSS_SELECTOR, "li.item")

💡 어떤 선택자를 써야 할까: ID가 있으면 By.ID가 가장 빠릅니다. 없다면 By.CSS_SELECTOR를 기본으로 사용하고, CSS로 접근이 어려운 복잡한 구조에서만 By.XPATH를 사용하는 것을 권장합니다.

요소 조작하기 — 클릭, 입력, 텍스트 읽기

# 텍스트 입력
input_box = driver.find_element(By.NAME, "q")
input_box.send_keys("Python 자동화")

# 버튼 클릭
button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
button.click()

# 텍스트 읽기
heading = driver.find_element(By.TAG_NAME, "h1")
print(heading.text)

# 속성값 읽기
link = driver.find_element(By.CSS_SELECTOR, "a.result-link")
print(link.get_attribute("href"))



대기(Wait) 처리 — 가장 중요한 개념

Selenium 초보자가 가장 많이 겪는 오류가 NoSuchElementException입니다. 코드가 너무 빠르게 실행되어, 브라우저가 페이지를 아직 다 불러오기 전에 요소를 찾으려 해서 발생하는 오류입니다.

비유하자면, 택배가 아직 도착하지 않았는데 문을 열어보는 것과 같습니다. 기다리는 로직이 반드시 필요합니다.

명시적 대기 (권장) — WebDriverWait

특정 요소가 나타날 때까지 최대 N초 동안 기다리는 방식입니다. 가장 안정적이고 실무에서 권장되는 방법입니다.

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

wait = WebDriverWait(driver, 10)  # 최대 10초 대기

# 요소가 나타날 때까지 대기 후 클릭
element = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".result-item"))
)
print(element.text)

암묵적 대기 (간단한 경우)

driver.implicitly_wait(10)  # 모든 find 요청에 최대 10초 대기 적용

⚠️ 명시적 대기와 암묵적 대기를 혼용하면 예측하기 어려운 오류가 발생합니다. 하나만 선택해서 일관되게 사용하십시오.


실전 예제 1 — 구글 검색 자동화

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time

driver = webdriver.Chrome()
driver.get("https://www.google.com")

wait = WebDriverWait(driver, 10)

# 검색창 찾아서 키워드 입력
search_box = wait.until(
    EC.presence_of_element_located((By.NAME, "q"))
)
search_box.send_keys("Python 웹 자동화")
search_box.send_keys(Keys.RETURN)  # 엔터키

# 결과 로딩 대기
time.sleep(2)

# 검색 결과 제목 수집
results = driver.find_elements(By.CSS_SELECTOR, "h3")
for idx, result in enumerate(results[:5], 1):
    print(f"{idx}. {result.text}")

driver.quit()



실전 예제 2 — 스크롤 내려 동적 콘텐츠 로딩

무한 스크롤 페이지에서 콘텐츠를 수집할 때 사용하는 패턴입니다.

import time

driver.get("https://example-infinite-scroll.com")

# 3번 스크롤 내리기
for _ in range(3):
    # 페이지 맨 아래로 스크롤
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(2)  # 콘텐츠 로딩 대기

# 스크롤 후 로딩된 요소 수집
items = driver.find_elements(By.CSS_SELECTOR, ".feed-item")
print(f"수집된 항목 수: {len(items)}")

실전 예제 3 — 로그인 자동화

from selenium import webdriver
from selenium.webdriver.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://example.com/login")

wait = WebDriverWait(driver, 10)

# 아이디 입력
id_field = wait.until(EC.presence_of_element_located((By.ID, "username")))
id_field.send_keys("my_username")

# 비밀번호 입력
pw_field = driver.find_element(By.ID, "password")
pw_field.send_keys("my_password")

# 로그인 버튼 클릭
login_btn = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
login_btn.click()

# 로그인 후 페이지 확인
wait.until(EC.url_contains("/dashboard"))
print(f"로그인 성공. 현재 URL: {driver.current_url}")

⚠️ 주의: 계정 정보를 코드에 직접 하드코딩하지 마십시오. .env 파일이나 환경 변수로 관리하는 것을 권장합니다.


자주 발생하는 오류와 해결법

오류 1 — NoSuchElementException

원인: 요소가 아직 로딩되지 않았거나 선택자가 잘못됨
해결: WebDriverWait으로 명시적 대기 추가, 브라우저 개발자도구(F12)로 선택자 재확인

오류 2 — StaleElementReferenceException

원인: 페이지가 새로 고침되거나 DOM이 변경된 후 기존에 찾은 요소를 다시 사용
해결: 요소를 사용 직전에 다시 find_element()로 재탐색

오류 3 — WebDriverException: Chrome not reachable

원인: Chrome 버전과 ChromeDriver 버전 불일치
해결: pip install --upgrade selenium으로 최신 버전 업그레이드. Selenium 4.6 이상은 자동으로 호환 드라이버를 설치합니다.

오류 4 — 요소는 있는데 클릭이 안 되는 경우

원인: 요소가 화면 밖에 있거나 다른 요소에 가려진 경우
해결: JavaScript로 스크롤 후 클릭

driver.execute_script("arguments[0].scrollIntoView();", element)
element.click()

마무리 — 핵심 요약

✅ Python Selenium 자동화 체크리스트

  1. JavaScript 동적 로딩 페이지에는 Selenium을 사용합니다
  2. webdriver.Chrome()으로 브라우저를 실행합니다
  3. 요소는 By.CSS_SELECTOR를 기본 선택자로 사용합니다
  4. WebDriverWait으로 명시적 대기를 반드시 적용합니다time.sleep()만 의존하면 불안정합니다
  5. 작업 완료 후 driver.quit()으로 브라우저를 종료합니다
  6. 계정 정보는 코드에 직접 쓰지 않고 환경 변수로 관리합니다

다음으로 알아두면 좋은 것

Selenium에 익숙해졌다면 Playwright를 살펴보시길 권장합니다. Microsoft가 만든 Playwright는 Selenium보다 속도가 빠르고, 비동기 처리를 기본 지원하며, 특히 최신 SPA(Single Page Application) 환경에서 더 안정적으로 작동합니다. 두 도구를 모두 다룰 수 있으면 어떤 웹 자동화 상황에서도 적절한 도구를 선택할 수 있게 됩니다.



댓글

이 블로그의 인기 게시물

1.Python 설치부터 실행까지 10분 만에 끝내기 — 초보자도 바로 따라 하는 완벽 가이드

30.Python pyautogui로 마우스와 키보드 자동화하기: VSCode 실전 가이드

29.Python subprocess로 외부 명령어 실행하기: VSCode 실전 활용법