Python Selenium 튜토리얼 웹 브라우저 자동화 완벽 가이드

Python Selenium 튜토리얼: 웹 브라우저 자동화 완벽 가이드

Python Selenium 튜토리얼: 웹 브라우저 자동화 완벽 가이드

Selenium 소개

Selenium은 웹 브라우저를 자동화하기 위한 강력한 도구입니다. 웹 애플리케이션 테스트, 웹 스크래핑, 반복적인 웹 작업 자동화 등 다양한 용도로 활용됩니다. Python과 함께 사용하면 간단한 코드로 복잡한 브라우저 작업을 자동화할 수 있어, 개발자와 테스터들에게 필수적인 도구로 자리잡았습니다.

설치 및 환경 설정

Selenium을 사용하기 위해서는 Python 패키지와 웹 드라이버가 필요합니다. 최신 버전의 Selenium은 드라이버를 자동으로 관리해주는 기능을 제공합니다.

# Selenium 설치
pip install selenium

# 웹드라이버 자동 관리를 위한 webdriver-manager 설치 (선택사항)
pip install webdriver-manager

1. 기본 브라우저 제어

브라우저 시작과 종료

Selenium으로 브라우저를 제어하는 가장 기본적인 작업부터 시작해보겠습니다. Chrome, Firefox, Edge 등 다양한 브라우저를 지원합니다.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

# 방법 1: webdriver-manager를 사용한 자동 드라이버 관리
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

# 방법 2: Selenium 4.6+ 버전의 자동 드라이버 관리
driver = webdriver.Chrome()  # 자동으로 드라이버 다운로드 및 관리

# 웹페이지 열기
driver.get("https://www.google.com")
print(f"현재 페이지 제목: {driver.title}")
print(f"현재 URL: {driver.current_url}")

# 브라우저 크기 조절
driver.set_window_size(1280, 720)
time.sleep(2)

# 브라우저 최대화
driver.maximize_window()
time.sleep(2)

# 페이지 새로고침
driver.refresh()

# 뒤로가기, 앞으로가기
driver.get("https://www.naver.com")
driver.back()  # 뒤로가기
time.sleep(1)
driver.forward()  # 앞으로가기

# 브라우저 종료
driver.quit()  # 모든 창 닫기
# driver.close()  # 현재 창만 닫기

헤드리스 모드와 옵션 설정

브라우저를 화면에 표시하지 않고 백그라운드에서 실행하거나, 다양한 옵션을 설정할 수 있습니다.

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

# Chrome 옵션 설정
chrome_options = Options()

# 헤드리스 모드 (브라우저 창을 표시하지 않음)
chrome_options.add_argument('--headless')

# 브라우저 창 크기 설정
chrome_options.add_argument('--window-size=1920,1080')

# GPU 가속 비활성화 (일부 환경에서 필요)
chrome_options.add_argument('--disable-gpu')

# 샌드박스 비활성화 (Linux 환경에서 필요할 수 있음)
chrome_options.add_argument('--no-sandbox')

# 개발자 도구 자동 열기
chrome_options.add_argument('--auto-open-devtools-for-tabs')

# 시크릿 모드
chrome_options.add_argument('--incognito')

# 알림 비활성화
prefs = {"profile.default_content_setting_values.notifications": 2}
chrome_options.add_experimental_option("prefs", prefs)

# User-Agent 변경
chrome_options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')

# 옵션을 적용하여 드라이버 생성
driver = webdriver.Chrome(options=chrome_options)

driver.get("https://www.example.com")
print(f"페이지 로드 완료: {driver.title}")

driver.quit()

2. 웹 요소 찾기와 선택

웹 페이지의 요소를 찾는 것은 자동화의 핵심입니다. Selenium은 다양한 방법으로 요소를 찾을 수 있습니다.

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://www.example.com")

# 단일 요소 찾기 - find_element
# ID로 찾기
element = driver.find_element(By.ID, "element-id")

# 클래스명으로 찾기
element = driver.find_element(By.CLASS_NAME, "class-name")

# 이름으로 찾기
element = driver.find_element(By.NAME, "element-name")

# 태그명으로 찾기
element = driver.find_element(By.TAG_NAME, "div")

# 링크 텍스트로 찾기
element = driver.find_element(By.LINK_TEXT, "클릭하세요")

# 부분 링크 텍스트로 찾기
element = driver.find_element(By.PARTIAL_LINK_TEXT, "클릭")

# CSS 선택자로 찾기
element = driver.find_element(By.CSS_SELECTOR, "div.class-name #id-name")

# XPath로 찾기
element = driver.find_element(By.XPATH, "//div[@class='example']//span[text()='텍스트']")

# 여러 요소 찾기 - find_elements
elements = driver.find_elements(By.CLASS_NAME, "item")
print(f"찾은 요소 개수: {len(elements)}")

# 요소 순회하기
for element in elements:
    print(element.text)

# 중첩된 요소 찾기
parent = driver.find_element(By.ID, "parent-id")
child = parent.find_element(By.CLASS_NAME, "child-class")

driver.quit()

XPath와 CSS Selector 활용

