[실무 강의] Playwright & Python: 동적 웹페이지에서 실시간 데이터 10분 만에 즉시 추출하기 완벽 마스터 튜토리얼

학습 목표: 왜 2026년에도 Playwright인가?

과거의 정적인 웹 페이지와 달리, 현대의 웹은 React, Vue, Next.js와 같은 프레임워크를 기반으로 한 동적 렌더링이 주를 이룹니다. 단순히 BeautifulSoup이나 Requests 라이브러리만으로는 자바스크립트가 실행된 후의 최종 데이터를 가져올 수 없습니다. 본 강의에서는 2026년 현재 가장 강력하고 빠른 브라우저 자동화 도구인 PlaywrightPython을 결합하여, 복잡한 로그인이나 무한 스크롤이 포함된 동적 웹페이지에서 실시간 데이터를 단 10분 만에 추출하는 실무 기술을 전수합니다.

이 과정을 통해 여러분은 데이터 수집의 병목 현상을 해결하고 자동화 파이프라인을 구축하는 능력을 갖추게 될 것입니다.

사전 준비 사항

실습을 시작하기 전에 다음 환경이 구성되어 있는지 확인하십시오. 2026년 기준 최신 안정화 버전을 권장합니다.

  • 운영체제(OS): Windows 11 이상, macOS 14(Sonoma) 이상, 또는 Ubuntu 24.04 LTS
  • 코드 에디터: VS Code (Visual Studio Code) 최신 버전 권장
  • 파이썬 버전: Python 3.11 이상 (3.12 버전 적극 권장)
  • 필수 라이브러리 설치: 터미널(Terminal) 또는 CMD에서 아래 명령어를 순차적으로 입력합니다.
# Playwright 라이브러리 설치
pip install playwright

# 필요한 브라우저 바이너리(Chromium, Firefox, WebKit) 설치
playwright install chromium

단계별 실습 과정

1단계: Playwright 기본 스켈레톤 코드 작성

Playwright는 동기(Sync) 방식과 비동기(Async) 방식을 모두 지원합니다. 실무에서는 대량의 데이터를 처리할 때 비동기 방식을 선호하지만, 학습 초기에는 흐름을 파악하기 쉬운 동기 방식을 먼저 익히는 것이 좋습니다. 아래 코드는 브라우저를 실행하고 특정 페이지에 접속하는 가장 기초적인 구조입니다.

from playwright.sync_api import sync_playwright

def run():
    with sync_playwright() as p:
        # headless=False로 설정하면 브라우저가 뜨는 것을 직접 볼 수 있습니다.
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://example.com")
        print(f"페이지 제목: {page.title()}")
        browser.close()

if __name__ == "__main__":
    run()

2단계: 동적 요소 대기 및 데이터 추출 전략

동적 웹페이지의 핵심은 ‘데이터가 로드될 때까지 기다리는 것’입니다. Playwright는 wait_for_selector 기능을 통해 특정 요소가 화면에 나타날 때까지 지능적으로 대기합니다. 이는 과거 Selenium에서 사용하던 수동적인 time.sleep()보다 훨씬 효율적이고 안정적입니다.

실시간 주식 정보나 뉴스 헤드라인을 가져오는 시나리오를 가정해 보겠습니다.

from playwright.sync_api import sync_playwright

def extract_dynamic_data():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        
        # 대상 URL 접속 (예: 실시간 데이터가 포함된 대시보드)
        page.goto("https://www.scrapingcourse.com/ecommerce/")
        
        # 제품 목록 요소가 렌더링될 때까지 최대 10초 대기
        page.wait_for_selector(".product")
        
        # 모든 제품 이름과 가격 추출
        products = page.query_selector_all(".product")
        
        results = []
        for product in products:
            name = product.query_selector("h2").inner_text()
            price = product.query_selector(".price").inner_text()
            results.append({"name": name, "price": price})
            
        for item in results:
            print(f"상품명: {item['name']} | 가격: {item['price']}")
            
        browser.close()

extract_dynamic_data()

3단계: 무한 스크롤 및 복잡한 인터렉션 처리

