[실무 강의] **2026년 실무 필수! Playwright & Python으로 동적 웹페이지 데이터 즉시 크롤링** 완벽 마스터 튜토리얼

1. 강의 목표: 왜 2026년에도 Playwright인가?

2026년 현재, 웹 기술은 더욱 복잡해졌습니다. 대부분의 기업용 대시보드와 커머스 사이트는 React, Vue, Next.js와 같은 프레임워크를 기반으로 실시간 데이터 바인딩을 수행합니다. 과거의 BeautifulSoup이나 단순한 Selenium만으로는 자바스크립트가 실행된 후의 데이터를 가져오는 데 한계가 있습니다. 본 강의에서는 마이크로소프트에서 개발한 강력한 브라우저 자동화 라이브러리인 PlaywrightPython을 결합하여, 속도와 안정성을 모두 잡은 동적 웹페이지 크롤링 기술을 습득합니다. 비동기 처리(Asyncio)를 통해 수백 개의 페이지를 단 몇 초 만에 처리하는 실무 최적화 기법을 배우게 될 것입니다.

2. 사전 준비 사항

실습을 시작하기 전에 아래 환경이 구축되어 있어야 합니다. 2026년 표준 환경에 맞춰 설정되었습니다.

  • 운영체제(OS): Windows 11 이상, macOS Sequoia 이상, 또는 Ubuntu 24.04 LTS
  • 편집기: VS Code (Visual Studio Code) 최신 버전 추천
  • 파이썬 버전: Python 3.14 이상 (최신 비동기 문법 최적화 활용)
  • 필수 라이브러리 설치: 터미널(CMD/Terminal)에서 아래 명령어를 실행하세요.
# 라이브러리 설치
pip install playwright

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

3. 단계별 실습 과정

단계 1: 프로젝트 초기화 및 기본 구조 설계

Playwright는 동기(Sync)와 비동기(Async) 방식을 모두 지원하지만, 실무에서는 대량의 데이터를 빠르게 처리하기 위해 비동기 방식을 주로 사용합니다. 먼저 main.py 파일을 생성하고 기본 골격을 작성합니다.

import asyncio
from playwright.async_api import async_playwright

async def run():
    async with async_playwright() as p:
        # 브라우저 실행 (headless=False로 설정하면 작동 과정을 눈으로 볼 수 있음)
        browser = await p.chromium.launch(headless=False)
        page = await browser.new_page()
        
        # 대상 URL 접속
        await page.goto('https://example.com/dynamic-data')
        
        print("페이지 접속 완료")
        await browser.close()

if __name__ == "__main__":
    asyncio.run(run())

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

동적 웹페이지의 가장 큰 특징은 페이지 로드 후 자바스크립트가 데이터를 불러온다는 점입니다. 이를 위해 특정 요소가 나타날 때까지 기다리는 wait_for_selector 기능이 필수적입니다.

async def extract_data(page):
    # 특정 데이터 리스트가 로드될 때까지 최대 10초 대기
    selector = '.product-list-item'
    await page.wait_for_selector(selector, timeout=10000)
    
    # 모든 아이템 요소 가져오기
    items = await page.query_selector_all(selector)
    
    results = []
    for item in items:
        # 제목과 가격 추출
        title_el = await item.query_selector('.title')
        price_el = await item.query_selector('.price')
        
        title = await title_el.inner_text() if title_el else "N/A"
        price = await price_el.inner_text() if price_el else "0"
        
        results.append({"title": title, "price": price})
    
    return results

단계 3: 무한 스크롤 처리 및 네트워크 가로채기

많은 현대적 웹사이트는 스크롤을 내릴 때마다 데이터를 추가로 불러옵니다. Playwright의 mouse.wheel 기능을 사용하여 이를 자동화할 수 있습니다. 또한, API 응답을 직접 가로채어(Intercept) 파싱 속도를 극대화하는 기법도 포함합니다.

async def scroll_to_bottom(page):
    # 화면 끝까지 스크롤 반복
    prev_height = 0
    while True:
        await page.mouse.wheel(0, 1000)
        await page.wait_for_timeout(1000) # 로딩 대기
        
        curr_height = await page.evaluate("document.body.scrollHeight")
        if curr_height == prev_height:
            break
        prev_height = curr_height
    print("스크롤 완료")

단계 4: 안티 크롤링 우회 및 컨텍스트 관리

2026년의 웹사이트들은 더욱 강력한 봇 탐지 시스템을 갖추고 있습니다. User-Agent 설정과 Viewport 조정을 통해 일반 사용자로 위장하는 과정이 필요합니다.

async def secure_run():
    async with async_playwright() as p:
        # 브라우저 컨텍스트 설정 (모바일 기기 환경 모방 가능)
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
            viewport={'width': 1920, 'height': 1080}
        )
        page = await context.new_page()
        
        # 실제 작업 수행...
        await page.goto('https://target-site.com')
        # ... 생략 ...
        await browser.close()

4. 결과 확인 및 데이터 저장

추출된 데이터를 CSV 또는 JSON 파일로 저장하여 실무에서 즉시 활용 가능한 결과물을 만듭니다. Python의 내장 json 모듈이나 pandas 라이브러리를 활용하면 효율적입니다.

import json

def save_results(data):
    with open('output_2026.json', 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=4)
    print(f"총 {len(data)}건의 데이터 저장 완료.")

# 전체 프로세스 실행 예시
# data = await extract_data(page)
# save_results(data)

5. 실무 핵심 요약

본 강의를 통해 여러분은 다음과 같은 핵심 역량을 확보했습니다.

  • 비동기 처리: asyncio를 활용한 고성능 크롤링 구조 설계
  • 동적 제어: 자바스크립트 렌더링 완료 시점을 정확히 포착하는 대기 전략
  • 유연성: Chromium뿐만 아니라 Firefox, WebKit 등 다양한 브라우저 엔진 대응
  • 안정성: 브라우저 컨텍스트 격리를 통한 세션 및 쿠키 관리

이제 여러분은 2026년의 복잡한 웹 환경에서도 막힘없이 데이터를 수집할 수 있는 실무 기술을 갖추게 되었습니다. 위 코드를 기반으로 대상 사이트의 구조에 맞춰 셀렉터를 수정하며 실습을 반복해 보시기 바랍니다.

댓글 남기기


Fatal error: Uncaught ErrorException: md5_file(/hosting/apdldk/html/wp-content/litespeed/js/5303c704c97f105dcf9dcc3b3879d1a1.js.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(392): 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