복잡한 요소를 찾을 때는 XPath나 CSS Selector를 활용하면 효과적입니다.

from selenium import webdriver
from selenium.webdriver.common.by import By

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

# XPath 예제
# 속성으로 찾기
element = driver.find_element(By.XPATH, "//input[@type='text' and @name='username']")

# 텍스트 내용으로 찾기
element = driver.find_element(By.XPATH, "//button[contains(text(), '로그인')]")

# 부모/자식 관계
element = driver.find_element(By.XPATH, "//div[@class='container']//span")

# n번째 요소
element = driver.find_element(By.XPATH, "(//div[@class='item'])[3]")

# following-sibling, preceding-sibling
element = driver.find_element(By.XPATH, "//label[text()='이메일']/following-sibling::input")

# CSS Selector 예제
# 클래스와 ID 조합
element = driver.find_element(By.CSS_SELECTOR, "#container .item")

# 속성 선택자
element = driver.find_element(By.CSS_SELECTOR, "input[type='password']")

# 의사 클래스
element = driver.find_element(By.CSS_SELECTOR, "li:first-child")
element = driver.find_element(By.CSS_SELECTOR, "li:nth-child(3)")

# 자손 선택자
element = driver.find_element(By.CSS_SELECTOR, "div > p")  # 직계 자식
element = driver.find_element(By.CSS_SELECTOR, "div p")    # 모든 자손

driver.quit()

3. 요소와 상호작용

찾은 요소와 다양한 방식으로 상호작용할 수 있습니다. 클릭, 텍스트 입력, 선택 등의 작업을 수행할 수 있습니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.action_chains import ActionChains
import time

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

# 텍스트 입력
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Selenium Python")
time.sleep(1)

# 텍스트 지우기
search_box.clear()
search_box.send_keys("웹 자동화")

# 특수 키 입력
search_box.send_keys(Keys.ENTER)  # Enter 키
# search_box.send_keys(Keys.TAB)  # Tab 키
# search_box.send_keys(Keys.ESCAPE)  # ESC 키

# Ctrl+A, Ctrl+C, Ctrl+V 등 조합키
search_box.send_keys(Keys.CONTROL, 'a')  # 전체 선택
search_box.send_keys(Keys.CONTROL, 'c')  # 복사

time.sleep(2)

# 클릭 작업
driver.get("https://www.example.com")
button = driver.find_element(By.TAG_NAME, "button")
button.click()

# 드롭다운 선택 (Select 요소)
# HTML: <select id="dropdown"><option value="1">옵션1</option>...</select>
try:
    dropdown = Select(driver.find_element(By.ID, "dropdown"))
    dropdown.select_by_value("1")  # value로 선택
    dropdown.select_by_index(0)  # 인덱스로 선택
    dropdown.select_by_visible_text("옵션1")  # 보이는 텍스트로 선택
except:
    pass

# 체크박스와 라디오 버튼
try:
    checkbox = driver.find_element(By.CSS_SELECTOR, "input[type='checkbox']")
    if not checkbox.is_selected():
        checkbox.click()
except:
    pass

# 마우스 액션 (ActionChains)
action = ActionChains(driver)

# 마우스 호버
try:
    menu = driver.find_element(By.CLASS_NAME, "menu")
    action.move_to_element(menu).perform()
except:
    pass

# 드래그 앤 드롭
try:
    source = driver.find_element(By.ID, "source")
    target = driver.find_element(By.ID, "target")
    action.drag_and_drop(source, target).perform()
except:
    pass

# 더블 클릭
try:
    element = driver.find_element(By.ID, "double-click")
    action.double_click(element).perform()
except:
    pass

# 우클릭
try:
    element = driver.find_element(By.ID, "right-click")
    action.context_click(element).perform()
except:
    pass

driver.quit()

4. 대기 전략 (Wait Strategies)

웹 페이지의 요소가 로드될 때까지 기다리는 것은 안정적인 자동화를 위해 매우 중요합니다. Selenium은 명시적 대기와 암시적 대기를 제공합니다.

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
from selenium.common.exceptions import TimeoutException
import time

driver = webdriver.Chrome()

# 암시적 대기 (Implicit Wait)
# 모든 요소에 대해 최대 10초까지 대기
driver.implicitly_wait(10)

driver.get("https://www.example.com")

# 명시적 대기 (Explicit Wait)
# 특정 조건이 만족될 때까지 대기
wait = WebDriverWait(driver, 10)

try:
    # 요소가 나타날 때까지 대기
    element = wait.until(
        EC.presence_of_element_located((By.ID, "dynamic-element"))
    )
    
    # 요소가 클릭 가능할 때까지 대기
    clickable = wait.until(
        EC.element_to_be_clickable((By.BUTTON, "submit-button"))
    )
    
    # 요소가 보일 때까지 대기
    visible = wait.until(
        EC.visibility_of_element_located((By.CLASS_NAME, "popup"))
    )
    
    # 텍스트가 포함될 때까지 대기
    text_present = wait.until(
        EC.text_to_be_present_in_element((By.ID, "status"), "완료")
    )
    
    # 특정 값이 속성에 포함될 때까지 대기
    attribute_contains = wait.until(
        EC.text_to_be_present_in_element_attribute(
            (By.ID, "input-field"), "value", "expected"
        )
    )
    
    # 프레임으로 전환 가능할 때까지 대기
    frame_available = wait.until(
        EC.frame_to_be_available_and_switch_to_it((By.ID, "iframe-id"))
    )
    
    # 알림창이 나타날 때까지 대기
    alert_present = wait.until(EC.alert_is_present())
    
    # 요소가 사라질 때까지 대기
    invisible = wait.until(
        EC.invisibility_of_element_located((By.ID, "loading"))
    )
    
    # 여러 요소가 모두 나타날 때까지 대기
    all_elements = wait.until(
        EC.presence_of_all_elements_located((By.CLASS_NAME, "item"))
    )
    
