포스트

[Python 100일 챌린지] Day 59 - Selenium 기초

[Python 100일 챌린지] Day 59 - Selenium 기초

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: 검색 자동화

요구사항:

  1. Google에서 “Python Selenium” 검색
  2. 첫 10개 결과의 제목과 링크 추출
  3. 딕셔너리 리스트로 저장
  4. 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: 로그인 후 데이터 수집

요구사항:

  1. 로그인 페이지에서 사용자 이름과 비밀번호 입력
  2. 로그인 버튼 클릭
  3. 로그인 성공 확인 (대시보드 페이지 로드)
  4. 대시보드에서 사용자 정보 추출
  5. 로그아웃
💡 힌트
  • 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, 처음엔 복잡해 보여도 브라우저를 자동으로 제어하는 마법 같은 도구입니다! 🌐

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.