목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #23: Selenium과 Playwright로 JavaScript 웹사이트 자동화하기

BeanCon
Python으로 Selenium과 Playwright를 사용해 JavaScript 웹사이트를 자동화하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 23편입니다. requests와 BeautifulSoup으로 수집하기 어려운 JavaScript 웹사이트를 Selenium과 Playwright로 자동화하는 방법을 정리했습니다. 브라우저 실행, 요소 탐색, 클릭과 입력, 명시적 대기, 자동 대기, 스크롤, 스크린샷, 로그인, Headless 모드, iframe과 팝업 처리, 안정적인 자동화 원칙까지 다룹니다.

목차

메타 설명
requests와 BeautifulSoup으로 가져올 수 없는 JavaScript 웹사이트는 어떻게 수집해야 할까요? Selenium과 Playwright를 이용해 브라우저 실행, 클릭, 입력, 대기, 스크롤, 로그인, 데이터 수집까지 자동화하는 방법을 Python 코드와 함께 알아봅니다.

지난 시간에는 웹사이트의 HTML을 분석하고 원하는 데이터를 수집하는 웹 크롤링을 배웠습니다.

그런데 막상 실제 웹사이트를 수집하려고 하면 이상한 일이 벌어집니다.

분명 브라우저 화면에는 상품명, 뉴스 제목, 가격, 댓글이 보이는데...

import requests

html = requests.get("https://example.com").text
print(html)

결과를 확인해 보면 원하는 내용이 없습니다.

웹페이지가 우리에게 이렇게 말하는 것 같습니다.

“데이터는 있는데, 그냥은 안 보여주지.” 😎

이런 현상이 발생하는 이유는 많은 현대 웹사이트가 HTML 안에 모든 데이터를 넣어 두지 않고, JavaScript가 실행된 이후 서버에서 데이터를 불러와 화면에 표시하기 때문입니다.

이때 필요한 것이 바로 브라우저 자동화 도구입니다.

이번 시간에는 Python으로 실제 브라우저를 실행하고, 버튼을 누르고, 검색어를 입력하고, 스크롤하고, JavaScript로 생성된 데이터를 수집하는 방법을 알아보겠습니다.

오늘의 주인공은 다음 두 명입니다.

* 자동화계의 오랜 베테랑, Selenium* 빠르고 영리한 신세대 자동화 도구, Playwright

브라우저 운전석에 Python을 앉혀 보겠습니다. 🚗💨

목차

1. 일반적인 웹 크롤링의 한계2. 브라우저 자동화란?3. Selenium이란?4. Selenium 설치하기5. Selenium으로 브라우저 조작하기6. 요소를 찾는 다양한 방법7. JavaScript 로딩 기다리기8. Selenium 실전 크롤링9. Playwright란?10. Playwright 설치하기11. Playwright로 브라우저 조작하기12. Playwright의 자동 대기 기능13. Selenium과 Playwright 비교14. 스크롤과 스크린샷 자동화15. 로그인 자동화의 구조16. Headless 모드 사용하기17. 자주 발생하는 오류18. 안전한 웹 자동화를 위한 주의사항19. 미니 프로젝트20. 연습 문제21. 마무리

1. 일반적인 웹 크롤링의 한계

웹 크롤링을 배울 때 가장 먼저 사용하는 조합은 보통 다음과 같습니다.

import requests
from bs4 import BeautifulSoup

`requests`는 웹 서버에 요청을 보내 HTML을 가져옵니다.

`BeautifulSoup`은 가져온 HTML을 분석해 원하는 데이터를 찾습니다.

import requests
from bs4 import BeautifulSoup

url = "https://example.com"

response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")

print(soup.title.text)

이 방식은 빠르고 간단합니다.

하지만 중요한 한계가 있습니다.

requests는 JavaScript를 실행하지 않는다

`requests`는 웹 서버가 처음 전달한 HTML만 가져옵니다.

다음과 같은 JavaScript 코드는 실행하지 않습니다.

fetch("/api/products")
  .then(response => response.json())
  .then(products => {
    // 화면에 상품 목록 표시
  });

브라우저는 HTML을 받은 뒤 JavaScript를 실행하고 추가 데이터를 가져옵니다.

하지만 `requests`는 HTML을 받은 자리에서 업무를 종료합니다.

정리하면 다음과 같습니다.

requests
   ↓
서버에서 전달한 최초 HTML만 가져옴
   ↓
JavaScript 실행 안 함
   ↓
동적으로 추가된 데이터가 보이지 않음

반면 브라우저 자동화 도구는 실제 브라우저를 실행합니다.

Selenium 또는 Playwright
   ↓
Chrome, Firefox 등의 브라우저 실행
   ↓
HTML, CSS, JavaScript 모두 처리
   ↓
사용자가 보는 최종 화면에 접근

JavaScript 웹사이트라는 성벽 앞에서 `requests`가 초인종을 누르는 도구라면, Selenium과 Playwright는 직접 문을 열고 들어가는 브라우저 로봇입니다. 🤖

2. 브라우저 자동화란?

브라우저 자동화란 사람이 브라우저에서 수행하는 작업을 프로그램이 대신 실행하도록 만드는 기술입니다.

예를 들어 사람이 검색 사이트를 이용하는 과정은 다음과 같습니다.

1. 브라우저를 실행한다.2. 검색 사이트에 접속한다.3. 검색창을 찾는다.4. 검색어를 입력한다.5. 검색 버튼을 누른다.6. 검색 결과가 나타날 때까지 기다린다.7. 결과를 읽는다.

브라우저 자동화 도구를 사용하면 이 모든 작업을 Python 코드로 표현할 수 있습니다.

브라우저_실행()
웹사이트_접속()
검색창_찾기()
검색어_입력()
검색버튼_클릭()
결과_기다리기()
데이터_수집()

실제로 활용할 수 있는 분야도 다양합니다.

* JavaScript 기반 웹사이트 크롤링* 웹서비스 기능 테스트* 반복적인 관리자 페이지 작업* 여러 페이지의 데이터 수집* 자동 로그인* 파일 업로드와 다운로드* 화면 캡처* 웹서비스 모니터링* 상품 가격이나 재고 확인* 뉴스 제목과 게시 시간 수집

단, 자동화가 가능하다고 해서 모든 웹사이트를 자유롭게 조작해도 된다는 의미는 아닙니다.

서비스 이용약관, 접근 권한, 개인정보, 서버 부하 등을 반드시 고려해야 합니다.

3. Selenium이란?

Selenium은 웹 브라우저를 자동으로 제어할 수 있도록 만들어진 대표적인 브라우저 자동화 프로젝트입니다.