except TimeoutException:
    print("요소를 찾을 수 없습니다. 시간 초과!")

# 사용자 정의 대기 조건
class element_has_css_class:
    def __init__(self, locator, css_class):
        self.locator = locator
        self.css_class = css_class
    
    def __call__(self, driver):
        element = driver.find_element(*self.locator)
        if self.css_class in element.get_attribute("class"):
            return element
        else:
            return False

# 사용자 정의 조건 사용
try:
    element = wait.until(
        element_has_css_class((By.ID, "my-element"), "active")
    )
except:
    pass

# Fluent Wait (폴링 간격 조정)
from selenium.webdriver.support.ui import WebDriverWait

wait_fluent = WebDriverWait(driver, timeout=10, poll_frequency=0.5)
try:
    element = wait_fluent.until(
        EC.presence_of_element_located((By.ID, "slow-loading"))
    )
except:
    pass

driver.quit()

5. JavaScript 실행

Selenium을 통해 JavaScript 코드를 직접 실행할 수 있습니다. 이는 복잡한 작업을 수행하거나 일반적인 방법으로 접근하기 어려운 요소를 다룰 때 유용합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

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

# JavaScript 실행 - execute_script
# 페이지 스크롤
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1)

# 특정 위치로 스크롤
driver.execute_script("window.scrollTo(0, 500);")

# 요소로 스크롤
try:
    element = driver.find_element(By.ID, "footer")
    driver.execute_script("arguments[0].scrollIntoView(true);", element)
except:
    pass

# 요소의 속성 변경
try:
    element = driver.find_element(By.ID, "hidden-input")
    driver.execute_script("arguments[0].style.display='block';", element)
    driver.execute_script("arguments[0].value='새로운 값';", element)
except:
    pass

# JavaScript로 클릭 (일반 클릭이 안 될 때)
try:
    button = driver.find_element(By.ID, "difficult-button")
    driver.execute_script("arguments[0].click();", button)
except:
    pass

# 새 탭 열기
driver.execute_script("window.open('https://www.google.com','_blank');")

# localStorage 조작
driver.execute_script("localStorage.setItem('key', 'value');")
value = driver.execute_script("return localStorage.getItem('key');")
print(f"localStorage 값: {value}")

# 페이지 정보 가져오기
title = driver.execute_script("return document.title;")
url = driver.execute_script("return window.location.href;")
ready_state = driver.execute_script("return document.readyState;")

print(f"제목: {title}")
print(f"URL: {url}")
print(f"페이지 상태: {ready_state}")

# 여러 인자 전달
try:
    element1 = driver.find_element(By.ID, "elem1")
    element2 = driver.find_element(By.ID, "elem2")
    driver.execute_script(
        "arguments[0].style.backgroundColor = 'yellow'; arguments[1].style.border = '2px solid red';",
        element1, element2
    )
except:
    pass

# 비동기 JavaScript 실행
result = driver.execute_async_script("""
    var callback = arguments[arguments.length - 1];
    setTimeout(function() {
        callback('비동기 작업 완료');
    }, 2000);
""")
print(f"비동기 결과: {result}")

driver.quit()

6. 창과 탭 관리

여러 창이나 탭을 다루는 것은 복잡한 웹 애플리케이션을 자동화할 때 필수적입니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

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

# 현재 창 핸들 저장
main_window = driver.current_window_handle
print(f"메인 창 핸들: {main_window}")

# 새 탭 열기 (Selenium 4+)
driver.switch_to.new_window('tab')
driver.get("https://www.naver.com")
time.sleep(2)

# 새 창 열기 (Selenium 4+)
driver.switch_to.new_window('window')
driver.get("https://www.github.com")
time.sleep(2)

# 모든 창 핸들 가져오기
all_windows = driver.window_handles
print(f"열린 창 개수: {len(all_windows)}")

# 창 간 전환
for window in all_windows:
    driver.switch_to.window(window)
    print(f"현재 창 제목: {driver.title}")
    time.sleep(1)

# 메인 창으로 돌아가기
driver.switch_to.window(main_window)
print(f"메인 창으로 돌아옴: {driver.title}")

# 특정 창 닫기
for window in all_windows:
    if window != main_window:
        driver.switch_to.window(window)
        driver.close()

# 메인 창으로 다시 전환
driver.switch_to.window(main_window)

