[실무 강의] 2026년 필수 매크로: Python Playwright로 웹페이지 데이터 싹쓸이 자동화 5분 실습 완벽 마스터 튜토리얼

학습 목표: 2026년 웹 자동화의 표준, Playwright 마스터하기

2026년 현재, 웹 기술은 그 어느 때보다 복잡해졌습니다. 단순한 정적 페이지를 넘어 동적인 싱글 페이지 애플리케이션(SPA)과 강력한 보안 솔루션이 기본이 된 시대입니다. 과거에 주로 사용되던 Selenium은 무겁고 느린 속도, 그리고 복잡한 드라이버 설정 문제로 인해 현업에서 점차 멀어지고 있습니다. 오늘 이 시간에는 구글, 마이크로소프트 등 글로벌 IT 기업들이 표준으로 채택하고 있는 Playwright를 활용하여, 단 5분 만에 웹페이지의 데이터를 안전하고 빠르게 추출하는 실무 매크로 제작법을 배웁니다.

본 강의를 통해 여러분은 비동기 프로그래밍(Asyncio)을 기반으로 한 고성능 크롤러의 구조를 이해하고, 실제 상용 사이트에서도 막힘없이 작동하는 자동화 스크립트를 직접 구축하게 될 것입니다.

사전 준비 사항

실습을 시작하기 전에 아래의 환경이 갖춰져 있는지 확인하십시오. 2026년 표준 개발 환경을 기준으로 구성되었습니다.

1. 개발 환경 정보

  • 운영체제(OS): Windows 11, macOS Sequoia, 또는 Ubuntu 24.04 LTS 이상
  • 코드 에디터: VS Code (Visual Studio Code) 최신 버전 권장
  • 파이썬 버전: Python 3.12 버전 이상 (성능과 보안을 위해 최신 스테이블 버전 권장)

2. 필수 라이브러리 설치

터미널(Terminal) 또는 명령 프롬프트(CMD)를 열고 아래 명령어를 순차적으로 입력하여 필요한 패키지를 설치합니다. Playwright는 브라우저 엔진을 자체적으로 관리하므로 별도의 크롬 드라이버 설치가 필요 없습니다.

# Playwright 라이브러리 설치
pip install playwright

# 필요한 브라우저 엔진(Chromium, Firefox, WebKit) 설치
python -m playwright install

단계별 실습 과정

Step 1: 프로젝트 초기화 및 비동기 구조 설계

Playwright는 현대적인 파이썬의 비동기(Async/Await) 문법을 완벽하게 지원합니다. 이는 여러 페이지를 동시에 처리하거나 네트워크 대기 시간을 최소화하는 데 핵심적인 역할을 합니다. 먼저 main.py 파일을 생성하고 기본 골격을 작성합니다.

import asyncio
from playwright.async_api import async_playwright

async def run_macro():
    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://news.google.com")
        print("페이지 접속 성공!")
        
        await browser.close()

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

Step 2: 데이터 타겟팅 및 로케이터(Locator) 활용

2026년의 웹사이트들은 클래스명이 수시로 변하는 난독화 기법을 자주 사용합니다. 따라서 우리는 Playwright의 강력한 Locator 기능을 사용하여 텍스트, 속성, 또는 계층 구조로 데이터를 정확히 짚어내야 합니다. 구글 뉴스의 헤드라인 제목들을 수집해 보겠습니다.

# ... 이전 코드 생략 ...
        # 뉴스 제목 요소들이 로드될 때까지 대기
        await page.wait_for_selector('article h4')

        # 모든 뉴스 제목 요소 가져오기
        headlines = await page.locator('article h4').all()
        
        extracted_data = []
        for index, headline in enumerate(headlines[:10]): # 상위 10개만 추출
            title = await headline.inner_text()
            extracted_data.append(title)
            print(f"{index + 1}: {title}")
# ... 이후 코드 생략 ...

Step 3: 동적 스크롤 및 네트워크 대기 처리