Selenium WebDriver는 브라우저를 로컬 또는 원격 환경에서 실제 사용자처럼 제어합니다. 또한 WebDriver는 W3C 표준을 기반으로 동작합니다.

Selenium을 사용하면 다음과 같은 작업을 수행할 수 있습니다.

브라우저 열기
웹사이트 이동
버튼 클릭
키보드 입력
체크박스 선택
새 창 열기
팝업 처리
스크롤 이동
파일 업로드
스크린샷 저장
페이지 데이터 수집

Selenium의 기본 구조

Selenium은 대략 다음 구조로 브라우저를 조작합니다.

Python 코드
    ↓
Selenium WebDriver
    ↓
브라우저 드라이버
    ↓
Chrome, Edge, Firefox

과거에는 사용자가 ChromeDriver 같은 브라우저 드라이버를 직접 내려받고 버전까지 맞춰야 했습니다.

요즘 Selenium은 기본적으로 Selenium Manager를 사용해 필요한 드라이버와 브라우저 관리를 자동화합니다. Selenium Manager는 Selenium 배포판에 포함되며 Windows, Linux, macOS에서 사용할 수 있습니다.

덕분에 다음과 같이 간단하게 브라우저를 실행할 수 있습니다.

from selenium import webdriver

driver = webdriver.Chrome()

과거의 드라이버 설치 과정이 수동 변속기였다면, Selenium Manager는 자동 변속기에 가깝습니다.

4. Selenium 설치하기

터미널이나 명령 프롬프트를 실행하고 다음 명령어를 입력합니다.

pip install selenium

가상환경을 사용하고 있다면 먼저 가상환경을 활성화합니다.

Windows

python -m venv venv
venv\Scripts\activate
pip install selenium

macOS 또는 Linux

python3 -m venv venv
source venv/bin/activate
pip install selenium

설치 여부를 확인합니다.

pip show selenium

또는 Python에서 직접 확인할 수도 있습니다.

import selenium

print(selenium.__version__)

5. Selenium으로 브라우저 조작하기

먼저 가장 간단한 브라우저 실행 프로그램을 만들어 보겠습니다.

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("https://www.python.org")

input("브라우저를 종료하려면 Enter를 누르세요.")

driver.quit()

코드 분석

브라우저 실행

driver = webdriver.Chrome()

Chrome 브라우저를 자동화할 WebDriver 객체를 생성합니다.

웹사이트 접속

driver.get("https://www.python.org")

브라우저 주소창에 URL을 입력하고 이동하는 것과 같습니다.

브라우저 종료

driver.quit()

열려 있는 모든 브라우저 창과 WebDriver 프로세스를 종료합니다.

자동화 프로그램에서는 `quit()`을 빠뜨리지 않는 것이 중요합니다.

브라우저를 계속 열어 두면 백그라운드에 프로세스가 쌓일 수 있습니다.

따라서 일반적으로 `try`와 `finally`를 함께 사용합니다.

from selenium import webdriver

driver = webdriver.Chrome()

try:
    driver.get("https://www.python.org")
    print(driver.title)

finally:
    driver.quit()

`finally` 블록은 중간에 오류가 발생하더라도 실행됩니다.

브라우저 자동화계의 “퇴실할 때 불 끄기” 규칙입니다. 💡

6. Selenium으로 입력하고 버튼 클릭하기

Selenium 공식 테스트 페이지를 이용해 간단한 입력 자동화를 만들어 보겠습니다.

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

driver = webdriver.Chrome()

try:
    driver.get(
        "https://www.selenium.dev/selenium/web/web-form.html"
    )

    text_box = driver.find_element(By.NAME, "my-text")
    submit_button = driver.find_element(
        By.CSS_SELECTOR,
        "button"
    )

    text_box.send_keys("Python 자동화 시작!")
    submit_button.click()

    message = driver.find_element(By.ID, "message")

    print(message.text)

finally:
    driver.quit()

Selenium에서 수행하는 대부분의 작업은 브라우저에 명령을 전달하거나 브라우저의 정보를 읽는 형태로 구성됩니다.

주요 명령어

글자 입력

element.send_keys("입력할 내용")

클릭

element.click()

화면의 텍스트 읽기

print(element.text)

입력창 내용 삭제

element.clear()

현재 페이지 제목 확인

print(driver.title)

현재 URL 확인

print(driver.current_url)

이전 페이지 이동

driver.back()

다음 페이지 이동

driver.forward()

새로고침

driver.refresh()

Python 코드 몇 줄로 브라우저의 뒤로 가기 버튼과 새로고침 버튼까지 접수했습니다.

7. 웹 요소를 찾는 다양한 방법

Selenium이 버튼이나 입력창을 클릭하려면 먼저 해당 요소를 찾아야 합니다.

이를 위해 `By` 클래스를 사용합니다.

from selenium.webdriver.common.by import By

ID로 찾기

HTML이 다음과 같다고 가정해 보겠습니다.

<input id="username">

Python 코드는 다음과 같습니다.

element = driver.find_element(
    By.ID,
    "username"
)

NAME으로 찾기

<input name="email">
element = driver.find_element(
    By.NAME,
    "email"
)

클래스 이름으로 찾기

<button class="login-button">로그인</button>
element = driver.find_element(
    By.CLASS_NAME,
    "login-button"
)

태그 이름으로 찾기

headings = driver.find_elements(
    By.TAG_NAME,
    "h2"
)

CSS 선택자로 찾기

element = driver.find_element(
    By.CSS_SELECTOR,
    "#login-form input[name='email']"
)

XPath로 찾기

element = driver.find_element(
    By.XPATH,
    "//button[contains(text(), '로그인')]"
)

하나 찾기와 여러 개 찾기

하나의 요소를 찾을 때는 `find_element()`를 사용합니다.

title = driver.find_element(
    By.CSS_SELECTOR,
    "h1"
)

여러 요소를 찾을 때는 `find_elements()`를 사용합니다.

items = driver.find_elements(
    By.CSS_SELECTOR,
    ".product-item"
)

for item in items:
    print(item.text)

`find_elements()`는 조건에 맞는 요소를 리스트 형태로 반환합니다.

8. CSS 선택자 빠르게 이해하기

브라우저 자동화에서는 CSS 선택자를 자주 사용합니다.

다음 HTML이 있다고 가정해 보겠습니다.

<div id="product-list">
    <div class="product">
        <h2 class="name">키보드</h2>
        <span class="price">50,000원</span>
    </div>
</div>

ID 선택자

#product-list
driver.find_element(
    By.CSS_SELECTOR,
    "#product-list"
)

클래스 선택자

.product
driver.find_elements(
    By.CSS_SELECTOR,
    ".product"
)