# JavaScript로 새 탭 열기 (대체 방법)
driver.execute_script("window.open('about:blank','_blank');")
driver.switch_to.window(driver.window_handles[-1])
driver.get("https://www.stackoverflow.com")

# 창 크기와 위치 조절
driver.set_window_position(0, 0)
driver.set_window_size(800, 600)

# 창 크기 가져오기
size = driver.get_window_size()
print(f"창 크기: {size['width']} x {size['height']}")

# 창 위치 가져오기
position = driver.get_window_position()
print(f"창 위치: ({position['x']}, {position['y']})")

driver.quit()

7. 프레임과 iframe 처리

웹 페이지에 포함된 프레임이나 iframe 내부의 요소에 접근하려면 먼저 해당 프레임으로 전환해야 합니다.

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://www.example.com/page-with-iframe")

# iframe으로 전환 - 인덱스 사용
driver.switch_to.frame(0)  # 첫 번째 프레임

# iframe으로 전환 - name 또는 id 사용
driver.switch_to.frame("iframe-name")

# iframe으로 전환 - WebElement 사용
iframe_element = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe_element)

# iframe 내부에서 작업
try:
    element_in_iframe = driver.find_element(By.ID, "element-in-iframe")
    element_in_iframe.click()
except:
    print("iframe 내부 요소를 찾을 수 없습니다")

# 부모 프레임으로 돌아가기
driver.switch_to.parent_frame()

# 메인 문서로 돌아가기
driver.switch_to.default_content()

# 중첩된 iframe 처리
try:
    # 첫 번째 iframe으로 전환
    driver.switch_to.frame("outer-frame")
    
    # 내부 iframe으로 전환
    driver.switch_to.frame("inner-frame")
    
    # 작업 수행
    element = driver.find_element(By.ID, "nested-element")
    element.click()
    
    # 메인으로 돌아가기
    driver.switch_to.default_content()
except:
    pass

# WebDriverWait를 사용한 iframe 대기
wait = WebDriverWait(driver, 10)
try:
    wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, "dynamic-iframe")))
    # iframe 내부 작업
    element = driver.find_element(By.CLASS_NAME, "iframe-content")
    print(element.text)
    driver.switch_to.default_content()
except:
    print("iframe을 찾을 수 없습니다")

driver.quit()

8. 알림창과 팝업 처리

JavaScript 알림창(alert, confirm, prompt)을 처리하는 방법입니다.

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
from selenium.common.exceptions import NoAlertPresentException
import time

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

# Alert 처리
try:
    # Alert 생성 (테스트용)
    driver.execute_script("alert('이것은 알림입니다');")
    time.sleep(1)
    
    # Alert로 전환
    alert = driver.switch_to.alert
    
    # Alert 텍스트 읽기
    alert_text = alert.text
    print(f"Alert 메시지: {alert_text}")
    
    # Alert 수락 (확인 버튼)
    alert.accept()
    
except NoAlertPresentException:
    print("Alert가 없습니다")

# Confirm 처리
try:
    driver.execute_script("confirm('계속하시겠습니까?');")
    time.sleep(1)
    
    confirm = driver.switch_to.alert
    
    # 취소 버튼 클릭
    confirm.dismiss()
    # 또는 확인 버튼: confirm.accept()
    
except:
    pass

# Prompt 처리
try:
    driver.execute_script("prompt('이름을 입력하세요:');")
    time.sleep(1)
    
    prompt = driver.switch_to.alert
    
    # 텍스트 입력
    prompt.send_keys("홍길동")
    
    # 확인 버튼
    prompt.accept()
    
except:
    pass

# WebDriverWait를 사용한 Alert 대기
def trigger_and_handle_alert():
    try:
        # Alert 트리거 (예시)
        button = driver.find_element(By.ID, "alert-button")
        button.click()
        
        # Alert 대기
        wait = WebDriverWait(driver, 10)
        alert = wait.until(EC.alert_is_present())
        
        # Alert 처리
        alert_text = alert.text
        print(f"Alert 내용: {alert_text}")
        alert.accept()
        
        return True
    except:
        return False

# 인증 팝업 처리 (Basic Authentication)
# URL에 자격 증명 포함
username = "user"
password = "pass"
driver.get(f"https://{username}:{password}@www.example.com/secure")

# 브라우저 알림 권한 팝업 비활성화
chrome_options = webdriver.ChromeOptions()
prefs = {"profile.default_content_setting_values.notifications": 2}
chrome_options.add_experimental_option("prefs", prefs)

driver.quit()

9. 스크린샷과 페이지 소스

디버깅이나 증거 수집을 위해 스크린샷을 캡처하고 페이지 소스를 저장할 수 있습니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from datetime import datetime
import os

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

# 전체 페이지 스크린샷
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = f"screenshot_{timestamp}.png"
driver.save_screenshot(screenshot_path)
print(f"스크린샷 저장: {screenshot_path}")

# 스크린샷을 바이트로 가져오기
screenshot_bytes = driver.get_screenshot_as_png()
with open("screenshot_bytes.png", "wb") as f:
    f.write(screenshot_bytes)

# Base64 인코딩된 스크린샷
screenshot_base64 = driver.get_screenshot_as_base64()

