driver.get(url) → element.click() → element.send_keys('text') → 브라우저가 자동으로 움직인다! 😊
JavaScript로 만들어진 동적 웹페이지도 문제없이 스크래핑! 로그인, 버튼 클릭, 폼 작성… 브라우저를 완전 자동화합니다!
(40-50분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
🎯 학습 목표 1: Selenium 설치하고 설정하기
Selenium이란?
Selenium은 웹 브라우저를 프로그래밍 방식으로 제어하는 도구입니다. 실제 브라우저를 열어 사람처럼 클릭, 입력, 스크롤 등을 자동화할 수 있습니다.
Selenium vs BeautifulSoup
| 구분 | BeautifulSoup + requests | Selenium |
| 작동 방식 | HTML 다운로드 후 파싱 | 실제 브라우저 실행 |
| JavaScript | 실행 안 됨 (정적 HTML만) | 실행됨 (동적 콘텐츠) |
| 속도 | 빠름 (HTTP 요청만) | 느림 (브라우저 로딩) |
| 메모리 | 적음 | 많음 (브라우저 실행) |
| 사용 사례 | 정적 페이지 스크래핑 | 동적 페이지, 로그인, 자동화 |
| 예시 | 뉴스 헤드라인 추출 | SPA, 무한 스크롤, 버튼 클릭 |
설치하기
1
2
3
4
5
| # Selenium 설치
pip install selenium
# WebDriver Manager 설치 (브라우저 드라이버 자동 관리)
pip install webdriver-manager
|
기본 사용 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# Chrome 드라이버 자동 설치 및 실행
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
# 페이지 열기
driver.get('https://www.example.com')
# 페이지 제목 출력
print(f"Page title: {driver.title}")
# 현재 URL 출력
print(f"Current URL: {driver.current_url}")
# 브라우저 종료
driver.quit()
|
출력:
1
2
| Page title: Example Domain
Current URL: https://www.example.com/
|
브라우저 옵션 설정
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
| from selenium.webdriver.chrome.options import Options
# Chrome 옵션 생성
options = Options()
# 1. Headless 모드 (브라우저 창 안 띄움)
options.add_argument('--headless')
# 2. 창 크기 설정
options.add_argument('--window-size=1920,1080')
# 3. User-Agent 변경
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
# 4. 이미지 로드 안 함 (속도 향상)
prefs = {'profile.managed_default_content_settings.images': 2}
options.add_experimental_option('prefs', prefs)
# 5. 자동화 감지 비활성화
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# 드라이버 생성
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
|
지원하는 브라우저
| 브라우저 | WebDriver | 설치 방법 |
| Chrome | ChromeDriver | webdriver.Chrome() |
| Firefox | GeckoDriver | webdriver.Firefox() |
| Edge | EdgeDriver | webdriver.Edge() |
| Safari | SafariDriver | webdriver.Safari() (macOS만) |
🎯 학습 목표 2: 웹 드라이버로 브라우저 제어하기
요소 찾기 (Locators)
Selenium은 다양한 방법으로 HTML 요소를 찾을 수 있습니다:
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
| from selenium.webdriver.common.by import By
# 1. ID로 찾기
element = driver.find_element(By.ID, 'username')
# 2. Name으로 찾기
element = driver.find_element(By.NAME, 'email')
# 3. CSS 선택자로 찾기
element = driver.find_element(By.CSS_SELECTOR, 'div.container > p.text')
# 4. XPath로 찾기
element = driver.find_element(By.XPATH, '//div[@class="content"]/p')
# 5. 클래스 이름으로 찾기
element = driver.find_element(By.CLASS_NAME, 'btn-primary')
# 6. 태그 이름으로 찾기
element = driver.find_element(By.TAG_NAME, 'h1')
# 7. 링크 텍스트로 찾기
element = driver.find_element(By.LINK_TEXT, 'Click here')
# 8. 부분 링크 텍스트로 찾기
element = driver.find_element(By.PARTIAL_LINK_TEXT, 'Click')
|
여러 요소 찾기
1
2
3
4
5
6
7
| # 모든 <p> 태그 찾기
paragraphs = driver.find_elements(By.TAG_NAME, 'p')
print(f"Found {len(paragraphs)} paragraphs")
for p in paragraphs:
print(p.text)
|
요소 상호작용
1. 텍스트 입력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 텍스트 입력
element = driver.find_element(By.ID, 'username')
element.send_keys('alice123')
# 기존 텍스트 지우고 입력
element.clear()
element.send_keys('bob456')
# 특수 키 입력
from selenium.webdriver.common.keys import Keys
element.send_keys('password')
element.send_keys(Keys.RETURN) # Enter 키
# Ctrl + A (전체 선택)
element.send_keys(Keys.CONTROL, 'a')
|
2. 클릭
1
2
3
4
5
6
7
| # 버튼 클릭
button = driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
button.click()
# 링크 클릭
link = driver.find_element(By.LINK_TEXT, 'Next Page')
link.click()
|
3. 드롭다운 선택
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from selenium.webdriver.support.ui import Select
# <select> 요소 찾기
dropdown = Select(driver.find_element(By.ID, 'country'))
# 값으로 선택
dropdown.select_by_value('kr')
# 보이는 텍스트로 선택
dropdown.select_by_visible_text('South Korea')
# 인덱스로 선택
dropdown.select_by_index(2)
# 현재 선택된 옵션
selected = dropdown.first_selected_option
print(selected.text)
|
4. 체크박스/라디오 버튼
1
2
3
4
5
6
7
8
9
10
| # 체크박스 찾기
checkbox = driver.find_element(By.ID, 'agree')
# 체크 여부 확인
if not checkbox.is_selected():
checkbox.click() # 체크
# 라디오 버튼 선택
radio = driver.find_element(By.CSS_SELECTOR, 'input[value="male"]')
radio.click()
|
요소 속성 가져오기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| element = driver.find_element(By.ID, 'link')
# 텍스트 가져오기
print(element.text)
# 속성 값 가져오기
print(element.get_attribute('href'))
print(element.get_attribute('class'))
# CSS 값 가져오기
print(element.value_of_css_property('color'))
# 요소 표시 여부
print(element.is_displayed()) # True/False
# 요소 활성화 여부
print(element.is_enabled()) # True/False
|
페이지 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 페이지 이동
driver.get('https://www.example.com')
# 뒤로 가기
driver.back()
# 앞으로 가기
driver.forward()
# 새로고침
driver.refresh()
# 현재 URL
print(driver.current_url)
# 페이지 소스 (HTML)
print(driver.page_source)
|
스크립트 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
| # JavaScript 실행
driver.execute_script('alert("Hello from Selenium!");')
# 스크롤
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
# 요소로 스크롤
element = driver.find_element(By.ID, 'footer')
driver.execute_script('arguments[0].scrollIntoView();', element)
# 값 반환
title = driver.execute_script('return document.title;')
print(title)
|
🎯 학습 목표 3: 동적 웹페이지 스크래핑하기
동적 콘텐츠란?
동적 콘텐츠는 JavaScript로 로드되는 콘텐츠로, 일반 requests로는 가져올 수 없습니다.
| 예시 | 설명 |
| 무한 스크롤 | 스크롤 시 추가 콘텐츠 로드 (Instagram, Twitter) |
| AJAX 요청 | 버튼 클릭 시 데이터 로드 |
| SPA | React, Vue로 만든 Single Page Application |
| 지연 로드 | 이미지가 스크롤 시 로드됨 (Lazy Loading) |
무한 스크롤 스크래핑
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
| import time
driver.get('https://infinite-scroll-example.com')
# 스크롤 전 높이
last_height = driver.execute_script('return document.body.scrollHeight')
while True:
# 페이지 끝까지 스크롤
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
# 로딩 대기
time.sleep(2)
# 새 높이 계산
new_height = driver.execute_script('return document.body.scrollHeight')
# 더 이상 로드할 콘텐츠가 없으면 종료
if new_height == last_height:
break
last_height = new_height
# 모든 항목 수집
items = driver.find_elements(By.CSS_SELECTOR, '.item')
print(f"Total items: {len(items)}")
|
버튼 클릭으로 데이터 로드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver.get('https://ajax-example.com')
# "Load More" 버튼 클릭
load_more_button = driver.find_element(By.ID, 'load-more')
load_more_button.click()
# 새 콘텐츠가 로드될 때까지 대기
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.new-content')))
# 데이터 추출
items = driver.find_elements(By.CSS_SELECTOR, '.item')
for item in items:
print(item.text)
|
로그인 자동화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| driver.get('https://example.com/login')
# 사용자 이름 입력
username_input = driver.find_element(By.ID, 'username')
username_input.send_keys('my_username')
# 비밀번호 입력
password_input = driver.find_element(By.ID, 'password')
password_input.send_keys('my_password')
# 로그인 버튼 클릭
login_button = driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
login_button.click()
# 로그인 성공 대기
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.ID, 'dashboard')))
print("로그인 성공!")
# 로그인 후 데이터 수집
data = driver.find_element(By.ID, 'user-data').text
print(data)
|
팝업/Alert 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 페이지에서 alert 발생
driver.get('https://alert-example.com')
button = driver.find_element(By.ID, 'show-alert')
button.click()
# Alert 전환
alert = driver.switch_to.alert
# Alert 텍스트 가져오기
print(alert.text)
# Alert 확인
alert.accept()
# Alert 취소 (confirm일 경우)
# alert.dismiss()
# Prompt에 텍스트 입력
# alert.send_keys('My input')
# alert.accept()
|
여러 창/탭 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 원래 창
main_window = driver.current_window_handle
# 링크 클릭 (새 창 열림)
link = driver.find_element(By.LINK_TEXT, 'Open in new window')
link.click()
# 모든 창 핸들
all_windows = driver.window_handles
# 새 창으로 전환
for window in all_windows:
if window != main_window:
driver.switch_to.window(window)
break
# 새 창에서 작업
print(driver.title)
# 새 창 닫기
driver.close()
# 원래 창으로 돌아가기
driver.switch_to.window(main_window)
|
iframe 처리
1
2
3
4
5
6
7
8
9
10
| # iframe으로 전환
iframe = driver.find_element(By.ID, 'myframe')
driver.switch_to.frame(iframe)
# iframe 내부 요소 접근
element = driver.find_element(By.ID, 'inside-iframe')
print(element.text)
# 원래 페이지로 돌아가기
driver.switch_to.default_content()
|
🎯 학습 목표 4: 대기 전략과 예외 처리하기
대기 전략
1. 암시적 대기 (Implicit Wait)
1
2
3
4
5
| # 모든 요소 찾기에 최대 10초 대기
driver.implicitly_wait(10)
# 이제 find_element는 요소가 나타날 때까지 최대 10초 기다림
element = driver.find_element(By.ID, 'dynamic-element')
|
특징:
- 전역 설정 (모든 요소에 적용)
- 간단하지만 유연성 낮음
2. 명시적 대기 (Explicit Wait)
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
| from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 특정 조건이 만족될 때까지 대기
wait = WebDriverWait(driver, 10) # 최대 10초
# 요소가 나타날 때까지 대기
element = wait.until(
EC.presence_of_element_located((By.ID, 'result'))
)
# 요소가 클릭 가능할 때까지 대기
element = wait.until(
EC.element_to_be_clickable((By.ID, 'submit-button'))
)
# 요소가 보일 때까지 대기
element = wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.popup'))
)
# 텍스트가 나타날 때까지 대기
wait.until(
EC.text_to_be_present_in_element((By.ID, 'status'), 'Complete')
)
|
주요 Expected Conditions:
| 조건 | 설명 |
presence_of_element_located | DOM에 요소가 존재 (보이지 않아도 됨) |
visibility_of_element_located | 요소가 보임 (display: none이 아님) |
element_to_be_clickable | 요소가 보이고 클릭 가능 |
text_to_be_present_in_element | 요소에 특정 텍스트 포함 |
title_is | 페이지 제목이 특정 값 |
url_contains | URL에 특정 문자열 포함 |
staleness_of | 요소가 DOM에서 제거됨 |
3. time.sleep() (최후의 수단)
1
2
3
4
5
6
7
| import time
driver.get('https://example.com')
time.sleep(3) # 3초 대기
# ⚠️ 비추천: 고정 대기 시간은 비효율적
# → 명시적 대기 사용 권장
|
예외 처리
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
| from selenium.common.exceptions import (
NoSuchElementException,
TimeoutException,
ElementClickInterceptedException,
StaleElementReferenceException
)
try:
# 요소 찾기 시도
element = driver.find_element(By.ID, 'non-existent')
except NoSuchElementException:
print("요소를 찾을 수 없습니다")
except TimeoutException:
print("대기 시간 초과")
except ElementClickInterceptedException:
print("요소를 클릭할 수 없습니다 (다른 요소에 가려짐)")
except StaleElementReferenceException:
print("요소가 DOM에서 제거되었습니다")
finally:
driver.quit()
|
재시도 로직
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| from selenium.common.exceptions import TimeoutException
import time
def find_element_with_retry(driver, by, value, max_attempts=3, delay=2):
"""재시도 로직이 있는 요소 찾기"""
for attempt in range(max_attempts):
try:
wait = WebDriverWait(driver, 10)
element = wait.until(
EC.presence_of_element_located((by, value))
)
return element
except TimeoutException:
if attempt < max_attempts - 1:
print(f"재시도 {attempt + 1}/{max_attempts}")
time.sleep(delay)
else:
raise
# 사용
element = find_element_with_retry(driver, By.ID, 'dynamic-content')
|
Context Manager 패턴
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
| from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
class BrowserSession:
def __init__(self, headless=False):
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless')
self.driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
def __enter__(self):
return self.driver
def __exit__(self, exc_type, exc_val, exc_tb):
self.driver.quit()
# 사용
with BrowserSession(headless=True) as driver:
driver.get('https://example.com')
print(driver.title)
# 블록 종료 시 자동으로 driver.quit() 호출
|
💡 실전 팁 & 주의사항
✅ DO (이렇게 하세요)
- 명시적 대기 사용:
WebDriverWait로 조건이 만족될 때까지 대기 - headless 모드 활용: 서버에서 실행 시
--headless 옵션 사용 - 예외 처리: 요소를 찾지 못할 경우를 대비한 try-except
- 드라이버 종료: 작업 완료 후 반드시
driver.quit() 호출 - User-Agent 설정: 봇 감지 방지를 위해 실제 브라우저처럼 설정
- CSS 선택자 우선: XPath보다 CSS 선택자가 빠르고 읽기 쉬움
❌ DON’T (하지 마세요)
- time.sleep() 남용: 고정 대기 시간은 비효율적
- 암시적 + 명시적 대기 혼용: 예상치 못한 대기 시간 발생
- 너무 많은 브라우저 인스턴스: 메모리 부족
- 에러 무시: 예외를
pass로 무시하지 말 것 - Captcha 우회 시도: 대부분 사이트의 이용 약관 위반
성능 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. Headless 모드
options.add_argument('--headless')
# 2. 이미지 로드 비활성화
prefs = {'profile.managed_default_content_settings.images': 2}
options.add_experimental_option('prefs', prefs)
# 3. GPU 가속 비활성화
options.add_argument('--disable-gpu')
# 4. 브라우저 로그 비활성화
options.add_argument('--log-level=3')
# 5. 페이지 로드 전략
options.page_load_strategy = 'eager' # DOM 로드되면 바로 진행
|
🧪 연습 문제
문제 1: 검색 자동화
요구사항:
- Google에서 “Python Selenium” 검색
- 첫 10개 결과의 제목과 링크 추출
- 딕셔너리 리스트로 저장
- JSON 파일로 저장
💡 힌트
driver.get('https://www.google.com') - 검색창에 텍스트 입력 후 Enter
find_elements로 여러 결과 찾기 element.text, element.get_attribute('href') 사용
✅ 정답 예시
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| import json
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
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# 드라이버 설정
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
try:
# Google 열기
driver.get('https://www.google.com')
# 검색창 찾기
search_box = driver.find_element(By.NAME, 'q')
# 검색어 입력
search_box.send_keys('Python Selenium')
search_box.send_keys(Keys.RETURN)
# 결과 로드 대기
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div#search')))
# 결과 추출
results = []
result_elements = driver.find_elements(By.CSS_SELECTOR, 'div.g')[:10]
for element in result_elements:
try:
title_element = element.find_element(By.CSS_SELECTOR, 'h3')
link_element = element.find_element(By.CSS_SELECTOR, 'a')
title = title_element.text
link = link_element.get_attribute('href')
if title and link:
results.append({
'title': title,
'link': link
})
except:
continue
# JSON 저장
with open('search_results.json', 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"검색 결과 {len(results)}개 저장 완료")
finally:
driver.quit()
|
문제 2: 로그인 후 데이터 수집
요구사항:
- 로그인 페이지에서 사용자 이름과 비밀번호 입력
- 로그인 버튼 클릭
- 로그인 성공 확인 (대시보드 페이지 로드)
- 대시보드에서 사용자 정보 추출
- 로그아웃
💡 힌트
send_keys()로 입력, click()으로 클릭 WebDriverWait로 페이지 전환 대기 - 로그인 성공 확인: 특정 요소의 존재 여부
✅ 정답 예시
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| 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.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# 드라이버 설정
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
try:
# 로그인 페이지 열기
driver.get('https://example.com/login')
# 사용자 이름 입력
username_input = driver.find_element(By.ID, 'username')
username_input.send_keys('testuser')
# 비밀번호 입력
password_input = driver.find_element(By.ID, 'password')
password_input.send_keys('testpass123')
# 로그인 버튼 클릭
login_button = driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
login_button.click()
# 대시보드 로드 대기
wait = WebDriverWait(driver, 10)
dashboard = wait.until(
EC.presence_of_element_located((By.ID, 'dashboard'))
)
print("로그인 성공!")
# 사용자 정보 추출
user_info = {
'name': driver.find_element(By.ID, 'user-name').text,
'email': driver.find_element(By.ID, 'user-email').text,
'role': driver.find_element(By.ID, 'user-role').text
}
print(f"사용자 정보: {user_info}")
# 로그아웃
logout_button = driver.find_element(By.ID, 'logout')
logout_button.click()
# 로그아웃 확인
wait.until(EC.presence_of_element_located((By.ID, 'login-form')))
print("로그아웃 완료")
finally:
driver.quit()
|
📝 오늘 배운 내용 정리
| 개념 | 설명 | 핵심 코드 |
| Selenium | 브라우저 자동화 도구 | driver = webdriver.Chrome() |
| 요소 찾기 | By.ID, By.CSS_SELECTOR 등 | driver.find_element(By.ID, 'username') |
| 요소 조작 | 클릭, 입력, 선택 | element.click(), element.send_keys('text') |
| 명시적 대기 | 조건 만족 시까지 대기 | WebDriverWait(driver, 10).until(EC...) |
| JavaScript 실행 | 스크롤, 값 변경 등 | driver.execute_script('...') |
| 팝업/Alert | 경고창 처리 | driver.switch_to.alert.accept() |
핵심 코드 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 기본 브라우저 제어
driver = webdriver.Chrome()
driver.get('https://example.com')
element = driver.find_element(By.ID, 'username')
element.send_keys('text')
element.click()
driver.quit()
# 2. 명시적 대기
wait = WebDriverWait(driver, 10)
element = wait.until(
EC.presence_of_element_located((By.ID, 'result'))
)
# 3. JavaScript 실행
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
# 4. 예외 처리
try:
element = driver.find_element(By.ID, 'missing')
except NoSuchElementException:
print("요소를 찾을 수 없습니다")
finally:
driver.quit()
|
🔗 관련 자료
📚 이전 학습
← Day 58: 웹 크롤러 만들기 BFS/DFS 크롤링 전략, URL 정규화, 중복 방지, robots.txt
📚 다음 학습
Day 60: Phase 6 실전 프로젝트 → 웹 스크래핑과 API의 모든 것을 종합한 실전 프로젝트
“늦었다고 생각할 때가 가장 빠를 때입니다.” Selenium, 처음엔 복잡해 보여도 브라우저를 자동으로 제어하는 마법 같은 도구입니다! 🌐