자식 요소 선택

.product .name
driver.find_elements(
    By.CSS_SELECTOR,
    ".product .name"
)

속성 선택

input[name="email"]
driver.find_element(
    By.CSS_SELECTOR,
    'input[name="email"]'
)

CSS 선택자를 잘 다루면 Selenium과 Playwright 양쪽에서 활용할 수 있습니다.

웹 자동화의 절반은 기다리기이고, 나머지 절반은 선택자와 친해지는 일이라고 해도 과언이 아닙니다.

9. JavaScript 로딩 기다리기

브라우저 자동화에서 초보자가 가장 자주 만나는 오류는 다음과 같습니다.

NoSuchElementException
ElementNotInteractableException
ElementClickInterceptedException
TimeoutException

코드에는 분명 요소가 있는데 Selenium이 찾지 못합니다.

왜 그럴까요?

Selenium이 너무 부지런해서 그렇습니다.

페이지가 데이터를 불러오기도 전에 요소를 찾으려고 달려간 것입니다.

Selenium: 버튼 어디 있지?
웹페이지: 아직 만들고 있는데요?
Selenium: 없군. 오류!

좋지 않은 해결 방법

import time

time.sleep(5)

`time.sleep()`은 무조건 지정된 시간만큼 기다립니다.

페이지가 1초 만에 준비되어도 5초를 기다립니다.

반대로 페이지 로딩에 7초가 걸리면 5초를 기다려도 실패합니다.

명시적 대기 사용하기

Selenium에서는 `WebDriverWait`과 Expected Conditions를 사용할 수 있습니다.

명시적 대기는 특정 조건이 충족될 때까지 주기적으로 상태를 확인합니다. 조건이 제한 시간 내에 충족되지 않으면 시간 초과 오류가 발생합니다.

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()

try:
    driver.get(
        "https://www.selenium.dev/selenium/web/web-form.html"
    )

    wait = WebDriverWait(driver, 10)

    text_box = wait.until(
        EC.visibility_of_element_located(
            (By.NAME, "my-text")
        )
    )

    text_box.send_keys("기다림 완료!")

    submit_button = wait.until(
        EC.element_to_be_clickable(
            (By.CSS_SELECTOR, "button")
        )
    )

    submit_button.click()

    message = wait.until(
        EC.visibility_of_element_located(
            (By.ID, "message")
        )
    )

    print(message.text)

finally:
    driver.quit()

자주 사용하는 대기 조건

요소가 존재할 때까지

EC.presence_of_element_located(
    (By.ID, "result")
)

요소가 화면에 보일 때까지

EC.visibility_of_element_located(
    (By.ID, "result")
)

요소를 클릭할 수 있을 때까지

EC.element_to_be_clickable(
    (By.ID, "submit")
)

여러 요소가 생성될 때까지

EC.presence_of_all_elements_located(
    (By.CSS_SELECTOR, ".item")
)

URL이 변경될 때까지

EC.url_contains("/dashboard")

특정 제목이 나타날 때까지

EC.title_contains("관리자")

웹 자동화에서는 다음 원칙을 기억하면 좋습니다.

“몇 초 기다릴까?”보다 “무엇이 준비될 때까지 기다릴까?”를 생각한다.

10. Selenium 실전 크롤링

이번에는 JavaScript로 명언 목록을 표시하는 연습용 사이트에서 데이터를 수집해 보겠습니다. 해당 페이지는 명언 목록과 다음 페이지 링크를 제공하는 실습용 페이지입니다.

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()
wait = WebDriverWait(driver, 10)

try:
    driver.get("https://quotes.toscrape.com/js/")

    quote_elements = wait.until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, ".quote")
        )
    )

    for index, quote in enumerate(
        quote_elements,
        start=1
    ):
        text = quote.find_element(
            By.CSS_SELECTOR,
            ".text"
        ).text

        author = quote.find_element(
            By.CSS_SELECTOR,
            ".author"
        ).text

        tags = quote.find_elements(
            By.CSS_SELECTOR,
            ".tag"
        )

        tag_names = [
            tag.text for tag in tags
        ]

        print(f"{index}. {text}")
        print(f"   작가: {author}")
        print(f"   태그: {', '.join(tag_names)}")
        print()

finally:
    driver.quit()

결과 예시

1. “The world as we have created it...”
   작가: Albert Einstein
   태그: change, deep-thoughts, thinking

2. “It is our choices, Harry...”
   작가: J.K. Rowling
   태그: abilities, choices

여러 페이지 수집하기

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()
wait = WebDriverWait(driver, 10)

results = []

try:
    driver.get("https://quotes.toscrape.com/js/")

    while True:
        quote_elements = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".quote")
            )
        )

        for quote in quote_elements:
            text = quote.find_element(
                By.CSS_SELECTOR,
                ".text"
            ).text

            author = quote.find_element(
                By.CSS_SELECTOR,
                ".author"
            ).text

            results.append(
                {
                    "text": text,
                    "author": author,
                }
            )

        next_buttons = driver.find_elements(
            By.CSS_SELECTOR,
            "li.next > a"
        )

        if not next_buttons:
            break

        first_quote = quote_elements[0]

        next_buttons[0].click()

        wait.until(
            EC.staleness_of(first_quote)
        )

finally:
    driver.quit()

print(f"수집 완료: {len(results)}개")

for result in results[:5]:
    print(result)

`staleness_of()`는 기존 페이지의 요소가 사라질 때까지 기다립니다.

따라서 다음 페이지가 실제로 로딩되었는지 판단하는 데 활용할 수 있습니다.

11. 수집한 데이터를 CSV로 저장하기

수집한 데이터를 파일로 저장해 보겠습니다.

import csv

results = [
    {
        "text": "명언 내용",
        "author": "작가 이름",
    },
    {
        "text": "두 번째 명언",
        "author": "두 번째 작가",
    },
]

with open(
    "quotes.csv",
    "w",
    newline="",
    encoding="utf-8-sig"
) as file:
    writer = csv.DictWriter(
        file,
        fieldnames=["text", "author"]
    )

    writer.writeheader()
    writer.writerows(results)

print("quotes.csv 저장 완료")

`utf-8-sig`를 사용하면 Windows Excel에서도 한글이 깨질 가능성을 줄일 수 있습니다.

Selenium은 브라우저 조작을 담당하고, CSV 모듈은 수집 결과를 저장합니다.

각자 맡은 임무가 분명한 작은 자동화 팀입니다.

12. Playwright란?

Playwright는 Microsoft가 개발한 브라우저 자동화 도구입니다.

Python에서는 Chromium, Firefox, WebKit 엔진을 실행할 수 있으며 동기 방식과 비동기 방식 API를 제공합니다.

