- 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>
65 lines
2.1 KiB
JavaScript
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);
|
|
}
|
|
}
|