많은 현대적 웹사이트는 스크롤을 내릴 때마다 새로운 데이터를 로드합니다. Playwright에서는 mouse.wheel이나 evaluate를 사용하여 자바스크립트 명령어를 직접 실행할 수 있습니다. 이를 통해 페이지 하단까지 스크롤하여 모든 데이터를 노출시킨 후 한꺼번에 수집하는 기법을 적용해 보겠습니다.

def scroll_and_extract():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://example.com/infinite-scroll")

        # 5번 스크롤 반복
        for _ in range(5):
            page.mouse.wheel(0, 1000)
            page.wait_for_timeout(2000) # 데이터 로딩 대기 (ms 단위)

        # 업데이트된 전체 데이터 추출
        items = page.locator(".item-list-element").all_inner_texts()
        print(f"총 추출된 항목 수: {len(items)}")
        browser.close()

결과 확인 및 실무 적용 팁

위의 코드를 실행하면 터미널에 실시간으로 추출된 데이터 리스트가 출력되는 것을 확인할 수 있습니다. Playwright는 네트워크 요청을 가로채거나(Interception), 특정 API 응답을 기다리는 기능도 제공하므로 단순한 HTML 파싱 이상의 정교한 데이터 수집이 가능합니다.

성능 최적화 및 유지보수

  • Headless Mode: 실제 배포 환경에서는 headless=True로 설정하여 리소스 소모를 최소화하십시오.
  • User-Agent 설정: 봇 차단을 방지하기 위해 실제 브라우저와 유사한 User-Agent 헤더를 추가하는 것이 좋습니다.
  • 에러 처리: try-except 블록을 활용하여 네트워크 타임아웃이나 요소 미발견 상황에 대비하십시오.

이제 여러분은 파이썬과 Playwright를 활용해 그 어떤 까다로운 동적 웹사이트라도 자유자재로 요리할 수 있는 기초 체력을 갖추게 되었습니다. 오늘 배운 내용을 바탕으로 자신만의 자동화 데이터 수집기를 구축해 보시기 바랍니다.

댓글 남기기


Warning: getimagesize(): https:// wrapper is disabled in the server configuration by allow_url_fopen=0 in /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/media.cls.php on line 1158

Warning: getimagesize(https://velog.velcdn.com/images/hun-ing/post/04f5206c-efb6-471b-bdb1-c36f88868f74/image.png): Failed to open stream: no suitable wrapper could be found in /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/media.cls.php on line 1158

Fatal error: Uncaught ErrorException: md5_file(/hosting/apdldk/html/wp-content/litespeed/css/760bfc00fc47d122c0c72f587167e892.css.tmp): Failed to open stream: No such file or directory in /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimizer.cls.php:148 Stack trace: #0 [internal function]: litespeed_exception_handler() #1 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimizer.cls.php(148): md5_file() #2 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimize.cls.php(845): LiteSpeed\Optimizer->serve() #3 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimize.cls.php(338): LiteSpeed\Optimize->_build_hash_url() #4 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimize.cls.php(265): LiteSpeed\Optimize->_optimize() #5 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimize.cls.php(226): LiteSpeed\Optimize->_finalize() #6 /hosting/apdldk/html/wp-includes/class-wp-hook.php(341): LiteSpeed\Optimize->finalize() #7 /hosting/apdldk/html/wp-includes/plugin.php(205): WP_Hook->apply_filters() #8 /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/core.cls.php(464): apply_filters() #9 [internal function]: LiteSpeed\Core->send_headers_force() #10 /hosting/apdldk/html/wp-includes/functions.php(5481): ob_end_flush() #11 /hosting/apdldk/html/wp-includes/class-wp-hook.php(341): wp_ob_end_flush_all() #12 /hosting/apdldk/html/wp-includes/class-wp-hook.php(365): WP_Hook->apply_filters() #13 /hosting/apdldk/html/wp-includes/plugin.php(522): WP_Hook->do_action() #14 /hosting/apdldk/html/wp-includes/load.php(1308): do_action() #15 [internal function]: shutdown_action_hook() #16 {main} thrown in /hosting/apdldk/html/wp-content/plugins/litespeed-cache/src/optimizer.cls.php on line 148