Playwright는 특히 다음 기능에서 강점을 보입니다.

* 요소가 준비될 때까지 자동으로 기다리는 Auto-waiting* 안정적인 Locator API* Chromium, Firefox, WebKit 지원* 독립된 브라우저 세션인 Browser Context* 네트워크 요청 제어* 새 창과 팝업 처리* 모바일 화면 크기와 환경 모방* 스크린샷과 동영상 기록* 웹서비스 테스트 자동화

Playwright에서 Locator는 요소 탐색뿐 아니라 자동 대기와 재시도 기능의 중심 역할을 합니다.

Selenium이 수많은 현장을 경험한 베테랑 기사라면, Playwright는 내비게이션과 자동 주차 기능을 잔뜩 장착한 최신 전기차에 가깝습니다.

둘 다 목적지에는 도착합니다.

다만 운전 감각이 조금 다릅니다.

13. Playwright 설치하기

먼저 Playwright Python 패키지를 설치합니다.

pip install playwright

그다음 Playwright에서 사용할 브라우저를 설치합니다.

playwright install

Playwright는 패키지 버전에 맞는 브라우저 바이너리를 별도로 사용하기 때문에 패키지를 업데이트한 뒤 브라우저 설치 명령을 다시 실행해야 할 수 있습니다.

특정 브라우저만 설치할 수도 있습니다.

playwright install chromium
playwright install firefox
playwright install webkit

Linux에서 필요한 시스템 라이브러리까지 함께 설치하려면 다음 명령어를 사용할 수 있습니다.

playwright install --with-deps

14. Playwright로 브라우저 실행하기

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False
    )

    page = browser.new_page()

    page.goto("https://www.python.org")

    print(page.title())

    browser.close()

코드 분석

Playwright 시작

with sync_playwright() as p:

Playwright 실행 환경을 시작합니다.

Chromium 실행

browser = p.chromium.launch(
    headless=False
)

`headless=False`는 실제 브라우저 창을 화면에 표시한다는 의미입니다.

새 페이지 생성

page = browser.new_page()

새 브라우저 탭과 비슷한 페이지 객체를 생성합니다.

URL 이동

page.goto("https://www.python.org")

브라우저 종료

browser.close()

`with` 블록을 사용하면 Playwright 실행 환경의 정리 과정도 보다 안전하게 관리할 수 있습니다.

15. Playwright로 입력하고 클릭하기

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False
    )

    page = browser.new_page()

    page.goto(
        "https://www.selenium.dev/"
        "selenium/web/web-form.html"
    )

    page.locator(
        'input[name="my-text"]'
    ).fill("Playwright 자동화 시작!")

    page.locator("button").click()

    message = page.locator("#message")

    print(message.inner_text())

    browser.close()

Selenium과 표현 비교

Selenium에서는 다음과 같이 작성합니다.

element = driver.find_element(
    By.NAME,
    "my-text"
)

element.send_keys("Python")

Playwright에서는 다음과 같이 작성합니다.

page.locator(
    'input[name="my-text"]'
).fill("Python")

Playwright는 요소를 찾고 조작하는 과정이 비교적 간결합니다.

16. Playwright Locator 사용하기

Playwright는 사용자에게 보이는 텍스트나 웹 접근성 역할을 기준으로 요소를 찾는 방식을 권장합니다.

CSS 구조에 강하게 묶인 선택자보다 사용자가 인식할 수 있는 텍스트나 역할 기반 Locator를 우선하면 화면 구조가 조금 변경되어도 코드가 유지될 가능성이 높습니다.

텍스트로 찾기

page.get_by_text("로그인").click()

버튼 역할로 찾기

page.get_by_role(
    "button",
    name="로그인"
).click()

입력창 라벨로 찾기

page.get_by_label(
    "이메일"
).fill("user@example.com")

Placeholder로 찾기

page.get_by_placeholder(
    "검색어를 입력하세요"
).fill("Python")

CSS 선택자로 찾기

page.locator(
    ".product-name"
)

여러 요소 다루기

items = page.locator(".product-item")

print(items.count())

for index in range(items.count()):
    print(
        items.nth(index).inner_text()
    )

첫 번째와 마지막 요소

first_item = items.first
last_item = items.last

조건을 추가해 찾기

product = page.locator(
    ".product-item"
).filter(
    has_text="Python 책"
)

Locator는 실제 요소를 한 번 찾아 고정해 두는 것이 아니라, 작업이 실행되는 시점에 조건에 맞는 요소를 다시 평가할 수 있습니다.

동적으로 화면이 바뀌는 웹사이트에서 특히 유용합니다.

17. Playwright의 자동 대기 기능

Playwright의 대표적인 장점은 Auto-waiting입니다.

버튼을 클릭할 때 Playwright는 단순히 요소가 HTML에 존재하는지만 확인하지 않습니다.

해당 요소가 다음 조건을 만족하는지 점검합니다.

* 화면에 표시되는가?* 위치가 안정적인가?* 다른 요소에 가려져 있지 않은가?* 클릭할 수 있는 상태인가?* 활성화되어 있는가?

필요한 조건이 충족되면 작업을 실행하고, 제한 시간 내에 충족되지 않으면 `TimeoutError`를 발생시킵니다.

따라서 다음과 같은 코드를 자주 작성하지 않아도 됩니다.

import time

time.sleep(3)

Playwright에서는 다음 코드만으로 충분한 경우가 많습니다.

page.get_by_role(
    "button",
    name="검색"
).click()

Playwright가 내부적으로 버튼이 준비될 때까지 기다립니다.

다만 자동 대기가 모든 상황을 해결하는 마법 주문은 아닙니다.

특정 API 응답, 숫자 변경, 애니메이션 종료처럼 별도의 조건을 확인해야 할 때는 명시적으로 기다릴 수 있습니다.

page.wait_for_selector(
    ".search-result"
)
page.wait_for_url(
    "**/dashboard"
)
page.get_by_text(
    "저장 완료"
).wait_for()

18. Playwright 실전 크롤링

앞에서 사용한 JavaScript 명언 페이지를 Playwright로 수집해 보겠습니다.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True
    )

    page = browser.new_page()

    page.goto(
        "https://quotes.toscrape.com/js/"
    )

    quote_cards = page.locator(".quote")

    results = []

    for index in range(
        quote_cards.count()
    ):
        card = quote_cards.nth(index)

        text = card.locator(
            ".text"
        ).inner_text()

        author = card.locator(
            ".author"
        ).inner_text()

        tags = card.locator(
            ".tag"
        ).all_inner_texts()

        results.append(
            {
                "text": text,
                "author": author,
                "tags": tags,
            }
        )

    browser.close()

