웹스크래핑의 고질적 이슈: 자원 효율과 안정성을 고려한 선택

parkjunhyeok_·2026년 1월 21일

news trend system

목록 보기
2/6

1️⃣ 모든 선택에는 대가가 따릅니다

뉴스 기사 수집 파이프라인을 설계하면서 제가 가장 먼저 내린 결정은 "Puppeteer나 Playwright 같은 동적 스크래핑은 배제한다"는 것이었습니다.

하지만 이 결정은 단순히 구현을 줄이기 위한 편의상의 선택이 아니었습니다. 일부 사이트는 수집할 수 없고, JavaScript로 렌더링되는 컨텐츠는 포기해야 하며, 구조 변경 시 대응 수단이 제한된다는 점을 명확히 인지한 상태에서 내린 전략적 선택이었습니다. 이 글에서는 그 대가가 무엇이었고, 이를 어떻게 감당하려 했는지, 그리고 왜 그럼에도 이 선택이 합리적이었는지를 정리해 보고자 합니다.


2️⃣ 동적 스크래핑을 하지 않았을 때 발생하는 문제들

2-1. JavaScript 렌더링 페이지 수집 불가

정적 스크래핑(axios + cheerio)만 사용할 경우, React나 Vue 등으로 만들어진 SPA 기반 언론사 사이트에서는 본문을 가져올 수 없습니다.

  • 손실: 일부 현대적인 언론사 수집 불가, 데이터 커버리지 약 10~15% 감소 예상.
  • 선택한 대안: 수집 기준에 따라 선정한 언론사 10곳 중 9곳이 SSR(Server Side Rendering) 방식을 사용한다는 점에 착안하여, SSR 기반 사이트 위주로 셀렉터 맵을 구성했습니다.
    (구조 변경 사항에 있어서는, 로그 모니터링을 통해 수작업으로 유지보수를 하는 방향으로 개선할 수 있을 것입니다.)
