작성일:

Playwright 가이드: 웹 자동화

1. Playwright란?

Playwright는 웹 브라우저 자동화를 위한 도구로, Selenium의 대안으로 설계되었습니다. 주요 용도는 다음과 같습니다:

  • 웹 애플리케이션 테스트 자동화
  • 웹 스크래핑 및 데이터 추출
  • 반복적인 웹 작업 자동화
  • 크로스 브라우저 테스팅

주요 기능

  • 브라우저 제어: Chromium, Firefox, Webkit(Safari 엔진) 지원
  • DOM 조작: querySelectorlocator로 요소 검색 및 조작
  • 폼 자동화: 입력 및 제출 자동화
  • 스크린샷 캡처: 페이지 또는 요소 캡처
  • 헤드리스 모드: GUI 없는 실행

Playwright는 Selenium보다 최신 기술을 활용하며, 자동 대기와 간결한 API를 제공합니다.

Selenium과의 비교

기능 Selenium Playwright
브라우저 지원 별도 드라이버 필요 드라이버 내장
DOM 탐색 find_element 등 복잡 querySelectorlocator로 간결
동적 요소 처리 WebDriverWait로 명시적 대기 기본적으로 자동 대기 지원
설치 WebDriver 관리 필요 playwright install로 통합
속도 일부 작업에서 느릴 수 있음 최적화로 더 빠름

Playwright는 Selenium보다 설정이 간단하고, 동적 웹 페이지 처리에서 우수합니다.


2. 지원하는 브라우저

  • Chromium (Chrome과 유사)
  • Firefox
  • Webkit (Safari와 유사)

별도 드라이버 설치 없이 Playwright 자체에서 관리합니다.


3. 기본적인 웹 페이지 호출 방법 (Chromium 사용)

설치 명령어:

$ 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://www.example.com')
    browser.close()

비동기 방식: async_playwrightawait 사용 가능 (후술).


4. DOM을 찾기 위한 querySelectorlocator 사용법

querySelector

CSS 선택자로 단일 요소를 찾습니다:

element = page.query_selector('#search-input')

querySelectorAll

여러 요소를 리스트로 반환:

elements = page.query_selector_all('.example-class')
for el in elements:
    print(el.text_content())

locator

Playwright 고유의 기능으로, 동적 요소를 안정적으로 추적:

locator = page.locator('#search-input')
print(locator.text_content())

선택 방법

방법 설명
page.query_selector('#id') ID로 요소 찾기
page.query_selector('.class') 클래스명으로 요소 찾기
page.query_selector('tag') 태그명으로 요소 찾기
page.locator('text=Link') 텍스트로 요소 찾기

예시 (동기)

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

    # querySelector
    element = page.query_selector('#example-id')
    print(f"ID: {element.text_content()}")

    # locator
    locator = page.locator('div > p')
    print(f"Locator: {locator.text_content()}")

    browser.close()

동적 도메인 추가 (href 예시)

href 속성 값을 가져올 때, 상대 경로(/path)라면 현재 페이지 도메인을 동적으로 추가:

link = page.query_selector('a')
href = link.get_attribute('href')
base_url = page.url  # 현재 페이지 URL
from urllib.parse import urljoin
full_url = urljoin(base_url, href)  # 도메인 추가
print(f"Full URL: {full_url}")

5. 주요 DOM 조작 기능

click()

button = page.query_selector('#submit-button')
button.click()

fill()

input_field = page.query_selector('[name="username"]')
input_field.fill('myusername')

text_content()

paragraph = page.query_selector('.content')
print(paragraph.text_content())

get_attribute()

link = page.query_selector('a')
href = link.get_attribute('href')

6. 동적 웹 페이지 처리: wait_for_selector

동기 방식으로 요소가 나타날 때까지 대기:

node = page.wait_for_selector('a.ClassName', timeout=10000)  # 10초 대기

주요 메서드

메서드 설명
wait_for_selector 요소가 나타날 때까지 대기
wait_for_load_state 페이지 로드 완료 대기

예시

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

    button = page.wait_for_selector('#submit-button', timeout=10000)
    button.click()
    browser.close()

7. Headless 모드 및 기타 옵션

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

옵션

  • viewport={'width': 1200, 'height': 600}
  • args=['--user-agent=Mozilla/5.0 ...']

8. 비동기 방식 (aio) 사용 시 이슈 및 해결 방안

Playwright는 async_playwright로 비동기 실행을 지원합니다.

비동기 예시

from playwright.async_api import async_playwright
import asyncio

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto('https://www.example.com')
        await browser.close()

asyncio.run(main())

이슈 및 해결 방안

  1. 이슈: 비동기 코드의 복잡성
    • 동기 방식보다 코드가 복잡해지고, await 누락 시 오류 발생.
    • 해결: 간단한 작업은 동기 방식(sync_playwright) 사용 권장.
  2. 이슈: 타임아웃 문제
    • 비동기 작업 중 네트워크 지연으로 요소가 늦게 로드될 수 있음.
    • 해결: await page.wait_for_selector('#id', timeout=10000)로 명시적 대기 추가.
  3. 이슈: 이벤트 루프 충돌
    • 기존 asyncio 환경과 충돌 가능.
    • 해결: asyncio.run() 대신 기존 루프에 통합하거나, nest_asyncio 사용:
      import nest_asyncio
      nest_asyncio.apply()
      
  4. 이슈: 리소스 관리
    • 비동기 컨텍스트가 제대로 닫히지 않으면 메모리 누수 발생.
    • 해결: async with 문으로 자동 리소스 정리 보장.

9. connect_over_cdp를 사용한 Chromium 연결

Playwright는 connect_over_cdp를 통해 기존에 실행 중인 Chromium 인스턴스에 연결할 수 있습니다. 이는 Chrome DevTools Protocol(CDP)을 활용하며, 브라우저를 직접 제어하거나 디버깅할 때 유용합니다.

사용 방법 (동기 방식)

먼저, Chromium을 CDP 지원 모드로 실행합니다:

google-chrome --remote-debugging-port=9222

Playwright에서 연결:

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, devtools=True)
    page = browser.new_page()
    page.goto('https://example.com')
    browser.close()

Playwright는 Selenium보다 간결하고 강력한 기능을 제공하며, 동기와 비동기 방식을 모두 지원합니다. 동적 도메인 추가와 같은 실용적 기능도 쉽게 구현 가능합니다.

댓글남기기