브라우저 자동화 툴 Playwright과 루비 웹 스크랩핑

웹 스크래핑과 브라우저 자동화는 현대 웹 개발과 데이터 수집 영역에서 필수적인 기술이 되었습니다. 특히 동적 콘텐츠가 많은 현대 웹사이트를 다룰 때는 강력한 도구가 필요합니다. 이번 글에서는 마이크로소프트에서 개발한 강력한 브라우저 자동화 프레임워크인 Playwright를 소개하고, 이를 활용한 웹 스크래핑 기법을 자세히 살펴보겠습니다.

Playwright 소개: 개념과 주요 용도

Playwright란 무엇인가?

Playwright는 웹 브라우저 자동화 및 end-to-end 테스트를 위한 현대적인 프레임워크입니다. 기존의 Selenium이나 Puppeteer와 같은 도구들의 한계를 극복하기 위해 마이크로소프트에서 개발되었으며, 크로스 브라우저 테스트와 자동화에 중점을 두고 있습니다.

// Playwright의 기본 구조
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  // 다양한 작업 수행...
  await browser.close();
})();

Playwright의 주요 특징

Playwright는 다음과 같은 주요 특징을 가지고 있어 테스트와 스크래핑 작업에 매우 적합합니다:

  • 크로스 브라우저 지원: Chromium, Firefox, WebKit(Safari) 등 모든 주요 브라우저 엔진을 단일 API로 제어
  • 다양한 플랫폼 지원: Windows, Linux, macOS에서 동일하게 작동
  • 자동 대기 메커니즘: 요소의 로딩, 가시성, 활성화 등을 자동으로 처리하여 안정적인 테스트와 스크래핑 구현
  • 강력한 셀렉터 엔진: CSS, XPath, 텍스트 기반 선택자 등 다양한 방법으로 요소 검색
  • 네트워크 제어: 요청 가로채기, 모킹, 수정 등 네트워크 활동 제어
  • 모바일 에뮬레이션: 모바일 디바이스 환경 테스트 지원

Playwright을 이용한 테스트 자동화

Playwright의 원래 주요 목적은 웹 애플리케이션의 end-to-end 테스트입니다. 테스트 자동화 분야에서 Playwright는 어떤 장점과 기능을 제공하는지 살펴보겠습니다.

기본 테스트 작성 방법

Playwright Test 프레임워크를 사용하면 간결하고 강력한 테스트를 작성할 수 있습니다:

// example.spec.js
import { test, expect } from '@playwright/test';

test('기본 테스트', async ({ page }) => {
  // 페이지 이동
  await page.goto('https://playwright.dev/');

  // 타이틀 검증
  const title = page.locator('.navbar__inner .navbar__title');
  await expect(title).toHaveText('Playwright');

  // 링크 클릭
  await page.click('text=Get Started');

  // URL 검증
  await expect(page).toHaveURL(/.*intro/);
});

테스트 실행 방법

테스트를 실행하는 방법은 매우 간단합니다:

# 모든 테스트 실행
npx playwright test

# 특정 브라우저에서만 실행
npx playwright test --project=chromium

# UI 모드로 실행 (시각적 디버깅)
npx playwright test --ui

효과적인 테스트 전략

Playwright를 이용한 효과적인 테스트 전략은 다음과 같습니다:

  • 페이지 객체 모델 사용: 재사용 가능한 컴포넌트로 테스트 구조화
  • 자동 대기 활용: 명시적 지연 대신 Playwright의 자동 대기 메커니즘 활용
  • 병렬 실행: 여러 브라우저에서 동시에 테스트 실행하여 시간 단축
  • 시각적 비교 테스트: 스크린샷을 활용한 시각적 회귀 테스트
  • 네트워크 모킹: 테스트 환경 독립성을 위한 API 응답 모킹

Playwright를 활용한 웹 스크랩 개요

Playwright는 테스트 도구로 시작했지만, 강력한 브라우저 제어 기능 덕분에 웹 스크래핑에도 탁월한 성능을 발휘합니다.

웹 스크래핑에 적합한 이유

