# crawler_platform 책임 분리 리포트 (AI=Semantic Extractor) 작성일: 2026-05-11 대상: `C:\Users\lasta\MyProject\AI\crawler_platform` ## 1. 결론 요약 현재 구조는 동작은 하지만, **제어 책임이 API Route / SiteCrawler / CrawlPipeline / Repository에 분산**되어 있습니다. 특히 `SiteCrawler`와 `CrawlPipeline`이 탐색, 분석 판단, AI 호출, 저장까지 동시에 수행하고 있어 확장성과 테스트 경계가 약합니다. 핵심 정리: - AI(`LLMJsonExtractor`)는 현재 DB/크롤 흐름을 직접 알지 않으며, 대체로 “의미 추출기” 역할을 수행 중. - 하지만 시스템 전체 orchestration 주체가 부재하여 Route/Crawler/Pipeline에 제어가 분산됨. - Repository는 저장소를 넘어 일부 정책(신뢰도 결합/병합 전략)을 포함. --- ## 2. 구성요소별 실제 책임 진단 ## API Route (`app/api/routes.py`) 현재 책임: - 요청/응답 처리 외에 다음까지 수행 - config 로딩 및 의존 객체 생성 (`CrawlPipeline`, `SiteCrawler`) - 근거: 149-195 - crawl 파라미터 정책 적용(`max_depth/max_pages` 보정) - 근거: 188-191 - discovery 흐름 직접 수행(robots/fetch/discover) - 근거: 197-212 - claim confidence 갱신 정책(0~1 clamp) - 근거: 274-284 - entity merge 비즈니스 로직 직접 수행(Claim/Relation 재매핑) - 근거: 286-308 - 추천 태그 집계 규칙 직접 수행 - 근거: 310-343 판단: - Route가 단순 진입점을 넘어 **서비스/도메인 로직 조립 및 정책 수행자** 역할까지 맡음. - “얇은 Route + Service 호출” 원칙과 불일치. --- ## SiteCrawler (`app/core/crawler/site_crawler.py`) 현재 책임: - 사이트 탐색(queue/depth/visited/discover) - 근거: 70-117 - same-domain 필터, robots 차단 판단 - 근거: 89-100 - 페이지 유형 분류 및 분석 여부 판단 - 근거: 105, 119-120, 201-224 - AI/Extractor 호출 - 근거: 120 - 저장 호출(page/claim/evidence/log) - 근거: 121-135 - crawl_jobs 상태 생성/완료 처리 - 근거: 83, 172-188 판단: - `SiteCrawler`가 “웹 탐색기”를 넘어 **페이지 처리기 + 저장 오케스트레이터**까지 수행. - 요청하신 기준(탐색 전용) 대비 책임 과다. 분리 후보: - `classify_page`, `analyze_page_types` 판단 로직 - `extractor.extract(...)` 호출 - `repository.upsert_page/save_extraction_bundle(...)` 저장 호출 - `_create_job/_finish_job` 실행 추적 --- ## CrawlPipeline (`app/core/crawler/pipeline.py`) 현재 책임: - robots 정책 판단 - 근거: 34-35 - fetch + parse + extract - 근거: 37-41 - 프로젝트/소스 동기화 및 페이지/추출 결과 저장 - 근거: 43-55 판단: - 단일 URL 처리기 역할을 하면서도 저장 정책까지 포함. - 이름은 Pipeline이지만 사실상 **PageProcessor + 저장 orchestration**을 동시에 수행. 분리 후보: - `crawl_url`를 `PageProcessor.process(url)`와 `CrawlService.persist(processed_page)`로 분리 - robots 허용/재시도/저장 여부 판단은 Service 계층으로 이동 --- ## Extractor (`app/core/extractor/ai_provider.py`, `factory.py`) 현재 책임: - LLM 호출 및 JSON 파싱/복구 - ontology predicate normalize - 실패 시 rule-based fallback 주요 근거: - AI 추출 핵심: 42-107, 148-247 - ontology normalize: 75-78 - fallback: 109-131 - provider 선택(facade): `factory.py` 11-17 판단: - 크롤링 큐, 링크 탐색, DB 저장을 직접 알지 않음(좋음). - 다만 `LLMJsonExtractor` 내부 fallback은 “추출 품질 보완” 범주로는 허용 가능하나, 책임을 더 엄격히 분리하려면 fallback도 외부 orchestration(Service)로 이동 가능. 요약: - **치명적 위반 없음**(crawl/storage/pipeline orchestration은 알지 않음). --- ## Repository (`app/core/database/repository.py`) 현재 책임: - pages/entities/claims/evidence/extraction_logs 저장 및 upsert - claim hash 기반 dedup/merge - relation upsert 및 support_count 증가 - confidence 결합 규칙(extraction + source trust) 주요 근거: - 저장/병합 중심: 140-226, 228-306 - 신뢰도 결합 규칙: 163, 322-323 - claim hash 전략: 164-176, 326-342 판단: - 저장 인터페이스 역할은 수행하지만, **정책성 로직(신뢰도 결합 비율 0.7/0.3, max merge, relation support 전략)**이 포함됨. - “Repository는 저장소” 원칙을 엄격히 적용하면, 정책 계산은 Service(또는 Domain Policy)로 이동하는 것이 바람직. 분리 후보: - `combine_confidence` - claim update 시 `max(confidence)` 전략 - relation `support_count` 증가 규칙 --- ## session_scope (`app/core/database/session.py`) 현재 책임: - commit/rollback/close 트랜잭션 경계 근거: 40-51 판단: - 요청하신 기준과 일치. 변경 우선순위 낮음. --- ## 3. 현재 가장 큰 책임 혼재 지점 1. `SiteCrawler`가 탐색기 + 처리기 + 저장 오케스트레이터를 모두 수행 2. `CrawlPipeline`이 처리기 + 저장기를 동시에 수행 3. `API Route`가 서비스 조립/정책/집계/병합 로직을 직접 수행 4. `Repository`가 저장소를 넘어 정책 일부까지 포함 --- ## 4. 목표 아키텍처 제안 권장 호출 구조: `FastAPI Route -> CrawlService -> SiteCrawler -> PageProcessor -> Fetcher -> Parser -> Extractor -> Repository` 역할 재정의: - Route: request 검증, service 호출, response 변환 - CrawlService: 전체 orchestration/정책 판단/재시도/저장 여부 결정 - SiteCrawler: 링크 탐색(queue/depth/domain/link discovery)만 수행 - PageProcessor: 단일 URL의 fetch/parse/extract만 수행 - Extractor: 텍스트 -> 구조화 JSON/Entity/Claim 변환만 수행 - Repository: 저장/upsert 인터페이스만 수행 (정책 계산 제외) --- ## 5. 리팩터링 설계(코드 대규모 변경 전) ## Phase 0: 인터페이스 고정 - `PageProcessorResult` DTO 정의 - `url/final_url/status_code/title/clean_text/page_type/entities/claims/raw_output/errors` - `CrawlDecisionPolicy`(분석 여부/저장 여부 판단) 초안 분리 ## Phase 1: Service 계층 도입 - `app/core/services/crawl_service.py` 신설 - Route는 `CrawlService.crawl_url(...)`, `CrawlService.crawl_site(...)`만 호출 - 기존 로직은 내부적으로 재사용하되 외부 인터페이스 먼저 고정 ## Phase 2: SiteCrawler 축소 - `SiteCrawler` 반환을 “발견된 URL 작업 목록” 중심으로 전환 - 페이지 분류/분석 여부/AI 호출/저장은 `CrawlService`로 이동 ## Phase 3: CrawlPipeline -> PageProcessor 전환 - `CrawlPipeline.crawl_url`를 `PageProcessor.process`로 대체 - PageProcessor는 fetch/parse/extract까지만 수행, DB 접근 제거 ## Phase 4: Repository 정책 분리 - `combine_confidence`, merge rule을 `app/core/services/policies/*.py`로 이동 - Repository는 저장/조회/upsert만 수행 ## Phase 5: Route 슬림화 - `/crawl`, `/crawl-site`, `/discover`, `/entities/merge`, `/claims/{id}/confidence`를 Service 호출형으로 변환 --- ## 6. 안전한 단위 리팩터링 파일 목록과 변경 순서 1) `app/core/services/crawl_service.py` (신규) 2) `app/core/services/crawl_dto.py` (신규) 3) `app/core/services/policies.py` (신규; confidence/merge 정책) 4) `app/core/crawler/pipeline.py` (PageProcessor 역할로 축소 또는 `page_processor.py`로 분리) 5) `app/core/crawler/site_crawler.py` (탐색 전용으로 축소) 6) `app/core/database/repository.py` (정책 제거, 저장 전용화) 7) `app/api/routes.py` (Service 호출만 남기기) 8) `app/cli/main.py` (Route와 동일 Service 재사용) 9) `tests/` (서비스 단위/계층 경계 테스트 추가) --- ## 7. 테스트 전략(리팩터링 안전장치) - 계약 테스트: `Extractor` 입력/출력 계약 유지 - 단위 테스트: - `SiteCrawler`: URL discovery/queue/depth/domain 필터만 검증 - `PageProcessor`: fetch/parse/extract 파이프만 검증(저장 없음) - `CrawlService`: 분석 여부 판단/저장 호출/재시도 정책 검증 - `Repository`: pure upsert/조회만 검증 - 회귀 테스트: - `/crawl`, `/crawl-site` API 응답 필드 변화 없음 - claim/entity 수 및 dedup 결과 일관성 확인 --- ## 8. 즉시 적용 가능한 최소 원칙 - AI는 `clean_text -> structured data`만 담당 - “저장 여부, 재시도, 정책 판단”은 Service가 담당 - Repository에서 정책 계산 로직 분리 - Route에서 SQL/병합 규칙 직접 처리 제거