for result in results:
    print(result["text"])
    print(result["author"])
    print(result["tags"])
    print()

여러 페이지 수집하기

from playwright.sync_api import sync_playwright

results = []

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True
    )

    page = browser.new_page()

    page.goto(
        "https://quotes.toscrape.com/js/"
    )

    while True:
        quote_cards = page.locator(".quote")

        for index in range(
            quote_cards.count()
        ):
            card = quote_cards.nth(index)

            results.append(
                {
                    "text": card.locator(
                        ".text"
                    ).inner_text(),
                    "author": card.locator(
                        ".author"
                    ).inner_text(),
                }
            )

        next_link = page.locator(
            "li.next > a"
        )

        if next_link.count() == 0:
            break

        next_link.click()

    browser.close()

print(f"총 {len(results)}개를 수집했습니다.")

Selenium과 비교하면 대기 코드와 요소 조작 코드가 상당히 간결해진 것을 확인할 수 있습니다.

19. Browser Context란?

Playwright에서는 하나의 브라우저 안에 서로 독립된 여러 브라우저 세션을 만들 수 있습니다.

이를 Browser Context라고 합니다.

각 Context는 쿠키, 로그인 상태, 로컬 스토리지 등을 독립적으로 관리합니다. 기본적으로 비영구 Context는 브라우징 데이터를 디스크에 기록하지 않습니다.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()

    user1 = browser.new_context()
    user2 = browser.new_context()

    page1 = user1.new_page()
    page2 = user2.new_page()

    page1.goto("https://example.com")
    page2.goto("https://example.com")

    user1.close()
    user2.close()

    browser.close()

이를 이용하면 다음과 같은 테스트가 가능합니다.

* 일반 사용자와 관리자 동시 테스트* 구매자와 판매자 동시 접속 테스트* 서로 다른 로그인 계정 테스트* 로그인 전과 로그인 후 화면 비교* 여러 지역과 언어 환경 테스트

브라우저 하나에 여러 명의 가상 사용자를 태울 수 있는 셈입니다.

20. Selenium과 Playwright 비교

비교 항목SeleniumPlaywright
특징오랜 역사와 넓은 생태계현대적인 API와 자동 대기
브라우저 실행WebDriver 기반전용 브라우저 제어 구조
설치`pip install selenium`패키지와 브라우저 설치 필요
대기 처리명시적 대기를 자주 사용자동 대기 기능이 강력
요소 탐색`find_element()`Locator 중심
브라우저 격리별도 드라이버나 프로필 구성Browser Context 지원
병렬 테스트Selenium Grid 활용 가능Context와 테스트 도구 활용
기존 테스트 자산매우 풍부함비교적 새로운 프로젝트에 적합
초보자 체감개념을 명확히 배울 수 있음코드가 간결하고 편리함
추천 분야기존 시스템, 다양한 환경, 대규모 테스트 인프라최신 웹앱, 빠른 자동화, E2E 테스트

Selenium Grid는 WebDriver 명령을 원격 브라우저에 전달해 여러 머신과 브라우저 환경에서 테스트를 병렬로 실행할 수 있도록 지원합니다.

Selenium을 선택하기 좋은 경우

* 회사에 기존 Selenium 테스트 코드가 많은 경우* 다양한 언어와 도구를 함께 사용하는 경우* 원격 브라우저 테스트 환경이 필요한 경우* Selenium Grid를 활용해야 하는 경우* Selenium 관련 자료와 인력이 이미 확보된 경우

Playwright를 선택하기 좋은 경우

* 새로운 자동화 프로젝트를 시작하는 경우* React, Vue, Next.js 같은 동적 웹서비스를 테스트하는 경우* 자동 대기 기능이 중요한 경우* 여러 사용자 세션을 빠르게 구성해야 하는 경우* 간결한 Python 코드가 필요한 경우* 네트워크 요청과 브라우저 동작을 함께 다루는 경우

결론적으로 무엇이 더 좋은가?

무조건 한 도구가 다른 도구보다 좋다고 말하기는 어렵습니다.

기존 Selenium 인프라가 있다
→ Selenium

새 프로젝트이며 최신 웹앱을 다룬다
→ Playwright

대규모 원격 테스트가 중요하다
→ Selenium Grid 검토

간결함과 자동 대기가 중요하다
→ Playwright

브라우저 자동화 개념을 깊게 배우고 싶다
→ 둘 다 경험

도구 선택은 인기투표가 아니라 프로젝트의 요구사항에 따라 결정해야 합니다.

망치와 전동드릴 중 무엇이 더 우수한지 묻는 것보다, 지금 벽에 못을 박는지 구멍을 뚫는지 확인하는 것이 먼저입니다.

21. 브라우저 스크롤 자동화

일부 웹사이트는 화면을 아래로 스크롤해야 추가 데이터를 불러옵니다.

이를 무한 스크롤 방식이라고 합니다.

Selenium 스크롤

driver.execute_script(
    "window.scrollTo(0, document.body.scrollHeight);"
)

페이지 아래로 여러 번 이동하려면 다음과 같이 작성할 수 있습니다.

import time

for _ in range(5):
    driver.execute_script(
        "window.scrollTo("
        "0, document.body.scrollHeight"
        ");"
    )

    time.sleep(2)

보다 안전하게 처리하려면 페이지 높이가 더 이상 증가하지 않을 때 반복을 종료합니다.

import time

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

Playwright 스크롤

page.evaluate(
    "window.scrollTo(0, document.body.scrollHeight)"
)

특정 요소로 이동할 수도 있습니다.

target = page.locator(".last-item")

target.scroll_into_view_if_needed()

무한 스크롤 사이트에서는 단순히 페이지 끝으로 이동하는 것뿐 아니라 다음 조건도 함께 확인해야 합니다.

* 새로운 카드 개수가 증가했는가?* 로딩 표시가 사라졌는가?* 더 보기 버튼이 생성되었는가?* 서버 요청이 완료되었는가?* 마지막 게시물 ID가 변경되었는가?

22. 스크린샷 저장하기

브라우저 자동화는 데이터 수집뿐 아니라 화면 기록에도 사용할 수 있습니다.

Selenium 스크린샷

driver.save_screenshot(
    "selenium_screen.png"
)

Playwright 스크린샷

page.screenshot(
    path="playwright_screen.png"
)

전체 페이지를 저장하려면 다음과 같이 작성합니다.

page.screenshot(
    path="full_page.png",
    full_page=True
)

활용 사례는 다양합니다.