Playwright가 웹 스크래핑에 적합한 이유는 다음과 같습니다:

  1. JavaScript 렌더링 완전 지원: SPA(Single Page Application)와 같은 JavaScript 기반 애플리케이션도 완벽하게 렌더링
  2. 자동 대기 기능: 요소가 로드되고 상호작용 가능해질 때까지 자동으로 대기
  3. 강력한 네비게이션 제어: 다중 페이지, 탭, iframe 처리 가능
  4. 다양한 입력 방식: 키보드, 마우스, 터치 등 다양한 사용자 입력 시뮬레이션
  5. 인증 세션 관리: 로그인 상태 유지 및 쿠키 관리 용이

기본 스크래핑 패턴

Playwright를 이용한 기본적인 웹 스크래핑 패턴은 다음과 같습니다:

const { chromium } = require('playwright');

(async () => {
  // 브라우저 시작 (헤드리스 모드)
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();

  try {
    // 대상 페이지로 이동
    await page.goto('https://example.com');

    // 필요한 요소가 로드될 때까지 대기
    await page.waitForSelector('.content');

    // 데이터 추출
    const data = await page.evaluate(() => {
      // 브라우저 컨텍스트에서 실행되는 코드
      const items = Array.from(document.querySelectorAll('.item'));
      return items.map(item => ({
        title: item.querySelector('.title')?.textContent,
        description: item.querySelector('.description')?.textContent,
        url: item.querySelector('a')?.href
      }));
    });

    console.log('수집된 데이터:', data);

    // 결과 저장 (예: JSON 파일)
    require('fs').writeFileSync('results.json', JSON.stringify(data, null, 2));

  } catch (error) {
    console.error('스크래핑 중 오류 발생:', error);
  } finally {
    // 브라우저 종료
    await browser.close();
  }
})();

고급 스크래핑 기법

더 복잡한 웹사이트를 스크래핑하기 위한 고급 기법들:

  • 페이지네이션 처리: 여러 페이지에 걸친 데이터 수집
  • 무한 스크롤 처리: 스크롤 이벤트에 의해 로드되는 콘텐츠 수집
  • 인증 우회: 로그인이 필요한 페이지 접근
  • 차단 회피: IP 차단 및 CAPTCHA 우회 전략
  • 병렬 스크래핑: 여러 페이지 동시 처리

Ruby로 웹 스크랩하기 예제: Hackernews 웹스크랩

Playwright는 JavaScript 외에도 다양한 언어 바인딩을 제공합니다. 자바스크립트 예제는 많이 나와 있고 또 저는 프로젝트에서 루비를 많이 사용하기에 여기서도 Ruby를 사용하여 Hacker News 웹사이트를 스크래핑하는 실용적인 예제를 살펴보겠습니다.

Ruby용 Playwright 클라이언트 설정

Ruby에서 Playwright를 사용하기 위해 먼저 필요한 설정을 해봅시다:

# Gemfile
source 'https://rubygems.org'

gem 'playwright-ruby-client'
gem 'json'

Playwright 드라이버 설치:

npm init -y
npm install playwright
npx playwright install

HackerNews 스크래핑 코드

이제 Ruby를 사용하여 HackerNews의 주요 기사를 스크래핑하는 코드를 작성해보겠습니다:

# hackernews_scraper.rb
require 'playwright'
require 'json'