# 특정 요소의 스크린샷 (Selenium 4+)
try:
    element = driver.find_element(By.TAG_NAME, "h1")
    element.screenshot("element_screenshot.png")
except:
    print("요소 스크린샷 실패")

# 페이지 소스 가져오기
page_source = driver.page_source
with open("page_source.html", "w", encoding="utf-8") as f:
    f.write(page_source)

# 특정 요소의 HTML 가져오기
try:
    element = driver.find_element(By.CLASS_NAME, "content")
    element_html = element.get_attribute("outerHTML")
    element_text = element.text
    
    print("요소 HTML:", element_html[:100])
    print("요소 텍스트:", element_text[:100])
except:
    pass

# 전체 페이지 스크린샷 (스크롤 포함)
def full_page_screenshot(driver, file_name):
    # 페이지 전체 높이 가져오기
    total_height = driver.execute_script("return document.body.scrollHeight")
    viewport_height = driver.execute_script("return window.innerHeight")
    
    # 원래 위치 저장
    driver.execute_script("window.scrollTo(0, 0)")
    
    screenshots = []
    for i in range(0, total_height, viewport_height):
        driver.execute_script(f"window.scrollTo(0, {i})")
        time.sleep(0.5)
        screenshot = driver.get_screenshot_as_png()
        screenshots.append(screenshot)
    
    # 이미지 합치기 (PIL 필요)
    try:
        from PIL import Image
        import io
        
        images = [Image.open(io.BytesIO(ss)) for ss in screenshots]
        total_width = images[0].width
        total_height = sum(img.height for img in images)
        
        stitched = Image.new('RGB', (total_width, total_height))
        y_offset = 0
        for img in images:
            stitched.paste(img, (0, y_offset))
            y_offset += img.height
        
        stitched.save(file_name)
        print(f"전체 페이지 스크린샷 저장: {file_name}")
    except ImportError:
        print("PIL이 설치되어 있지 않습니다")

# 에러 발생 시 스크린샷 저장
def safe_click(driver, locator):
    try:
        element = driver.find_element(*locator)
        element.click()
    except Exception as e:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        driver.save_screenshot(f"error_{timestamp}.png")
        print(f"에러 발생: {e}")
        print(f"스크린샷 저장: error_{timestamp}.png")

driver.quit()

쿠키를 관리하여 로그인 상태를 유지하거나 세션을 재사용할 수 있습니다.

from selenium import webdriver
import pickle
import json

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

# 모든 쿠키 가져오기
cookies = driver.get_cookies()
print(f"쿠키 개수: {len(cookies)}")

for cookie in cookies:
    print(f"쿠키: {cookie['name']} = {cookie['value']}")

# 특정 쿠키 가져오기
session_cookie = driver.get_cookie("session_id")
if session_cookie:
    print(f"세션 쿠키: {session_cookie}")

# 새 쿠키 추가
driver.add_cookie({
    'name': 'custom_cookie',
    'value': 'test_value',
    'domain': '.example.com',
    'path': '/',
    'secure': False
})

# 쿠키 삭제
driver.delete_cookie("cookie_name")

# 모든 쿠키 삭제
driver.delete_all_cookies()

# 쿠키 저장 (pickle)
def save_cookies_pickle(driver, file_path):
    with open(file_path, 'wb') as file:
        pickle.dump(driver.get_cookies(), file)
    print(f"쿠키 저장 완료: {file_path}")

# 쿠키 불러오기 (pickle)
def load_cookies_pickle(driver, file_path):
    with open(file_path, 'rb') as file:
        cookies = pickle.load(file)
        for cookie in cookies:
            driver.add_cookie(cookie)
    print(f"쿠키 로드 완료: {file_path}")

# 쿠키 저장 (JSON)
def save_cookies_json(driver, file_path):
    with open(file_path, 'w') as file:
        json.dump(driver.get_cookies(), file, indent=2)
    print(f"쿠키 저장 완료 (JSON): {file_path}")

# 쿠키 불러오기 (JSON)
def load_cookies_json(driver, file_path):
    with open(file_path, 'r') as file:
        cookies = json.load(file)
        for cookie in cookies:
            driver.add_cookie(cookie)
    print(f"쿠키 로드 완료 (JSON): {file_path}")

# 로그인 상태 유지 예제
def login_and_save_session(driver):
    driver.get("https://www.example.com/login")
    
    # 로그인 수행 (예시)
    # username_input = driver.find_element(By.ID, "username")
    # password_input = driver.find_element(By.ID, "password")
    # username_input.send_keys("user")
    # password_input.send_keys("pass")
    # login_button = driver.find_element(By.ID, "login-btn")
    # login_button.click()
    
    # 로그인 후 쿠키 저장
    save_cookies_json(driver, "session_cookies.json")

def restore_session(driver):
    driver.get("https://www.example.com")
    
    # 저장된 쿠키 로드
    try:
        load_cookies_json(driver, "session_cookies.json")
        driver.refresh()  # 쿠키 적용을 위해 새로고침
        print("세션 복원 완료")
    except FileNotFoundError:
        print("저장된 세션이 없습니다")

