domharvest-playwright 내부: 프로덕션 레디 웹 스크래핑 도구를 설계한 방법

발행: (2026년 1월 9일 오후 11:10 GMT+9)
6 min read
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the paragraphs, headings, etc.) in order to do the translation. Could you please paste the article content here (excluding the source line you’ve already provided)? Once I have the text, I’ll translate it into Korean while preserving the original formatting, markdown, and any code blocks or URLs.

핵심 아키텍처

domharvest-playwright는 세 가지 주요 구성 요소를 중심으로 구축됩니다:

  • DOMHarvester Class – 주요 오케스트레이터
  • Browser Management – Playwright 라이프사이클 관리
  • Data Extraction Pipeline – 선택자 기반 수집

디자인 원칙

단순함 우선

모든 아키텍처 결정은 영리함보다 단순함을 우선시했습니다. 과도한 추상화나 불필요한 패턴은 없습니다.

빠르게 실패하고, 명확하게 실패

오류는 명확하고 실행 가능해야 합니다. 조용한 실패는 없습니다.

구성 가능성

복잡한 워크플로우를 위해 결합할 수 있는 작고 집중된 메서드들.

브라우저 수명 주기 관리

class DOMHarvester {
  async init(options = {}) {
    this.browser = await playwright.chromium.launch({
      headless: options.headless ?? true,
      ...options.browserOptions
    })
    this.context = await this.browser.newContext(options.contextOptions)
  }

  async close() {
    await this.context?.close()
    await this.browser?.close()
  }
}

왜 이 접근 방식을 사용하나요?

  • 명시적인 초기화는 사용자에게 제어권을 제공합니다.
  • 별도의 컨텍스트 관리는 다중 세션을 가능하게 합니다.
  • 깨끗한 종료는 리소스 누수를 방지합니다.

수확 파이프라인

핵심 harvest() 메서드는 직관적인 흐름을 따릅니다:

async harvest(url, selector, extractor) {
  const page = await this.context.newPage()

  try {
    await page.goto(url, { waitUntil: 'networkidle' })

    const elements = await page.$$(selector)
    const results = []

    for (const element of elements) {
      const data = await element.evaluate(extractor)
      results.push(data)
    }

    return results
  } finally {
    await page.close()
  }
}

핵심 결정 사항

  • waitUntil: 'networkidle' 은 속도와 신뢰성의 균형을 맞춥니다.
  • 순차 처리로 레이스 컨디션을 방지합니다.
  • finally 블록은 오류가 발생해도 정리를 보장합니다.
  • 추출기 함수는 성능을 위해 브라우저 컨텍스트에서 실행됩니다.

Error Handling Strategy

try {
  await page.goto(url, {
    waitUntil: 'networkidle',
    timeout: 30000
  })
} catch (error) {
  if (error.name === 'TimeoutError') {
    throw new Error(`Failed to load ${url}: timeout after 30s`)
  }
  throw error
}

Playwright 오류를 상황에 맞는 메시지로 감싸서, 사용자가 스택 트레이스를 살펴보지 않고도 디버깅할 수 있도록 돕습니다.

Custom Extraction Support

Beyond selector‑based harvesting, harvestCustom() allows arbitrary page evaluation:

async harvestCustom(url, evaluator) {
  const page = await this.context.newPage()

  try {
    await page.goto(url, { waitUntil: 'networkidle' })
    return await page.evaluate(evaluator)
  } finally {
    await page.close()
  }
}

This enables complex scenarios like:

  • Multi‑step interactions
  • Conditional logic based on page state
  • Aggregating data from multiple sources

테스트 아키텍처

테스트는 관심사별로 구성됩니다:

test/
├── unit/
│   ├── harvester.test.js
│   └── browser-management.test.js
├── integration/
│   └── harvest-workflow.test.js
└── fixtures/
    └── sample-pages/

실제 HTML 픽스처를 사용하고 모킹을 하지 않음으로써 테스트가 실제 환경 문제를 포착하도록 합니다.

Performance Considerations

Page Reuse vs. Clean State

I create new pages per harvest for isolation. Slight performance cost, but it eliminates entire classes of bugs.

Parallel vs. Sequential

Sequential processing is the default for predictability. Users can parallelize at the application level if needed.

Memory Management

Explicit page cleanup in finally blocks prevents memory leaks during long‑running sessions.

코드 조직

src/
├── index.js          # Public API
├── harvester.js      # DOMHarvester class
└── utils/
    ├── validators.js # Input validation
    └── errors.js     # Custom error types

플랫 구조이며, 깊은 중첩이 없습니다—탐색하기 쉽습니다.

배운 교훈

  1. 너무 일찍 추상화하지 말라 – 다양한 스크래핑 모드에 “전략 패턴”을 적용하려고 했지만, YAGNI가 옳았다.
  2. Explicit > Implicit – 사용 전에 init()을 요구하는 것이 번거로워 보일 수 있지만, 혼란스러운 초기화 버그를 방지한다.
  3. 브라우저 자동화는 I/O가 많이 소모된다 – 네트워크 지연이 주를 이루므로, 마이크로 최적화보다 신뢰성에 집중한다.
  4. 오류 메시지는 중요하다 – 사용자는 코드보다 오류를 더 많이 보므로, 오류 메시지를 유용하게 만든다.

다음은?

고려 중인 향후 아키텍처 개선 사항:

  • 맞춤형 미들웨어를 위한 플러그인 시스템
  • 지수 백오프를 적용한 내장 재시도 로직
  • 요청/응답 가로채기 훅
  • 대용량 데이터셋을 위한 스트리밍 결과

직접 해보기

npm install domharvest-playwright

아키텍처는 의도적으로 단순합니다. 소스를 읽어보세요—500줄 이하입니다.

링크

Back to Blog

관련 글

더 보기 »