class HackerNewsScraper
  def initialize(max_retries: 3)
    @max_retries = max_retries
  end

  def scrape
    retries = 0

    while retries < @max_retries
      begin
        puts "스크래핑 시작 (시도 #{retries + 1}/#{@max_retries})..."
        articles = scrape_hackernews
        save_articles(articles)
        return articles
      rescue => e
        puts "오류 발생: #{e.message}"
        puts e.backtrace
        retries += 1

        if retries >= @max_retries
          puts "최대 재시도 횟수를 초과했습니다."
          raise e
        end

        sleep_time = retries * 3
        puts "#{sleep_time}  재시도합니다..."
        sleep(sleep_time)
      end
    end
  end

  private

  def scrape_hackernews
    articles = []

    Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') do |playwright|
      playwright.chromium.launch(headless: false) do |browser|
        puts '브라우저를 시작합니다...'

        #  컨텍스트 생성
        context = browser.new_context(
          viewport: { width: 1280, height: 800 },
          userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
        )

        page = context.new_page

        # 페이지 설정
        page.set_default_timeout(30000) # 30 타임아웃

        # HackerNews로 이동
        puts 'HackerNews로 이동합니다...'
        page.goto('https://news.ycombinator.com/', waitUntil: 'networkidle')

        # 스크린샷 촬영
        timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
        page.screenshot(path: "hackernews_#{timestamp}.png")

        puts '뉴스 기사를 수집합니다...'

        # 뉴스 항목 선택자
        items = page.locator('tr.athing')
        count = items.count

        puts "#{count}개의 항목을 발견했습니다."

        #  항목 처리
        (0...count).each do |i|
          item = items.nth(i)

          # 필요한 요소 추출
          id = item.get_attribute('id')
          title_element = item.locator('td.title > span.titleline > a')
          title = title_element.text_content
          url = title_element.get_attribute('href')

          site_element = item.locator('span.sitestr')
          site = site_element.count > 0 ? site_element.text_content : ''

          # 다음  찾기
          subtext = page.locator("tr:has(#score_#{id})").last

          # 점수 추출
          score_element = subtext.locator('.score')
          score = score_element.count > 0 ? score_element.text_content : 'No score'

          # 댓글  추출
          comment_element = subtext.locator("a[href=\"item?id=#{id}\"]").last
          comments = comment_element.count > 0 ? comment_element.text_content : '0 comments'

          # 작성자 추출
          author_element = subtext.locator('.hnuser')
          author = author_element.count > 0 ? author_element.text_content : 'Unknown'

          articles << {
            id: id,
            title: title,
            url: url,
            site: site,
            score: score,
            comments: comments,
            author: author
          }
        end

        puts "#{articles.length}개의 기사를 수집했습니다."
      end
    end

    articles
  end

  def save_articles(articles)
    timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
    filename = "hackernews_#{timestamp}.json"

    File.write(filename, JSON.pretty_generate(articles))
    puts "결과를 #{filename}에 저장했습니다."

    filename
  end
end

scraper = HackerNewsScraper.new
begin
  scraper.scrape
  puts "스크래핑이 성공적으로 완료되었습니다."
rescue => e
  puts "스크래핑 실패: #{e.message}"
end

위 스크립트를 실행하면 아래와 같이 스크랩한 결과가 JSON 파일에 저장됩니다.

데이터 분석 추가

이제 스크래핑한 데이터를 간단히 분석하는 코드를 추가해봅시다:

# analyzer.rb
require 'json'

class HackerNewsAnalyzer
  def initialize(filename)
    @filename = filename
    @articles = JSON.parse(File.read(filename), symbolize_names: true)
    puts "#{@articles.length}개의 기사를 분석합니다..."
  end

  def analyze
    # 점수 추출  숫자로 변환
    scores = @articles.map do |article|
      score_text = article[:score]
      score_match = score_text.match(/(\d+)/)
      score_match ? score_match[1].to_i : 0
    end

    # 기본 통계 계산
    total_score = scores.sum
    avg_score = total_score.to_f / scores.length
    max_score = scores.max
    min_score = scores.select { |s| s > 0 }.min

    # 도메인별  개수
    domain_counts = Hash.new(0)
    @articles.each do |article|
      domain = article[:site].to_s.empty? ? '(no domain)' : article[:site]
      domain_counts[domain] += 1
    end

    # 상위 도메인 추출
    top_domains = domain_counts.sort_by { |_, count| -count }.take(5)

    # 결과 출력
    puts "\n===== 분석 결과 ====="
    puts "총 기사 수: #{@articles.length}"
    puts "평균 점수: #{avg_score.round(2)} 포인트"
    puts "최고 점수: #{max_score} 포인트"
    puts "최저 점수: #{min_score} 포인트"

    puts "\n상위 5개 도메인:"
    top_domains.each_with_index do |(domain, count), index|
      puts "#{index + 1}. #{domain}: #{count}개 기사"
    end

    # 최고 점수 기사 찾기
    top_article = @articles.max_by do |article|
      score_match = article[:score].match(/(\d+)/)
      score_match ? score_match[1].to_i : 0
    end

    puts "\n최고 점수 기사:"
    puts "제목: #{top_article[:title]}"
    puts "URL: #{top_article[:url]}"
    puts "점수: #{top_article[:score]}"
    puts "작성자: #{top_article[:author]}"
  end