# localStorage와 sessionStorage 조작
# localStorage 설정
driver.execute_script("window.localStorage.setItem('key', 'value');")

# localStorage 가져오기
value = driver.execute_script("return window.localStorage.getItem('key');")
print(f"localStorage 값: {value}")

# localStorage 모두 가져오기
all_storage = driver.execute_script("return Object.entries(window.localStorage);")
print(f"모든 localStorage: {all_storage}")

# localStorage 삭제
driver.execute_script("window.localStorage.removeItem('key');")
driver.execute_script("window.localStorage.clear();")

driver.quit()

11. 고급 기능과 최적화

성능 최적화와 고급 기능들을 활용하여 더 효율적인 자동화를 구현할 수 있습니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.proxy import Proxy, ProxyType
import time

# 페이지 로드 전략 설정
caps = DesiredCapabilities.CHROME
caps['pageLoadStrategy'] = 'eager'  # normal, eager, none

options = webdriver.ChromeOptions()
options.page_load_strategy = 'eager'

# 네트워크 조건 설정 (Chrome DevTools Protocol)
driver = webdriver.Chrome(options=options)

# 네트워크 속도 제한
driver.execute_cdp_cmd('Network.emulateNetworkConditions', {
    'offline': False,
    'downloadThroughput': 1.5 * 1024 * 1024 / 8,  # 1.5 Mbps
    'uploadThroughput': 750 * 1024 / 8,         # 750 Kbps
    'latency': 40  # 40ms 지연
})

# 모바일 에뮬레이션
mobile_emulation = {
    "deviceName": "iPhone X"
    # 또는 커스텀 설정
    # "deviceMetrics": {"width": 360, "height": 640, "pixelRatio": 3.0},
    # "userAgent": "Mozilla/5.0 (Linux; Android 4.2.1; ..."
}
options.add_experimental_option("mobileEmulation", mobile_emulation)

# 프록시 설정
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "proxy_address:port"
proxy.ssl_proxy = "proxy_address:port"

capabilities = webdriver.DesiredCapabilities.CHROME
proxy.add_to_capabilities(capabilities)

# 다운로드 디렉토리 설정
prefs = {
    "download.default_directory": "/path/to/download",
    "download.prompt_for_download": False,
    "download.directory_upgrade": True,
    "safebrowsing.enabled": True
}
options.add_experimental_option("prefs", prefs)

# 성능 로그 활성화
caps['goog:loggingPrefs'] = {'performance': 'ALL'}

# 확장 프로그램 추가
# options.add_extension('/path/to/extension.crx')

# 브라우저 로그 수집
def get_browser_logs(driver):
    logs = driver.get_log('browser')
    for log in logs:
        print(f"[{log['level']}] {log['message']}")

# 성능 메트릭 수집
def get_performance_metrics(driver):
    navigation_start = driver.execute_script(
        "return window.performance.timing.navigationStart"
    )
    response_start = driver.execute_script(
        "return window.performance.timing.responseStart"
    )
    dom_complete = driver.execute_script(
        "return window.performance.timing.domComplete"
    )
    
    backend_time = response_start - navigation_start
    frontend_time = dom_complete - response_start
    
    print(f"백엔드 처리 시간: {backend_time}ms")
    print(f"프론트엔드 렌더링 시간: {frontend_time}ms")

# 병렬 실행을 위한 멀티 스레딩
from concurrent.futures import ThreadPoolExecutor
from threading import Lock

print_lock = Lock()

def scrape_page(url):
    driver = webdriver.Chrome()
    try:
        driver.get(url)
        title = driver.title
        with print_lock:
            print(f"{url}: {title}")
    finally:
        driver.quit()

urls = ["https://www.google.com", "https://www.github.com", "https://www.stackoverflow.com"]

with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(scrape_page, urls)

# 재시도 로직
from selenium.common.exceptions import WebDriverException
import time

def retry_on_failure(func, max_retries=3, delay=2):
    for attempt in range(max_retries):
        try:
            return func()
        except WebDriverException as e:
            if attempt == max_retries - 1:
                raise
            print(f"시도 {attempt + 1} 실패: {e}")
            time.sleep(delay)

# Page Object Model 패턴
class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.username_input = (By.ID, "username")
        self.password_input = (By.ID, "password")
        self.login_button = (By.ID, "login-btn")
    
    def enter_username(self, username):
        self.driver.find_element(*self.username_input).send_keys(username)
    
    def enter_password(self, password):
        self.driver.find_element(*self.password_input).send_keys(password)
    
    def click_login(self):
        self.driver.find_element(*self.login_button).click()
    
    def login(self, username, password):
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()

driver.quit()

12. 실전 예제: 종합 자동화 프로젝트

지금까지 배운 내용을 종합하여 실제 웹 자동화 시나리오를 구현해보겠습니다. 뉴스 사이트에서 정보를 수집하고 분석하는 예제입니다.

# 종합 예제: 뉴스 사이트 자동화 및 데이터 수집
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
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from datetime import datetime
import csv
import json
import time
import os
import logging