* 웹서비스 장애 화면 기록* 디자인 변경 전후 비교* 관리자 화면 정기 캡처* 테스트 실패 화면 저장* 뉴스 페이지 화면 보관* 상품 가격 변경 증빙

자동화가 실패했을 때 스크린샷을 남기면 원인을 찾는 데 큰 도움이 됩니다.

try:
    page.get_by_role(
        "button",
        name="로그인"
    ).click()

except Exception:
    page.screenshot(
        path="login_error.png",
        full_page=True
    )

    raise

오류 메시지만 보면 미궁이지만, 화면을 보면 범인이 배너인지 팝업인지 금방 드러나는 경우가 많습니다. 🕵️

23. 로그인 자동화의 기본 구조

다음은 로그인 자동화의 일반적인 구조입니다.

실제 서비스가 아닌 예시 주소를 사용했습니다.

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()
wait = WebDriverWait(driver, 10)

try:
    driver.get(
        "https://example.com/login"
    )

    email_input = wait.until(
        EC.visibility_of_element_located(
            (By.NAME, "email")
        )
    )

    password_input = driver.find_element(
        By.NAME,
        "password"
    )

    login_button = driver.find_element(
        By.CSS_SELECTOR,
        "button[type='submit']"
    )

    email_input.send_keys(
        "user@example.com"
    )

    password_input.send_keys(
        "password"
    )

    login_button.click()

    wait.until(
        EC.url_contains("/dashboard")
    )

    print("로그인 완료")

finally:
    driver.quit()

Playwright 방식

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False
    )

    page = browser.new_page()

    page.goto(
        "https://example.com/login"
    )

    page.locator(
        'input[name="email"]'
    ).fill(
        "user@example.com"
    )

    page.locator(
        'input[name="password"]'
    ).fill(
        "password"
    )

    page.locator(
        'button[type="submit"]'
    ).click()

    page.wait_for_url(
        "**/dashboard"
    )

    print("로그인 완료")

    browser.close()

비밀번호를 코드에 직접 작성하지 않기

다음과 같은 코드는 피해야 합니다.

password = "my-real-password"

환경변수를 사용하는 것이 좋습니다.

import os

email = os.getenv("SITE_EMAIL")
password = os.getenv("SITE_PASSWORD")

실행 전에 환경변수를 설정합니다.

Windows PowerShell

$env:SITE_EMAIL="user@example.com"
$env:SITE_PASSWORD="secret"
python app.py

macOS 또는 Linux

export SITE_EMAIL="user@example.com"
export SITE_PASSWORD="secret"
python app.py

로그인 자동화에서는 반드시 다음 사항을 확인해야 합니다.

* 본인 계정 또는 허가받은 계정인가?* 서비스 약관에서 자동 로그인을 허용하는가?* 2단계 인증을 무력화하려는 코드는 아닌가?* 로그인 정보를 안전하게 보관하고 있는가?* 계정 잠금이 발생할 정도로 반복 요청하지 않는가?

CAPTCHA나 보안 인증을 우회하는 방향으로 자동화를 설계해서는 안 됩니다.

24. Headless 모드란?

Headless 모드는 브라우저 창을 화면에 표시하지 않고 백그라운드에서 실행하는 방식입니다.

Selenium Headless 모드

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

options = Options()
options.add_argument("--headless")

driver = webdriver.Chrome(
    options=options
)

try:
    driver.get("https://example.com")
    print(driver.title)

finally:
    driver.quit()

Playwright Headless 모드

browser = p.chromium.launch(
    headless=True
)

Playwright는 일반적으로 `headless=True`가 기본값입니다.

Headless 모드의 장점

* 브라우저 창이 나타나지 않음* 서버 환경에서 실행 가능* 자동화 작업에 적합* CI/CD 파이프라인에서 사용 가능* 반복 실행 시 화면 방해가 적음

개발 중에는 화면을 표시하자

처음 코드를 작성할 때는 브라우저를 화면에 표시하는 것이 좋습니다.

headless=False

자동화 과정에서 어디를 클릭하고 무엇을 기다리는지 직접 확인할 수 있기 때문입니다.

개발이 끝난 뒤 `headless=True`로 변경합니다.

개발 단계
→ 브라우저 표시

운영 단계
→ Headless 실행

처음부터 불을 끄고 디버깅하면, 방 안에서 잃어버린 양말을 찾는 기분이 될 수 있습니다.

25. 새 창과 팝업 처리하기

웹사이트에서 버튼을 클릭하면 새 탭이 열리는 경우가 있습니다.

Selenium 새 창 처리

original_window = driver.current_window_handle

driver.find_element(
    By.ID,
    "open-window"
).click()

WebDriverWait(driver, 10).until(
    EC.number_of_windows_to_be(2)
)

for window_handle in driver.window_handles:
    if window_handle != original_window:
        driver.switch_to.window(
            window_handle
        )
        break

print(driver.title)

Playwright 새 창 처리

with page.expect_popup() as popup_info:
    page.get_by_text(
        "새 창 열기"
    ).click()

popup = popup_info.value

print(popup.title())

Playwright에서는 새 페이지가 열릴 것으로 예상하는 범위를 `expect_popup()`으로 표현합니다.

26. iframe 처리하기

iframe은 현재 웹페이지 안에 다른 웹페이지를 삽입하는 구조입니다.

Selenium은 기본적으로 최상위 문서의 요소를 찾기 때문에 iframe 내부의 요소를 조작하려면 먼저 해당 프레임으로 전환해야 합니다.

Selenium iframe 전환

iframe = driver.find_element(
    By.CSS_SELECTOR,
    "iframe"
)

driver.switch_to.frame(iframe)

driver.find_element(
    By.ID,
    "inside-button"
).click()

driver.switch_to.default_content()

Playwright iframe 처리

frame = page.frame_locator(
    "iframe"
)

frame.get_by_role(
    "button",
    name="확인"
).click()

iframe을 모르고 요소를 계속 찾으면 선택자는 정확한데도 요소가 발견되지 않는 기묘한 상황이 생깁니다.

그럴 때는 개발자 도구에서 요소가 iframe 내부에 있는지 확인해야 합니다.

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

오류 1. 요소를 찾을 수 없음

NoSuchElementException

원인

* 선택자가 잘못됨* 요소가 아직 생성되지 않음* iframe 내부에 있음* 새 창에 있음* 로그인 후에만 보이는 요소임

해결

wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, ".target")
    )
)

오류 2. 요소를 클릭할 수 없음

ElementClickInterceptedException

원인

* 팝업이 버튼을 가림* 쿠키 동의 창이 가림* 로딩 화면이 남아 있음* 화면 밖에 요소가 있음* 애니메이션 진행 중

해결