private getSelectorMap(): Record<string, string[]> {
  return {
    'yna.co.kr': ['article', '.story-news', '.article'],      // ✅ SSR
    'chosun.com': ['.article-body', 'section.article-body'],   // ✅ SSR
    // CSR 기반 사이트는 과감히 제외하여 파이프라인 안정성 확보
  };

2-2. Lazy Loading 미지원

댓글이나 관련 기사 등 스크롤 시 로드되는 부가 데이터는 수집 범위에서 제외되었습니다.

  • 판단 근거: 뉴스 기사의 핵심 가치는 '본문'에 있다고 보았습니다. 댓글은 있으면 좋은 데이터로 분류하여 범위를 제한함으로써 전체 시스템의 복잡도를 낮췄습니다.

2-3. 스크래핑 시 발생하는 특유의 이슈들

정적 스크래핑을 선택하더라도, 수만 건 이상의 데이터를 처리할 때는 기술적 장벽이 존재합니다.

  • 안티 크롤링 및 IP 차단 (Anti-Bot): 단시간에 많은 요청을 보낼 경우 발생하는 차단 이슈를 방지하기 위해, Node.js의 비동기 특성을 활용하되 동시성 요청 수(Concurrency)를 제한하는 Throttle 로직을 적용했습니다.
  • 데이터 오염 및 노이즈 정제: HTML 내의 광고 태그, 무의미한 스크립트가 데이터 품질을 저해하지 않도록, 본문 추출 전 불필요한 DOM 요소를 선제적으로 제거하는 데이터 클리닝 파이프라인을 구축했습니다.
  • 비정형 구조의 정형화: 언론사마다 제각각인 HTML 구조를 하나의 공통 인터페이스로 추상화하여, 백엔드에서 즉시 활용 가능한 고품질 데이터를 제공하는 데 집중했습니다.

3️⃣ 그럼에도 동적 스크래핑을 피한 이유 (Trade-off)

가장 큰 이유는 Node.js 런타임 특성 때문입니다. 싱글 스레드 기반인 Node.js에서 브라우저 렌더링 작업을 수행하면 CPU와 메모리 사용량이 급격히 증가하여 전체 시스템의 응답성이 저하됩니다.

동적 크롤링에서 발생하는 주요 CPU 사용은 함께 실행되는 브라우저 프로세스에서 발생한다고 합니다.

근데, 결국 이 브라우저 프로세스는 node.js와 동일한 서버 자원을 공유하기 때문에, 결과적으로 node.js 기반 파이프라인 전체의 처리량과 안정성에 영향을 미치게 됩니다.

특히 프로젝트가 단일 서버 환경이기 때문에 이런 자원 경쟁이 이벤트 루프 지연을 일으킬 수 있기 때문에, 동적 크롤링을 채택하는것이 효율이 떨어진다고 판단했습니다.

4️⃣ 대신 집중한 것

동적 스크래핑을 포기한 대신, 정적 파이프라인의 신뢰성을 극대화하는 데 집중했습니다.

4-1. 동시성 제한과 요청 속도 제어

단시간에 과도한 요청이 발생할 경우 안티 크롤링 차단이나 네트워크 오류가 발생할 수 있기 때문에, Node.js의 비동기 특성을 활용하면서 동시 처리 요청 수를 제한하는 방식을 선택했습니다.

  • 한번에 처리하는 요청 수를 고정된 개수로 제한
  • 배치 단위 처리 후 고정 지연을 두어 요청 속도를 제어
private readonly concurrencyLimit = 5;

for (let i = 0; i < articles.length; i += this.concurrencyLimit) {
	const chunk = articles.slice(i, i + this.concurrencyLimit);
	await Promise.allSettled(chunk.map(...));
	await this.delay(500);
}

4-2. 상세한 에러 분류

실패 원인을 상세히 분류하여 어떤 문제가 발생했는지 즉각적으로 파악할 수 있게 했습니다.

private classifyError(error: any): string {
  if (axios.isAxiosError(error)) {
    const status = error.response?.status;
    if (status === 429) return 'RATE_LIMITED';
    if (error.code === 'ECONNABORTED') return 'TIMEOUT';
  }
  if (error.message?.includes('빈 본문')) return 'EMPTY_CONTENT';
  return 'UNKNOWN';
}

4-3. 파이프라인 분리와 중복 처리 방지

수집된 데이터는 즉시 메시지 큐로 전달하여, 수집 단계와 후속처리 단계를 분리했습니다.
중복 데이터는 큐 레이어에서 차단함으로써,
불필요한 후속 처리 비용을 줄이고 효율을 높였습니다.


5️⃣ 향후 개선 방향 고민

현재 설계의 한계를 인지하고 있으며, 서비스 성장에 따라 다음과 같은 확장 로드맵을 고민해봤습니다.

1. 하이브리드 수집 및 마이크로서비스 분리

  • 하이브리드 전략: 1차 정적 수집 실패 시에만 해당 URL을 별도의 '동적 수집 큐'로 전달하여 다시 시도합니다(Fallback).
  • 별도 서비스 격리: CPU 점유율이 높은 동적 스크래핑은 Python + Selenium이나 다른 런타임이나 프레임워크로 작성된 별도 마이크로서비스로 분리하여 메인 파이프라인의 안정성을 보장할 수 있을 것입니다.

2. 서버리스와 메시지 큐(MQ) 활용

  • 서버리스 스케일링: 서버리스와 MQ 조합을 통해 필요할 때만 컨테이너를 실행함으로써 메모리 누수 위험을 제거하고 비용을 최적화할 수 있습니다.
  • 결합도 해제: MQ를 도입해 수집 요청과 처리를 분리함으로써, 대량의 데이터를 유실 없이 처리하고 부하를 조절할 수 있을 것입니다.

3. LLM 기반 범용 추출기 및 외부 서비스 도입

  • LLM 기반 추출: GPT-4o mini 등을 비교적 가벼운 모델을 활용해 셀렉터 없이도 본문을 추출하는 엔진을 구축하여 유지보수 공수를 줄일 수도 있을 것 같습니다.
  • 외부 서비스: 운영 부담이 커지는 시점에는 크롤링 전문 서비스를 도입할 수 있을 것 같습니다.

6️⃣ 결론: trade-off 를 고려한 결정

이 프로젝트의 목표는 모든 페이지를 무조건 수집하는 것이 아니라, "Node.js 환경에서 지속 가능한 수집 파이프라인을 설계하는 것"이었습니다.

동적 스크래핑을 선택하지 않음으로써 일부 데이터는 포기해야 했지만, 대신 예측 가능한 리소스 사용량과 안정적인 시스템을 유지할 수 있었습니다. 현재 이 설계로 선정된 언론사에서 평균 95% 이상의 수집 성공률을 달성하고 있으며, 한 차례에 4,000건 이상을 안정적으로 처리하고 있습니다.


0개의 댓글