end

# 최신 파일 찾기
files = Dir['hackernews_*.json'].sort
if files.empty?
  puts "분석할 데이터 파일이 없습니다."
  exit(1)
end

latest_file = files.last
puts "최신 파일 #{latest_file}을 분석합니다."

analyzer = HackerNewsAnalyzer.new(latest_file)
analyzer.analyze

분석 결과는 다음과 같습니다(이 글을 쓰는 시점의 분석 결과입니다)

$ ruby analyzer.rb
최신 파일 hackernews_20250411_162217.json을 분석합니다.
30개의 기사를 분석합니다...

===== 분석 결과 =====
 기사: 30
평균 점수: 111.73 포인트
최고 점수: 578 포인트
최저 점수: 6 포인트

상위 5 도메인:
1. developer.mozilla.org: 1 기사
2. lesswrong.com: 1 기사
3. sciencedaily.com: 1 기사
4. garfieldminusgarfield.net: 1 기사
5. centminmod.com: 1 기사

최고 점수 기사:
제목: Garfield Minus Garfield
URL: https://garfieldminusgarfield.net
점수: 578 points
작성자: mike1o1

Ruby의 다른 스크래핑 라이브러리와 비교

참고로, Playwright 외에도 Ruby에는 다양한 웹 스크래핑 라이브러리가 있습니다:

라이브러리주요 특징Playwright와 비교했을 때
NokogiriHTML/XML 파서, CSS/XPath 선택자JavaScript 미지원, 정적 페이지만 가능
Mechanize폼 작성, 버튼 클릭 지원제한적 JavaScript 지원, 실제 브라우저 미사용
WatirSelenium 기반 브라우저 제어더 오래된 기술, 더 느림
Kimurai고급 스크래핑 프레임워크종합 프레임워크이나 성능 면에서 불리
FerrumChrome DevTools 프로토콜 기반Chrome만 지원

Playwright는 모든 주요 브라우저를 지원하고, 자동 대기 메커니즘이 뛰어나며, 성능이 우수하다는 장점이 있습니다. 특히 현대적인 JavaScript 기반 웹사이트를 다룰 때 가장 강력한 옵션입니다.

Playwright MCP

최근 마이크로소프트는 AI와 LLM(대규모 언어 모델)이 웹 브라우저와 상호작용할 수 있게 해주는 새로운 도구인 Playwright MCP(Model Context Protocol)를 출시했습니다.

Playwright MCP란?

Playwright MCP는 LLM이 웹 페이지와 상호작용할 수 있도록 하는 서버입니다. 스크린샷이나 시각적 모델을 사용하지 않고 구조화된 접근성 스냅샷(accessibility snapshot)을 통해 웹 페이지와 상호작용합니다.

주요 특징:

  • 빠르고 가벼운 동작: 픽셀 기반 입력이 아닌 접근성 트리 사용
  • LLM 친화적: 비전 모델 불필요, 구조화된 데이터로 작동
  • 결정적 도구 적용: 스크린샷 기반 접근에서 흔한 모호성 제거

MCP 사용 방법

VS Code에서 Playwright MCP를 설정하는 방법:

# VS Code에서
code --add-mcp '{"name":"playwright","command":"npx","args":["@playwright/mcp@latest"]}'

MCP 서버 구성 예시:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest"
      ]
    }
  }
}

MCP 주요 명령어

Playwright MCP는 다양한 브라우저 조작 명령을 제공합니다:

// URL로 이동
browser_navigate({ url: "https://example.com" });

// 요소 클릭하기
browser_click({
  element: "Login 버튼",
  ref: "button-login-123"
});

// 텍스트 입력하기
browser_type({
  element: "이메일 입력란",
  ref: "input-email-456",
  text: "[email protected]",
  submit: true
});