button = wait.until(
    EC.element_to_be_clickable(
        (By.ID, "submit")
    )
)

driver.execute_script(
    "arguments[0].scrollIntoView("
    "{block: 'center'}"
    ");",
    button
)

button.click()

오류 3. 기존 요소가 사라짐

StaleElementReferenceException

원인

페이지가 다시 렌더링되면서 기존 요소가 교체된 경우입니다.

해결

요소를 다시 찾습니다.

for _ in range(3):
    try:
        item = driver.find_element(
            By.CSS_SELECTOR,
            ".item"
        )

        print(item.text)
        break

    except Exception:
        continue

무작정 모든 예외를 잡기보다는 실제 코드에서는 구체적인 예외 클래스를 사용하는 것이 좋습니다.

오류 4. Playwright 시간 초과

TimeoutError

원인

* 요소가 나타나지 않음* 선택자가 틀림* 로그인 실패* 팝업이나 로딩 화면이 가림* 예상 URL로 이동하지 않음

해결

스크린샷을 저장하고 현재 URL을 확인합니다.

print(page.url)

page.screenshot(
    path="timeout.png",
    full_page=True
)

필요한 경우 제한 시간을 조정할 수 있습니다.

page.set_default_timeout(
    15000
)

단순히 제한 시간을 크게 늘리기 전에, 요소가 정말 나타나는지부터 확인해야 합니다.

28. 안정적인 자동화 코드를 만드는 방법

고정 시간 대기를 줄인다

time.sleep(10)

보다 다음 방식이 좋습니다.

wait.until(
    EC.visibility_of_element_located(
        (By.ID, "result")
    )
)

지나치게 긴 CSS 선택자를 피한다

다음 선택자는 화면 구조가 조금만 변경되어도 깨질 수 있습니다.

body > div:nth-child(3) > div > ul > li:nth-child(2)

가능하면 의미 있는 속성을 활용합니다.

button[data-testid="login-button"]

또는 Playwright의 역할 기반 Locator를 사용합니다.

page.get_by_role(
    "button",
    name="로그인"
)

브라우저 종료를 보장한다

Selenium에서는 `finally`를 사용합니다.

try:
    ...
finally:
    driver.quit()

Playwright에서는 `with` 구문을 활용합니다.

with sync_playwright() as p:
    ...

재시도 횟수를 제한한다

MAX_RETRIES = 3

무한 재시도 코드는 장애를 해결하는 것이 아니라 장애를 밤새 반복하는 기계가 될 수 있습니다.

로그를 남긴다

import logging

logging.basicConfig(
    level=logging.INFO,
    format=(
        "%(asctime)s "
        "%(levelname)s "
        "%(message)s"
    )
)

logging.info("페이지 접속 시작")

중복 데이터를 방지한다

seen_urls = set()

if article_url not in seen_urls:
    seen_urls.add(article_url)

요청 간격을 둔다

import random
import time

time.sleep(
    random.uniform(1.0, 2.5)
)

이는 탐지 회피가 아니라 웹사이트에 불필요한 부하를 주지 않기 위한 기본적인 예의로 사용해야 합니다.

29. 브라우저 자동화는 실제 사람으로 보일까?

Selenium이나 Playwright가 브라우저를 실제로 실행하더라도 자동화 프로그램이 곧 실제 사람으로 인정되는 것은 아닙니다.

웹사이트는 다음과 같은 다양한 단서를 통해 자동화된 접근을 판단할 수 있습니다.

* 비정상적으로 빠른 클릭 간격* 일정한 반복 패턴* 짧은 시간 동안 발생하는 대량 요청* 브라우저 자동화 환경의 특징* 마우스와 키보드 행동의 부재* 동일한 계정의 반복 로그인* 비정상적인 페이지 이동 순서

따라서 자동화 도구를 사용한다고 해서 사람의 정상적인 이용 행위로 자동 분류된다고 생각해서는 안 됩니다.

특히 다음 행동은 피해야 합니다.

* CAPTCHA 우회* 접근 제한 우회* 계정 대량 생성* 티켓이나 상품의 부정 구매* 타인의 계정 자동 로그인* 개인정보 무단 수집* 서비스 장애를 일으킬 정도의 반복 요청* 명시적인 자동화 금지 정책 위반

브라우저를 조작할 기술과 조작할 권한은 서로 다른 문제입니다.

30. 미니 프로젝트: JavaScript 페이지 수집기

이번에는 Playwright를 사용해 JavaScript 페이지의 데이터를 수집하고 CSV로 저장하는 프로그램을 만들어 보겠습니다.

import csv
from playwright.sync_api import sync_playwright

TARGET_URL = "https://quotes.toscrape.com/js/"
OUTPUT_FILE = "javascript_quotes.csv"


def collect_quotes() -> list[dict[str, str]]:
    results = []

    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True
        )

        page = browser.new_page(
            viewport={
                "width": 1280,
                "height": 900,
            }
        )

        try:
            page.goto(
                TARGET_URL,
                wait_until="domcontentloaded"
            )

            page.locator(
                ".quote"
            ).first.wait_for()

            while True:
                cards = page.locator(".quote")
                card_count = cards.count()

                for index in range(card_count):
                    card = cards.nth(index)

                    text = card.locator(
                        ".text"
                    ).inner_text()

                    author = card.locator(
                        ".author"
                    ).inner_text()

                    results.append(
                        {
                            "text": text,
                            "author": author,
                        }
                    )

                print(
                    f"현재까지 {len(results)}개 수집"
                )

                next_link = page.locator(
                    "li.next > a"
                )

                if next_link.count() == 0:
                    break

                next_link.click()

            page.screenshot(
                path="collection_complete.png",
                full_page=True
            )

        except Exception:
            page.screenshot(
                path="collection_error.png",
                full_page=True
            )

            raise

        finally:
            browser.close()

    return results


def save_csv(
    results: list[dict[str, str]]
) -> None:
    with open(
        OUTPUT_FILE,
        "w",
        newline="",
        encoding="utf-8-sig"
    ) as file:
        writer = csv.DictWriter(
            file,
            fieldnames=[
                "text",
                "author",
            ]
        )

        writer.writeheader()
        writer.writerows(results)


def main() -> None:
    results = collect_quotes()
    save_csv(results)

    print(
        f"{OUTPUT_FILE} 저장 완료"
    )


if __name__ == "__main__":
    main()

프로그램 실행 흐름

Playwright 실행
    ↓
Chromium 브라우저 실행
    ↓
JavaScript 페이지 접속
    ↓
명언 카드가 나타날 때까지 대기
    ↓
텍스트와 작가 수집
    ↓
다음 페이지 이동
    ↓
마지막 페이지까지 반복
    ↓