대부분의 현대 웹사이트는 스크롤을 내릴 때 데이터가 로딩되는 ‘인피니트 스크롤’ 방식을 사용합니다. 단순 접속만으로는 모든 데이터를 가져올 수 없습니다. 아래 코드를 추가하여 페이지 끝까지 스크롤하는 동작을 구현합니다.

        # 페이지 하단으로 스크롤 이동 (동적 로딩 트리거)
        await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        # 네트워크 활동이 잦아들 때까지 잠시 대기
        await page.wait_for_load_state("networkidle")

Step 4: 데이터 저장 자동화 (CSV/JSON)

수집한 데이터를 실무에서 바로 활용할 수 있도록 파일로 저장하는 과정이 필요합니다. 파이썬의 기본 json 모듈을 사용하여 수집한 헤드라인을 저장해 보겠습니다.

import json

# ... 데이터 추출 로직 수행 후 ...
        with open("scraped_data.json", "w", encoding="utf-8") as f:
            json.dump(extracted_data, f, ensure_ascii=False, indent=4)
        print("데이터 저장 완료: scraped_data.json")

Step 5: 전체 통합 코드 예시

위의 모든 단계를 하나로 합친 완성된 실무 매크로 코드입니다. 이 코드는 실제 환경에서 즉시 실행 가능합니다.

import asyncio
import json
from playwright.async_api import async_playwright

async def start_scraping():
    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/120.0.0.0 Safari/537.36"
        )
        page = await context.new_page()

        print("[상태] 대상 사이트 접속 중...")
        await page.goto("https://news.google.com", wait_until="domcontentloaded")

        # 동적 요소 로딩 대기
        await page.wait_for_selector('article h4', timeout=10000)

        # 데이터 추출
        headlines = await page.locator('article h4').all()
        results = []

        for item in headlines[:20]:
            text = await item.inner_text()
            if text:
                results.append({"title": text.strip()})

        # 파일 저장
        with open("news_results.json", "w", encoding="utf-8") as f:
            json.dump(results, f, ensure_ascii=False, indent=4)

        print(f"[성공] 총 {len(results)}개의 데이터를 수집하여 저장했습니다.")
        await browser.close()

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

결과 확인

1. 실행 및 로그 확인

터미널에서 python main.py를 실행합니다. 콘솔창에 “[성공] 총 20개의 데이터를 수집하여 저장했습니다.”라는 메시지가 출력되면 정상적으로 작동한 것입니다.

2. 산출물 검증

프로젝트 폴더 내에 생성된 news_results.json 파일을 엽니다. 아래와 같은 구조로 데이터가 정제되어 들어있다면 성공입니다.

[
    {
        "title": "2026년 글로벌 IT 시장의 변화와 전망"
    },
    {
        "title": "차세대 AI 모델 발표, 개발자 생태계 뒤흔드나"
    }
    ... (생략)
]

3. 유지보수 팁

  • Selector 수정: 웹사이트의 구조가 변경되어 데이터를 못 가져올 경우, 브라우저 개발자 도구(F12)를 열어 해당 요소의 CSS Selector나 XPath를 다시 확인하세요.
  • 속도 조절: 너무 빠른 요청은 IP 차단의 원인이 됩니다. 필요시 await asyncio.sleep(1)을 추가하여 지연 시간을 조절하십시오.
  • Headless 모드: 서버(Linux) 환경에서 배포할 때는 launch(headless=True) 옵션을 사용하여 리소스를 절약하세요.

이제 여러분은 2026년 가장 강력한 웹 자동화 도구인 Playwright를 자유자재로 다룰 수 있는 기초 체력을 갖추었습니다. 이 기술을 바탕으로 반복적인 데이터 수집 업무를 자동화하여 업무 효율을 극대화해 보시기 바랍니다.

댓글 남기기


Fatal error: Uncaught ErrorException: md5_file(/hosting/apdldk/html/wp-content/litespeed/css/5f0ebd3663f092024cc1364e7254e442.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