크롤러 개발 중 만난 Typescript 클래스 문제들

2025. 5. 29. 20:06Programming Language/Typescript

반응형

최근에 크롤링 기반으로 서비스를 만들고 있습니다. 데이터를 모으다 보니 욕심이 생겨서, 점점 비슷한 데이터를 가진 사이트들을 추가로 크롤링하게 되었어요.

문제는 사이트별로 크롤러를 하나씩 만들다 보니, 나중엔 관리가 불가능한 수준이 돼버리더군요. 한마디로 미칠 노릇이었죠.

그래서 Typescript 에서 지원하는 Class 를 만들어서, 상속 기반으로 같은 데이터 파이프라인을 유지한 채로 각 사이트 별로 상이한 웹 요소에 맞게 크롤링 할 수 있는 구조를 만들던 중 사소한 문제 몇개에 부딪히게 되어, 그 이야기를 풀어보려고합니다. 


문제 1: 생성자 인자 순서 혼란 -> 옵션 객체로 리팩토링

생성자 인자 순서를 잘못 기억하면 예상치 못한 버그가 발생할 수 있습니다.

더군다나 크롤링을 위한 통합적인 클래스를 만들다보니 optional 한 인자들이 많이 생기게 되어 문제가 되었습니다.

 

그래서 저는 옵션 객체 방식을 선택하여 이러한 문제를 해결하였습니다. 

// 기존 방식 (인자 순서 중요)
constructor(url: string, delay: number, headers: object) {}

// 옵션 객체 방식
constructor(options: { url: string; delay?: number; headers?: object }) {}

장점: 

  • 옵션 객체는 가독성이 뛰어나고 유연합니다.
  • 선택적인 인자를 추가하거나 제거하기 쉽습니다.

단점:

  • 타입 정의가 조금 복잡해질 수 있습니다. 

문제 2: Typescript 오버로드 충돌 해결법

제가 구현하던 크롤링 서비스는 특정 사이트 별로 페이지네이션 하는 방법들이 다른 경우가 있었습니다.

그래서 페이지네이션을 하는 함수를 별도로 구현하여, "상속 받은 클래스마다 함수 오버로드로 처리를하자!" 라고 생각하였습니다.

class Navigator {
  navigate(url: string): void;
  navigate(url: string, options: object): void;

  // 구현부가 슈퍼셋이어야 함
  navigate(url: string, options?: object) {
    console.log(url, options);
  }
}

주의할 점:

  • 구현부는 모든 오버로드를 포함하는 형태로 작성합니다.
  • Typescript의 override 키워드를 활용하면 부모 클래스와 충돌 여부를 컴파일 단에서 확인할 수 있습니다.

override 키워드의 이점:

  • 부모 클래스와의 시그니처 충돌을 방지해줍니다.
  • 오타나 실수를바로 잡아줍니다. 

문제 3: 크롤러 설계 구조: 역할별 클래스/함수 분리하기

크롤러를 관리 가능한 단위로 쪼개는 것은 정말 중요합니다.

각 사이트별로 분석을 통해, 통합적인 흐름을 확인해서 기능 별로 쪼갠다면 관리가 확실히 편해집니다. 대표적으로 역할을 나누면:

  • BrowserManager: 브라우저를 관리하는 클래스/함수
  • Navigator: 페이지 이동을 담당하는 클래스/함수
  • Scraper: 실제 데이터를 긁어오는 클래스/함수
src/
├── managers/
│   └── BrowserManager.ts
├── navigators/
│   └── PageNavigator.ts
├── scrapers/
│   └── DataScraper.ts
└── index.ts

이러한 구조가 주는 장점은 어떤 데이터를 추가로 수집하고 싶을 때, 일일이 수정하는 불편함과 위험요소를 줄일 수 있습니다. 즉 유지보수 및 기능 확장이 편해집니다. 

그리고 각 기능별로 테스트할 수 있기 때문에, 안정성면에서도 이점이 있습니다.


리팩토링하며 얻은 중요한 교훈

크롤러는 본질적으로 비동기적입니다. 그래서 구조 설계시 다음을 꼭 고려하세요:

  • 명확한 책임 분리: 각 동작별로 단 하나의 명확한 역할만을 수행하게 해주세요. 
  • 테스트 가능성: 비동기 메서드를 모킹할 수 있는 구조를 고려하세요.
  • 유지보수와 확장성: 코드가 길어지고 복잡해지기 전에 추상화를 하거나 공통적인 부분은 통합하여 관리하는 습관이 필요합니다. 

이번 리팩토링을 통해 저는 확장성과 유지보수성을 크게 개선했어요. 이제 크롤링 대상이 늘어나도 역할별로 분리되어 있어 문제를 빠르게 파악하고 수정할 수 있죠. 데이터 구조를 최상위 클래스에서 핸들링하기 때문에 새로운 요구사항이 생겨도 쉽게 대응할 수 있게 되었답니다! 

반응형