스크린샷 저장
    ↓
CSV 파일 저장

이 구조를 변경하면 뉴스, 상품, 게시판, 모니터링 페이지 등 다양한 곳에 활용할 수 있습니다.

실제 웹사이트에 적용할 때는 다음 부분을 대상 사이트에 맞게 변경합니다.

TARGET_URL = "수집할 페이지 주소"
cards = page.locator(
    "게시물 하나를 나타내는 선택자"
)
title = card.locator(
    "제목 선택자"
).inner_text()
url = card.locator(
    "a"
).get_attribute("href")

31. requests와 브라우저 자동화 중 무엇을 사용할까?

무조건 Selenium이나 Playwright를 사용하는 것이 좋은 것은 아닙니다.

브라우저 자동화는 실제 브라우저를 실행하기 때문에 상대적으로 많은 CPU와 메모리를 사용합니다.

다음 순서로 접근 방법을 결정하는 것이 좋습니다.

1단계: 공식 API가 있는가?

공식 API가 있다면 API를 우선 사용합니다.

공식 API
→ 빠름
→ 구조화된 데이터
→ 안정적
→ 불필요한 브라우저 실행 없음

2단계: HTML 안에 데이터가 있는가?

HTML에 데이터가 있다면 `requests`와 BeautifulSoup을 사용합니다.

requests
BeautifulSoup

3단계: 내부 API를 합법적으로 사용할 수 있는가?

웹페이지가 호출하는 API를 개발자 도구에서 확인할 수 있습니다.

단, 인증과 이용 조건을 확인해야 합니다.

4단계: JavaScript 실행이 꼭 필요한가?

JavaScript 실행, 클릭, 스크롤, 로그인 등이 반드시 필요하다면 Selenium이나 Playwright를 사용합니다.

공식 API
   ↓ 없다면

requests + BeautifulSoup
   ↓ 불가능하다면

허용된 API 요청 분석
   ↓ 그래도 브라우저가 필요하다면

Selenium 또는 Playwright

브라우저 자동화는 강력하지만 무거운 도구입니다.

문을 열기 위해 굴착기를 부를 필요가 없는 상황도 많습니다.

32. 연습 문제

문제 1

Selenium을 이용해 Python 공식 홈페이지에 접속하고 페이지 제목을 출력해 보세요.

힌트

driver.title

문제 2

Selenium 공식 테스트 페이지에 접속해 입력창에 자신의 이름을 입력하고 제출 버튼을 클릭해 보세요.

힌트

send_keys()
click()

문제 3

Playwright를 이용해 다음 페이지의 제목을 출력해 보세요.

https://www.python.org

문제 4

Playwright로 명언 페이지의 첫 번째 명언과 작가 이름만 출력해 보세요.

힌트

page.locator(".quote").first

문제 5

Selenium으로 현재 페이지 전체 화면을 이미지로 저장해 보세요.

힌트

driver.save_screenshot()

문제 6

Playwright로 브라우저 화면 크기를 모바일 크기로 설정해 보세요.

힌트

page = browser.new_page(
    viewport={
        "width": 390,
        "height": 844,
    }
)

문제 7

다음 중 브라우저 자동화가 꼭 필요하지 않을 가능성이 가장 높은 경우는 무엇일까요?

1. JavaScript로 데이터가 나중에 표시되는 페이지2. 버튼을 눌러야 다음 데이터가 나타나는 페이지3. 공식 JSON API가 제공되는 서비스4. 로그인이 필요한 관리자 페이지

정답은 3번입니다.

공식 API가 있다면 브라우저 자동화보다 API 사용을 먼저 검토하는 것이 좋습니다.

33. 핵심 요약

Selenium

오랜 역사
넓은 생태계
WebDriver 기반
명시적 대기 중요
Selenium Grid 활용 가능
기존 자동화 시스템에 강점

기본 설치:

pip install selenium

기본 실행:

from selenium import webdriver

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

Playwright

현대적인 API
강력한 자동 대기
Locator 중심
Browser Context 지원
동적 웹앱 자동화에 편리
코드가 비교적 간결함

기본 설치:

pip install playwright
playwright install

기본 실행:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")
    browser.close()

가장 중요한 원칙

API가 있다면 API를 우선한다.

정적 HTML이면 requests를 사용한다.

JavaScript 실행이 필요하면
Selenium 또는 Playwright를 사용한다.

고정된 sleep보다
조건 기반 대기를 사용한다.

자동화 가능 여부와
자동화 권한은 구분한다.

34. 마무리

이번 시간에는 Selenium과 Playwright를 사용해 Python으로 실제 웹 브라우저를 조작하는 방법을 알아보았습니다.

이제 Python은 단순히 HTML을 내려받는 것을 넘어 다음과 같은 작업을 수행할 수 있습니다.

* 브라우저 실행* JavaScript 웹페이지 접속* 검색어 입력* 버튼 클릭* 페이지 이동* 스크롤* 새 창 처리* iframe 조작* 로그인 흐름 자동화* 동적 데이터 수집* CSV 저장* 스크린샷 기록

Selenium은 오랜 시간 수많은 자동화 현장을 지켜 온 든든한 베테랑입니다.

Playwright는 자동 대기와 Locator라는 영리한 장비를 들고 등장한 현대적인 자동화 도구입니다.

어느 도구를 선택하든 가장 중요한 것은 웹사이트의 구조를 이해하고, 안정적인 선택자와 대기 조건을 설계하는 것입니다.

브라우저 자동화는 단순히 클릭을 대신해 주는 기술이 아닙니다.

사람이 반복하던 웹 작업을 코드라는 작은 로봇에게 넘겨주는 과정입니다.

이제 브라우저에서 반복 작업이 보이면 이렇게 생각해 볼 수 있습니다.

“잠깐, 이거 Python에게 시킬 수 있지 않을까?” 🤖🖱️

다음 편 예고

[Python 완전정복 시리즈 #24] pandas 완벽 이해하기 | 수천 개의 데이터를 표처럼 분석하고 정리하는 방법

웹에서 데이터를 가져왔다면 이제 정리할 차례입니다.

다음 시간에는 CSV와 Excel 데이터를 불러오고, 필요한 행을 찾고, 데이터를 정렬하고, 통계까지 계산할 수 있는 pandas를 알아보겠습니다.

#Python #파이썬 #Python강좌 #파이썬기초 #Selenium #Playwright #웹크롤링 #브라우저자동화 #웹자동화 #크롤링 #JavaScript크롤링 #동적웹크롤링 #PythonSelenium #PythonPlaywright #코딩공부 #프로그래밍 #개발자

댓글

0

댓글을 불러오는 중입니다.