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()
10. 쿠키와 세션 관리
쿠키를 관리하여 로그인 상태를 유지하거나 세션을 재사용할 수 있습니다.
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)를 적극 활용하여 안정적인 자동화를 구현하세요
- 예외 처리와 로깅을 통해 디버깅이 쉬운 코드를 작성하세요
- 성능 최적화를 위해 헤드리스 모드와 병렬 처리를 고려하세요
- 웹 스크래핑 시 해당 사이트의 이용약관을 반드시 확인하세요
- 과도한 요청으로 서버에 부담을 주지 않도록 적절한 딜레이를 설정하세요
- 개인정보나 저작권이 있는 콘텐츠를 무단으로 수집하지 마세요
- 자동화 스크립트 실행 시 보안에 주의하세요