# 로깅 설정
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('automation.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class NewsAutomation:
    def __init__(self, headless=False):
        """뉴스 자동화 클래스 초기화"""
        self.options = webdriver.ChromeOptions()
        
        # 브라우저 옵션 설정
        if headless:
            self.options.add_argument('--headless')
        self.options.add_argument('--no-sandbox')
        self.options.add_argument('--disable-dev-shm-usage')
        self.options.add_argument('--disable-blink-features=AutomationControlled')
        self.options.add_experimental_option("excludeSwitches", ["enable-automation"])
        self.options.add_experimental_option('useAutomationExtension', False)
        
        # User-Agent 설정
        self.options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
        
        self.driver = None
        self.wait = None
        self.articles = []
        
    def start_driver(self):
        """드라이버 시작"""
        try:
            self.driver = webdriver.Chrome(options=self.options)
            self.wait = WebDriverWait(self.driver, 10)
            self.driver.maximize_window()
            logger.info("드라이버 시작 완료")
        except Exception as e:
            logger.error(f"드라이버 시작 실패: {e}")
            raise
    
    def search_news(self, keyword, num_pages=3):
        """뉴스 검색 및 수집"""
        try:
            # Google 뉴스 접속
            self.driver.get("https://news.google.com")
            time.sleep(2)
            
            # 검색창 찾기 및 검색어 입력
            search_button = self.wait.until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "[aria-label*='Search']"))
            )
            search_button.click()
            
            search_input = self.wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text']"))
            )
            search_input.send_keys(keyword)
            search_input.send_keys(Keys.RETURN)
            
            logger.info(f"'{keyword}' 검색 시작")
            time.sleep(3)
            
            # 여러 페이지 순회
            for page in range(num_pages):
                logger.info(f"페이지 {page + 1} 수집 중...")
                self._collect_articles_on_page()
                
                # 다음 페이지로 이동
                if page < num_pages - 1:
                    if not self._go_to_next_page():
                        break
                        
        except TimeoutException:
            logger.error("페이지 로딩 시간 초과")
        except Exception as e:
            logger.error(f"검색 중 오류 발생: {e}")
            self._take_screenshot("error")
    
    def _collect_articles_on_page(self):
        """현재 페이지의 기사 수집"""
        try:
            # 기사 요소들 찾기
            articles = self.driver.find_elements(By.CSS_SELECTOR, "article")
            
            for article in articles[:10]:  # 상위 10개만
                try:
                    # 제목 추출
                    title_elem = article.find_element(By.CSS_SELECTOR, "h3, h4")
                    title = title_elem.text
                    
                    # 출처 추출
                    try:
                        source_elem = article.find_element(By.CSS_SELECTOR, "a[data-n-tid]")
                        source = source_elem.text
                    except:
                        source = "Unknown"
                    
                    # 시간 추출
                    try:
                        time_elem = article.find_element(By.TAG_NAME, "time")
                        publish_time = time_elem.get_attribute("datetime")
                    except:
                        publish_time = "Unknown"
                    
                    # 링크 추출
                    try:
                        link_elem = article.find_element(By.CSS_SELECTOR, "a[href]")
                        link = link_elem.get_attribute("href")
                    except:
                        link = "#"
                    
                    article_data = {
                        'title': title,
                        'source': source,
                        'time': publish_time,
                        'link': link,
                        'collected_at': datetime.now().isoformat()
                    }
                    
                    self.articles.append(article_data)
                    logger.info(f"수집: {title[:50]}...")
                    
                except Exception as e:
                    logger.warning(f"기사 수집 실패: {e}")
                    continue
                    
        except Exception as e:
            logger.error(f"페이지 수집 중 오류: {e}")
    
    def _go_to_next_page(self):
        """다음 페이지로 이동"""
        try:
            # 페이지 스크롤
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(2)
            
            # 더 많은 결과 로드
            try:
                more_button = self.driver.find_element(By.CSS_SELECTOR, "[aria-label*='More']")
                more_button.click()
                time.sleep(3)
                return True
            except:
                logger.info("더 이상 페이지가 없습니다")
                return False
                
        except Exception as e:
            logger.error(f"페이지 이동 실패: {e}")
            return False
    
    def save_to_csv(self, filename="news_data.csv"):
        """수집한 데이터를 CSV로 저장"""
        if not self.articles:
            logger.warning("저장할 데이터가 없습니다")
            return
        
        try:
            with open(filename, 'w', newline='', encoding='utf-8-sig') as file:
                fieldnames = ['title', 'source', 'time', 'link', 'collected_at']
                writer = csv.DictWriter(file, fieldnames=fieldnames)
                writer.writeheader()
                writer.writerows(self.articles)
            
            logger.info(f"데이터 저장 완료: {filename} ({len(self.articles)}개 기사)")
        except Exception as e:
            logger.error(f"CSV 저장 실패: {e}")
    
    def save_to_json(self, filename="news_data.json"):
        """수집한 데이터를 JSON으로 저장"""
        if not self.articles:
            logger.warning("저장할 데이터가 없습니다")
            return
        
        try:
            with open(filename, 'w', encoding='utf-8') as file:
                json.dump(self.articles, file, ensure_ascii=False, indent=2)
            
            logger.info(f"JSON 저장 완료: {filename}")
        except Exception as e:
            logger.error(f"JSON 저장 실패: {e}")
    
    def _take_screenshot(self, prefix="screenshot"):
        """스크린샷 저장"""
        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"{prefix}_{timestamp}.png"
            self.driver.save_screenshot(filename)
            logger.info(f"스크린샷 저장: {filename}")
        except Exception as e:
            logger.error(f"스크린샷 저장 실패: {e}")
    
    def analyze_results(self):
        """수집 결과 분석"""
        if not self.articles:
            logger.warning("분석할 데이터가 없습니다")
            return
        
        # 출처별 통계
        sources = {}
        for article in self.articles:
            source = article['source']
            sources = sources.get(source, 0) + 1
        
        logger.info("\n=== 수집 결과 분석 ===")
        logger.info(f"총 수집 기사 수: {len(self.articles)}")
        logger.info("\n출처별 기사 수:")
        for source, count in sorted(sources.items(), key=lambda x: x[1], reverse=True)[:5]:
            logger.info(f"  - {source}: {count}개")
    
    def close(self):
        """드라이버 종료"""
        if self.driver:
            self.driver.quit()
            logger.info("드라이버 종료 완료")

