검색 K
밝은/어두운 배경
밝은/어두운 배경
게시물 정보
스크래퍼 한 번 실행 시 데이터가 어떤 단계를 거쳐 최종 파일로 만들어지는지 설명합니다.
Neon DB (registered_feeds)
│
│ 1. fetchActiveFeeds()
▼
활성 피드 목록 (RssFeedConfig[])
│
│ 2. groupByFeedUrl() → groupByDomain()
▼
도메인별 그룹 (동일 도메인은 순차 처리, 최대 3개 도메인 병렬)
│
│ 3. fetchXml() — 재시도 최대 3회, 타임아웃 15초
▼
XML 원문
│
│ 4. ParserService.parse()
▼
ParsedFields[] — 당월 데이터만 필터링
│
│ 5. JsonStoreService.insertIfNotExists()
▼
FeedStore (메모리 내 누적)
│
├─ 6a. JsonStoreService.save()
│ → src/_data/{year}/{MM}/{feedUrl}.json
│
└─ 6b. rebuildMarkdown()
→ src/{folderName}/{year}/{MM}/{feedTitle}.mdfeedRepository.ts의 fetchActiveFeeds()가 registered_feeds 테이블에서 active = true인 피드를 모두 가져옵니다. 동일한 RSS URL이 여러 folderName에 등록될 수 있으므로, 다음 단계에서 그룹화합니다.
두 단계로 그룹화합니다.
CONCURRENCY_LIMIT(기본값 3)만큼 병렬로 처리합니다.fetchService.ts의 fetchXml()이 HTTP GET으로 RSS XML을 수신합니다. 브라우저와 동일한 User-Agent 헤더를 사용하며, 응답 실패 시 지수 백오프로 최대 3회 재시도합니다.
| 항목 | 값 |
|---|---|
| 타임아웃 | 15초 |
| 최대 재시도 | 3회 |
| 대기 시간 | 1.5s → 3s → (실패) |
ParserService가 XML을 FeedItem[]으로 파싱하고, feedItemExtractor.ts의 순수 함수들이 각 필드를 추출합니다. config.scope 설정에 따라 날짜 범위 필터링이 적용됩니다.
| scope 값 | 수집 범위 |
|---|---|
all | 전체 (기본값) |
today | 오늘 자정 이후 |
3days | 최근 3일 |
weekly | 최근 7일 |
파싱 이후 index.ts에서 당월 데이터만 추가로 필터링합니다. scope 필터와 당월 필터 두 단계를 모두 통과한 항목만 저장됩니다.
JsonStoreService.insertIfNotExists()가 post.link를 기준으로 중복 여부를 확인합니다. 중복이 아닌 항목만 삽입하고, 삽입 후 pubDate 내림차순으로 정렬합니다. 이미 수집된 포스트는 덮어쓰지 않습니다.
모든 피드 처리가 완료된 후 메모리의 FeedStore를 일괄 저장합니다.
6a. JSON 저장 (JsonStoreService.save()): 월별 JSON 파일에 전체 포스트 데이터를 저장합니다. 이 파일이 시스템의 원본 데이터입니다.
6b. Markdown 재빌드 (rebuildMarkdown()): 신규 포스트가 있거나 md 파일이 없는 경우에만 전체를 재작성합니다. 부분 업데이트 없이 항상 전체를 다시 씁니다.
수집 시점 향후 확장
─────────────────────────────────────────────────────
contentStatus: 'none' → 'pending' → 'fetched' | 'failed'
content: null → 원문 전체 텍스트
aiSummary: null → AI 요약 결과현재 스크래퍼는 contentStatus: 'none'으로만 저장합니다. 원문 수집(content)과 AI 요약(aiSummary) 처리는 별도 파이프라인에서 담당합니다.