Files
sundol/government/src/sources/base.js
joungmin f2a8f30867 gov-scraper: 중소벤처24(smes) 사업공고 소스 추가
- GenericHtmlSource 확장: 신청기간(period) 날짜 파싱, listOnly(목록 전용) 모드
- smes(중소벤처24 bizApply) config 추가 — href의 PBLN 공고ID 추출, 제목/분야/주관기관/신청기간 적재
- smes 상세는 팝업 전용(JS 다이얼로그)이라 직접 크롤 불가 → 목록 전용으로 적재(18건 검증)
- util: parseFlexibleDate(YY-MM-DD/YYYYMMDD 대응)
- pipeline: skipDetail 소스는 상세 단계 건너뜀

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 05:51:46 +00:00

65 lines
2.1 KiB
JavaScript

// OpportunitySource — Strategy 인터페이스.
// 소스(사이트)별 어댑터는 이 클래스를 상속해 list()/fetchDetail() 을 구현한다.
import { crawl } from '../crawler/crawler.js';
/**
* 공고 목록 항목 형태:
* {
* externalId: string, // 소스 고유 키 (필수, dedup)
* title: string, // 제목 (필수)
* agency?: string, // 소관/주관기관
* category?: string, // 지원분야
* target?: string, // 지원대상
* applyStart?: Date, // 접수 시작
* applyEnd?: Date, // 접수 마감
* detailUrl?: string, // 상세 페이지 URL
* raw?: object, // 원본 데이터(JSON 저장)
* }
*/
export class OpportunitySource {
/** @param {{code:string,name:string,baseUrl?:string,type:'API'|'HTML',config?:object}} meta */
constructor(meta) {
if (!meta.code || !meta.name || !meta.type) {
throw new Error('OpportunitySource meta 에 code/name/type 필수');
}
this.code = meta.code;
this.name = meta.name;
this.baseUrl = meta.baseUrl || null;
this.type = meta.type;
this.config = meta.config || {};
// true 면 파이프라인이 상세 본문 수집 단계를 건너뛴다(목록 전용 소스).
this.skipDetail = false;
}
meta() {
return {
code: this.code,
name: this.name,
baseUrl: this.baseUrl,
type: this.type,
config: this.config,
};
}
/**
* 공고 목록을 수집한다. 하위 클래스에서 반드시 구현.
* @returns {Promise<Array>}
*/
async list() {
throw new Error(`${this.code}: list() 미구현`);
}
/**
* 상세 본문을 수집한다. 기본 구현은 detailUrl 을 3단계 폴백 크롤러로 긁는다.
* API 처럼 본문이 이미 raw 에 있는 소스는 이 메서드를 오버라이드한다.
* @param {{id:string, externalId:string, detailUrl:string, raw:object|null}} row
* @returns {Promise<string>} 본문 텍스트
*/
async fetchDetail(row) {
if (!row.detailUrl) {
throw new Error(`${this.code}/${row.externalId}: detailUrl 없음 — 상세 수집 불가`);
}
return crawl(row.detailUrl);
}
}