# 메인 실행 함수
def main():
    """메인 실행 함수"""
    automation = NewsAutomation(headless=False)
    
    try:
        # 드라이버 시작
        automation.start_driver()
        
        # 뉴스 검색 및 수집
        keywords = ["인공지능", "자동화"]
        for keyword in keywords:
            logger.info(f"\n{'='*50}")
            logger.info(f"키워드: {keyword}")
            logger.info(f"{'='*50}")
            automation.search_news(keyword, num_pages=2)
            time.sleep(3)
        
        # 결과 분석
        automation.analyze_results()
        
        # 데이터 저장
        automation.save_to_csv("news_collection.csv")
        automation.save_to_json("news_collection.json")
        
        logger.info("\n✅ 자동화 작업 완료!")
        
    except Exception as e:
        logger.error(f"실행 중 오류 발생: {e}")
    
    finally:
        # 드라이버 종료
        automation.close()

# 추가 유틸리티 함수들
def create_report(data_file="news_collection.json"):
    """수집된 데이터로 리포트 생성"""
    try:
        with open(data_file, 'r', encoding='utf-8') as file:
            data = json.load(file)
        
        report = f"""
📊 뉴스 수집 리포트
{'='*50}
📅 생성일시: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
📰 총 기사 수: {len(data)}

🔍 주요 키워드:
"""
        
        # 제목에서 키워드 추출 (간단한 예시)
        words = {}
        for article in data:
            title_words = article['title'].split()
            for word in title_words:
                if len(word) > 3:  # 3글자 이상만
                    words[word] = words.get(word, 0) + 1
        
        top_words = sorted(words.items(), key=lambda x: x[1], reverse=True)[:10]
        for word, count in top_words:
            report += f"  - {word}: {count}회\n"
        
        report += f"""
{'='*50}
✅ 리포트 생성 완료
"""
        
        # 리포트 저장
        with open("news_report.txt", 'w', encoding='utf-8') as file:
            file.write(report)
        
        print(report)
        
    except Exception as e:
        logger.error(f"리포트 생성 실패: {e}")

# 스케줄링 예제
def schedule_automation():
    """정기적으로 자동화 실행"""
    import schedule
    
    def job():
        logger.info("스케줄된 작업 시작")
        main()
        create_report()
    
    # 매일 오전 9시에 실행
    schedule.every().day.at("09:00").do(job)
    
    # 매 시간마다 실행
    # schedule.every().hour.do(job)
    
    logger.info("스케줄러 시작...")
    while True:
        schedule.run_pending()
        time.sleep(60)

if __name__ == "__main__":
    main()
    create_report()

마무리

이 튜토리얼에서는 Selenium을 활용한 웹 브라우저 자동화의 핵심 기능들을 실제 예제와 함께 살펴보았습니다. Selenium은 웹 테스팅, 스크래핑, 자동화 작업에 매우 강력한 도구이며, Python과 함께 사용하면 복잡한 웹 상호작용도 쉽게 자동화할 수 있습니다.

추가 학습을 위한 팁:
  • 실제 웹사이트로 연습하되, 사이트의 이용약관과 robots.txt를 확인하세요
  • Page Object Model 패턴을 활용하여 유지보수가 쉬운 코드를 작성하세요
  • 명시적 대기(Explicit Wait)를 적극 활용하여 안정적인 자동화를 구현하세요
  • 예외 처리와 로깅을 통해 디버깅이 쉬운 코드를 작성하세요
  • 성능 최적화를 위해 헤드리스 모드와 병렬 처리를 고려하세요
주의사항:
  • 웹 스크래핑 시 해당 사이트의 이용약관을 반드시 확인하세요
  • 과도한 요청으로 서버에 부담을 주지 않도록 적절한 딜레이를 설정하세요
  • 개인정보나 저작권이 있는 콘텐츠를 무단으로 수집하지 마세요
  • 자동화 스크립트 실행 시 보안에 주의하세요

댓글 남기기

AI, 코딩, 일상 및 다양한 정보 공유에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기