// 스냅샷 캡처
const snapshot = await browser_snapshot();

MCP와 일반 Playwright의 차이점

항목일반 PlaywrightPlaywright MCP
대상 사용자개발자와 QA 엔지니어AI 에이전트와 LLM
사용 방식직접 코드 작성구조화된 명령 인터페이스
페이지 분석CSS/XPath 선택자접근성 트리 또는 스크린샷
주요 용도웹 테스트, 스크래핑AI 기반 웹 자동화
MCP는 특히 AI 에이전트(예: GitHub Copilot)가 웹 브라우저와 상호작용해야 하는 시나리오에서 강력한 도구입니다.

마무리

Playwright는 현대적인 웹 테스트 자동화와 스크래핑을 위한 강력한 도구입니다. 모든 주요 브라우저를 지원하며, 뛰어난 안정성과 성능으로 복잡한 웹 환경에서도 원활하게 작동합니다.

Playwright의 주요 장점 요약

  • 크로스 브라우저 호환성: 모든 주요 브라우저에서 일관된 동작
  • 자동 대기 메커니즘: 안정적인 테스트와 스크래핑을 위한 자동화된 대기
  • 강력한 API: 복잡한 사용자 상호작용 시뮬레이션 가능
  • 언어 지원: JavaScript, TypeScript, Python, Java, .NET, Ruby 등 지원
  • 병렬 실행: 빠른 테스트와 스크래핑을 위한 병렬 처리
  • 최신 웹 기술 지원: Shadow DOM, Web Components 등 최신 웹 기술 지원⠀

활용 시나리오

Playwright는 다음과 같은 다양한 시나리오에서 활용할 수 있습니다:

  1. 웹 애플리케이션 테스트 자동화: end-to-end 테스트 구현
  2. 데이터 수집 및 모니터링: 웹 사이트의 데이터를 주기적으로 수집
  3. 콘텐츠 마이그레이션: 한 플랫폼에서 다른 플랫폼으로 콘텐츠 이전
  4. 경쟁사 분석: 경쟁사 웹사이트의 가격, 제품, 기능 등을 모니터링
  5. AI와의 통합: MCP를 통한 AI 에이전트의 웹 자동화

앞으로의 발전 방향

Playwright는 계속해서 발전하고 있으며, 다음과 같은 방향으로 나아가고 있습니다:

  • 보다 강력한 AI 통합: LLM과의 긴밀한 통합으로 더 지능적인 브라우저 자동화
  • 성능 개선: 더 빠르고 안정적인 테스트 실행
  • 새로운 브라우저 기능 지원: 최신 웹 표준과 기능에 대한 지속적인 지원
  • 생산성 도구 확장: 코드 생성, 디버깅, 분석 도구의 개선


Playwright는 기존의 Selenium, Puppeteer 등의 도구를 뛰어넘는 기능과 성능을 제공하며, 웹 테스트 자동화와 스크래핑의 새로운 표준으로 자리잡고 있습니다. 특히 JavaScript 기반의 복잡한 웹 애플리케이션을 다룰 때 강력한 선택지가 될 것입니다.

처음엔 Playwright에 대한 소개를 하는 글을 쓰려고 했는데, 쓰다보니 어째 루비 웹스크랩에 더 많은 비중을 둔 게 아닌가 싶네요.

이 글을 통해 Playwright의 기본 개념부터 실제 사용 사례까지 살펴보았습니다. 웹 스크래핑과 테스트 자동화가 필요한 프로젝트에 Playwright를 도입하여 더 효율적이고 안정적인 솔루션을 구축해 보세요!

참고 자료

공식 문서 및 리소스

Ruby 관련 자료

튜토리얼 및 가이드

유용한 도구 및 확장

커뮤니티 및 지원

Contact

유스풀패러다임
03159 서울특별시 종로구 종로 33
그랑서울타워1, 7층

+82 02 720 5059
Contact Us

Connect

Links

유스풀패러다임의 다른 사이트들도 만나 보세요.

Usefulparadigm blog
WordPress 가이드
Landing Jekyll
Hello Gatsby