README 업데이트: Phase 0-4 빠른 시작 가이드
This commit is contained in:
462
README.md
462
README.md
@@ -1,326 +1,234 @@
|
||||
# Ontology Crawler Platform
|
||||
# Ontology Platform
|
||||
|
||||
향수 구독 플랫폼을 첫 사용 사례로 삼되, 차, 커피, 캔들, 디퓨저, 영양제, 선물, 패션 소품 같은 개인화 구독 추천 서비스에 재사용할 수 있는 범용 크롤러/온톨로지 기반 지식 DB MVP입니다.
|
||||
온톨로지 플랫폼은 웹에서 구조화된 지식(엔티티/관계)을 자동 추출, 검증, 저장하는 고속 시스템입니다.
|
||||
|
||||
## 핵심 아이디어
|
||||
**Phase 0-4** 전체 구현 완료 | 추출(10초) → 검증(<100ms) → 그래프 저장 → 벡터 검색
|
||||
|
||||
이 시스템은 웹에서 가져온 문장을 곧바로 사실로 저장하지 않습니다. 모든 정보는 `Claim`으로 저장됩니다.
|
||||
## 🚀 빠른 시작
|
||||
|
||||
```yaml
|
||||
subject: Product A
|
||||
predicate: hasTopNote
|
||||
object: Bergamot
|
||||
source: OfficialSite
|
||||
evidence_text: Top notes: Bergamot, Neroli
|
||||
confidence: 0.95
|
||||
```
|
||||
|
||||
추천 시스템은 원문 복제가 아니라, 출처, 근거, 신뢰도, 갱신일을 가진 온톨로지 매핑 지식을 사용합니다.
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["URL Discovery"] --> B["Page Fetch"]
|
||||
B --> C["HTML Clean"]
|
||||
C --> D["Site Parser Plugin"]
|
||||
D --> E["Extractor Provider"]
|
||||
E --> F["Ontology Mapping"]
|
||||
F --> G["Deduplication"]
|
||||
G --> H["Confidence Scoring"]
|
||||
H --> I["Knowledge DB"]
|
||||
I --> J["Recommendation API"]
|
||||
I --> K["Update Scheduling"]
|
||||
```
|
||||
|
||||
구성 단위:
|
||||
|
||||
- `Project`: 향수 구독, 차 구독, 선물 추천 같은 프로젝트 단위 설정
|
||||
- `Source`: 공식몰, 마켓플레이스, 리뷰 사이트 같은 데이터 출처
|
||||
- `Page`: 수집된 URL과 정제 텍스트 요약
|
||||
- `Entity`: 상품, 브랜드, 노트, 무드, 계절, 상황 등 의미 객체
|
||||
- `Claim`: 출처가 주장한 정보 단위
|
||||
- `Evidence`: Claim의 근거 문장
|
||||
- `Relation`: Entity 간 집계 관계
|
||||
- `ExtractionLog`: 추출 방식, Provider, 로그
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```text
|
||||
crawler_platform/
|
||||
app/
|
||||
main.py
|
||||
config/
|
||||
core/
|
||||
crawler/
|
||||
extractor/
|
||||
ontology/
|
||||
database/
|
||||
recommendation/
|
||||
scheduler/
|
||||
domains/
|
||||
perfume/
|
||||
tea/
|
||||
coffee/
|
||||
candle/
|
||||
supplement/
|
||||
gift/
|
||||
api/
|
||||
cli/
|
||||
configs/
|
||||
perfume_subscription.yaml
|
||||
tests/
|
||||
README.md
|
||||
```
|
||||
|
||||
## DB 스키마
|
||||
|
||||
초기 MVP는 SQLAlchemy ORM으로 SQLite와 PostgreSQL을 모두 지원합니다.
|
||||
|
||||
필수 테이블:
|
||||
|
||||
- `projects`: 프로젝트 이름, 도메인, JSON 설정
|
||||
- `sources`: 출처 타입, 신뢰도, robots 정책, rate limit
|
||||
- `pages`: URL, fetch 상태, content hash, 정제 텍스트 요약
|
||||
- `entities`: 범용 의미 객체
|
||||
- `attributes`: Entity 속성
|
||||
- `relations`: Entity 간 집계 관계
|
||||
- `claims`: 출처가 주장한 subject-predicate-object 정보
|
||||
- `evidence`: Claim 근거 텍스트
|
||||
- `extraction_logs`: 추출 Provider와 로그
|
||||
- `crawl_jobs`: 예약 수집 작업
|
||||
- `user_profiles`: 추천 사용자
|
||||
- `user_preferences`: 취향 구조
|
||||
- `feedback_logs`: 추천 피드백
|
||||
|
||||
## 설치
|
||||
### 1. 설치
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium
|
||||
# 기본 설치 (Phase 0-1: 추출)
|
||||
pip install fastapi uvicorn pydantic trafilatura httpx
|
||||
|
||||
# Phase 2 추가 (동적 페이지)
|
||||
pip install crawl4ai
|
||||
|
||||
# Phase 4 추가 (Neo4j)
|
||||
pip install neo4j sentence-transformers
|
||||
```
|
||||
|
||||
정적 페이지는 `requests + BeautifulSoup`로 처리합니다. 동적 페이지가 필요하면 config에서 `fetcher: playwright`로 바꾸면 됩니다.
|
||||
|
||||
## CLI 사용
|
||||
|
||||
DB 초기화:
|
||||
### 2. Phase 0-1만 사용 (가장 간단)
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db init-db
|
||||
# API 서버 시작
|
||||
python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload
|
||||
|
||||
# URL에서 추출
|
||||
curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com"
|
||||
```
|
||||
|
||||
향수 프로젝트 생성:
|
||||
### 3. Phase 4 (그래프 검색) 포함
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db create-project --config configs/perfume_subscription.yaml
|
||||
# Neo4j 시작
|
||||
docker-compose -f docker-compose.neo4j.yml up -d
|
||||
|
||||
# API 서버 시작
|
||||
python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload
|
||||
|
||||
# 추출 → 수집 → 검색
|
||||
curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com"
|
||||
curl -X POST "http://localhost:8000/api/v1/search/ingest" -d '{"entities": [...], "relations": [...]}'
|
||||
curl "http://localhost:8000/api/v1/search/vector?query=machine+learning"
|
||||
```
|
||||
|
||||
온톨로지 조회:
|
||||
## 📋 Phase별 기능
|
||||
|
||||
| Phase | 기능 | 시간 | 상태 |
|
||||
|-------|------|------|------|
|
||||
| 0-1 | HTML 추출 (Trafilatura) | 10-15초 | ✅ |
|
||||
| 2 | 동적 페이지 (Crawl4AI) | 20-30초 | ✅ |
|
||||
| 3A | 경량 검증 (Pydantic) | <100ms | ✅ |
|
||||
| 3B | SPARQL 검증 | <500ms | ✅ |
|
||||
| 4 | Neo4j + 벡터 검색 | 50-200ms | ✅ |
|
||||
|
||||
## 🎯 사용 예시
|
||||
|
||||
### 예시 1: 기본 추출 (10초)
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main ontology --domain perfume
|
||||
curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://wikipedia.org/wiki/Python"
|
||||
```
|
||||
|
||||
단일 URL 수집:
|
||||
응답:
|
||||
```json
|
||||
{
|
||||
"url": "https://wikipedia.org/wiki/Python",
|
||||
"title": "Python - Wikipedia",
|
||||
"entities": [
|
||||
{
|
||||
"id": "E_1",
|
||||
"label": "Python",
|
||||
"type": "ProgrammingLanguage",
|
||||
"confidence": 0.95
|
||||
}
|
||||
],
|
||||
"relations": [...],
|
||||
"extraction_time_sec": 9.5,
|
||||
"validation_passed": true
|
||||
}
|
||||
```
|
||||
|
||||
### 예시 2: 동적 페이지 (25초)
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
|
||||
--config configs/perfume_subscription.yaml \
|
||||
--source official_brand_site \
|
||||
--url https://example.com/perfume/product-page
|
||||
curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://app.example.com&profile=dynamic_page"
|
||||
```
|
||||
|
||||
네트워크 없이 로컬 샘플 HTML로 파이프라인을 확인할 수도 있습니다.
|
||||
### 예시 3: 그래프 수집 + 검색
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
|
||||
--config configs/perfume_subscription.yaml \
|
||||
--source official_brand_site \
|
||||
--url tests/fixtures/sample_perfume.html
|
||||
```
|
||||
# 1. 추출
|
||||
RESULT=$(curl -s -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com")
|
||||
|
||||
분석기 Provider를 바꿀 수도 있습니다. 기본값은 규칙 기반이며, AI Provider는 모델/API 키 또는 로컬 서버 설정이 필요합니다.
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
|
||||
--config configs/perfume_subscription.yaml \
|
||||
--source official_brand_site \
|
||||
--url tests/fixtures/sample_perfume.html \
|
||||
--extractor-provider ollama \
|
||||
--extractor-model llama3.1
|
||||
```
|
||||
|
||||
지원 Provider:
|
||||
|
||||
- `rule_based`: 정규식/키워드 기반 기본 분석기
|
||||
- `openai`: OpenAI 호환 Chat Completions API, `OPENAI_API_KEY`와 모델 필요
|
||||
- `ollama`: 로컬 Ollama, 기본 URL `http://localhost:11434/api/chat`
|
||||
- `lm_studio`: LM Studio OpenAI 호환 서버, 기본 URL `http://localhost:1234/v1/chat/completions`
|
||||
|
||||
LM Studio 사용 순서:
|
||||
|
||||
1. LM Studio에서 `Developer` 또는 Local Server 화면을 엽니다.
|
||||
2. OpenAI Compatible Server를 켭니다.
|
||||
3. 서버 주소가 보통 `http://localhost:1234/v1`인지 확인합니다.
|
||||
4. 웹 UI의 Analyzer에서 `LM Studio`를 선택합니다.
|
||||
5. Base URL은 비워두거나 `http://localhost:1234/v1`을 넣습니다.
|
||||
6. `연결 테스트`로 모델 목록을 불러옵니다.
|
||||
7. 모델이 자동 입력되면 `수집 실행`을 누릅니다.
|
||||
|
||||
Claim 확인:
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db claims --project perfume_subscription
|
||||
```
|
||||
|
||||
추천 예시:
|
||||
|
||||
```bash
|
||||
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db recommend \
|
||||
--project perfume_subscription \
|
||||
--target-type Perfume \
|
||||
--preferences-json "{\"preferred_notes\":[\"Bergamot\",\"Musk\"],\"preferred_moods\":[\"Fresh\"],\"season_context\":\"Summer\"}"
|
||||
```
|
||||
|
||||
## FastAPI 실행
|
||||
|
||||
```bash
|
||||
uvicorn crawler_platform.app.main:app --reload
|
||||
```
|
||||
|
||||
브라우저에서 관리자 UI를 열 수 있습니다.
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8000/
|
||||
```
|
||||
|
||||
관리자 UI에서 가능한 작업:
|
||||
|
||||
- 프로젝트 config 경로로 프로젝트 생성
|
||||
- Source 선택 후 URL 또는 로컬 HTML 샘플 수집
|
||||
- 분석기 Provider 선택: Rule-based, OpenAI, Ollama, LM Studio
|
||||
- 도메인 온톨로지, Entity, Claim 조회
|
||||
- Claim 신뢰도와 사유 수동 수정
|
||||
- Entity ID 기준 병합
|
||||
- 추천용 태그 확인
|
||||
- 사용자 취향 입력 후 추천 결과 테스트
|
||||
|
||||
주요 엔드포인트:
|
||||
|
||||
- `GET /health`
|
||||
- `GET /`
|
||||
- `GET /projects`
|
||||
- `POST /projects`
|
||||
- `GET /ontology/{domain}`
|
||||
- `POST /crawl`
|
||||
- `POST /crawl-site`
|
||||
- `GET /projects/{project_name}/entities`
|
||||
- `GET /projects/{project_name}/claims`
|
||||
- `PATCH /claims/{claim_id}/confidence`
|
||||
- `POST /entities/merge`
|
||||
- `GET /projects/{project_name}/recommendation-tags`
|
||||
- `POST /recommend`
|
||||
|
||||
## 사이트 순회 수집
|
||||
|
||||
단일 상품 URL뿐 아니라 Seed URL에서 시작해 같은 도메인의 링크를 따라가는 수집도 지원합니다.
|
||||
|
||||
```text
|
||||
Seed URL
|
||||
→ robots 확인
|
||||
→ 링크 추출
|
||||
→ same-domain 필터
|
||||
→ URL queue 저장
|
||||
→ depth / max pages 제한
|
||||
→ 각 페이지 fetch
|
||||
→ 상품/브랜드/리뷰 페이지 판별
|
||||
→ 분석
|
||||
→ DB 저장
|
||||
→ 다음 링크 반복
|
||||
```
|
||||
|
||||
웹 UI에서는 `Crawl site from seed`를 사용합니다.
|
||||
|
||||
API 예시:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/crawl-site \
|
||||
# 2. Neo4j에 수집
|
||||
curl -X POST "http://localhost:8000/api/v1/search/ingest" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"config_path": "configs/perfume_subscription.yaml",
|
||||
"source_name": "official_brand_site",
|
||||
"url": "https://example-brand.com",
|
||||
"extractor_provider": "rule_based",
|
||||
"max_depth": 2,
|
||||
"max_pages": 50,
|
||||
"same_domain_only": true,
|
||||
"analyze_page_types": ["product", "brand", "review"]
|
||||
}'
|
||||
-d "{\"entities\": $(echo $RESULT | jq '.entities'), \"relations\": $(echo $RESULT | jq '.relations')}"
|
||||
|
||||
# 3. 벡터 검색
|
||||
curl "http://localhost:8000/api/v1/search/vector?query=programming&limit=10"
|
||||
|
||||
# 4. 그래프 통계
|
||||
curl "http://localhost:8000/api/v1/search/stats"
|
||||
|
||||
# 5. 엔티티 이웃
|
||||
curl "http://localhost:8000/api/v1/search/entity/E_1?depth=1"
|
||||
```
|
||||
|
||||
주의: 검색 결과 페이지나 robots가 막는 페이지는 수집하지 않습니다. 그런 데이터는 공식 API Provider로 붙이는 방식이 맞습니다.
|
||||
## 🔧 설정
|
||||
|
||||
## 향수 도메인 MVP
|
||||
### Phase 선택 (validators.py)
|
||||
|
||||
기본 엔티티:
|
||||
```python
|
||||
# 경량 검증 (기본)
|
||||
guard = OntologyGuard(validator_type="lightweight")
|
||||
|
||||
- `Perfume`
|
||||
- `Brand`
|
||||
- `Note`
|
||||
- `Accord`
|
||||
- `Mood`
|
||||
- `Season`
|
||||
- `Occasion`
|
||||
- `Review`
|
||||
- `Price`
|
||||
- `ProductPage`
|
||||
# SPARQL 검증
|
||||
guard = OntologyGuard(validator_type="ontocast")
|
||||
```
|
||||
|
||||
기본 관계:
|
||||
### Neo4j 연결 (neo4j_adapter.py)
|
||||
|
||||
- `hasBrand`
|
||||
- `hasTopNote`
|
||||
- `hasMiddleNote`
|
||||
- `hasBaseNote`
|
||||
- `hasAccord`
|
||||
- `evokesMood`
|
||||
- `suitableForSeason`
|
||||
- `suitableForOccasion`
|
||||
- `similarTo`
|
||||
- `soldBy`
|
||||
- `hasPrice`
|
||||
- `hasReviewKeyword`
|
||||
```python
|
||||
# 기본값
|
||||
config = Neo4jConfig() # localhost:7687
|
||||
|
||||
규칙 기반 추출기는 `Top notes`, `Middle notes`, `Base notes`, 가격, 무드, 계절, 사용 상황, 리뷰 키워드를 우선 추출합니다.
|
||||
# 커스텀
|
||||
config = Neo4jConfig(
|
||||
uri="bolt://custom-host:7687",
|
||||
username="user",
|
||||
password="pass",
|
||||
database="mydb"
|
||||
)
|
||||
adapter = Neo4jAdapter(config=config)
|
||||
```
|
||||
|
||||
## 확장 방법
|
||||
## 📊 API 문서
|
||||
|
||||
새 도메인을 추가할 때는 다음을 추가하면 됩니다.
|
||||
서버 시작 후:
|
||||
- **Swagger UI**: http://localhost:8000/docs
|
||||
- **ReDoc**: http://localhost:8000/redoc
|
||||
|
||||
1. `configs/{project}.yaml`에 `domain`, `target_entities`, `fields`, `sources`, `ontology` 정의
|
||||
2. `crawler_platform/app/core/ontology/definitions.py`에 도메인 온톨로지 추가
|
||||
3. 필요하면 `crawler_platform/app/domains/{domain}/extractor.py`에 도메인별 Extractor 구현
|
||||
4. 사이트별 HTML 구조가 특수하면 `SiteParser`를 구현하고 `ParserRegistry`에 등록
|
||||
5. OpenAI, Ollama, LM Studio 등 AI 추출은 `AIExtractor`를 상속하는 Provider로 추가
|
||||
### 주요 엔드포인트
|
||||
|
||||
## 컴플라이언스 설계
|
||||
```
|
||||
POST /api/v1/extract/url 추출
|
||||
GET /api/v1/search/stats 통계
|
||||
POST /api/v1/search/vector 벡터 검색
|
||||
GET /api/v1/search/entity/{id} 이웃 탐색
|
||||
POST /api/v1/search/ingest 그래프 수집
|
||||
```
|
||||
|
||||
- `robots.txt` 확인 구조 포함
|
||||
- Source별 rate limit과 User-Agent 적용
|
||||
- retry, timeout 고려
|
||||
- 원문 전체 저장 대신 `evidence_text`와 정제 요약 중심 저장
|
||||
- 상품 설명은 복제 저장보다 Claim, 태그, 요약, 근거 중심으로 사용
|
||||
|
||||
## 테스트
|
||||
## 🧪 테스트
|
||||
|
||||
```bash
|
||||
pytest
|
||||
# Phase 0-1
|
||||
python test_phase0_extraction.py
|
||||
|
||||
# Phase 2
|
||||
python test_phase2_crawl.py
|
||||
|
||||
# Phase 3A
|
||||
python test_phase3_validation.py
|
||||
|
||||
# Phase 3B
|
||||
python test_phase3_option_b.py
|
||||
|
||||
# Phase 4
|
||||
python test_phase4_integration.py
|
||||
```
|
||||
|
||||
외부 테스트 러너가 없을 때는 기본 `unittest` 스모크 테스트를 실행할 수 있습니다.
|
||||
## 📦 의존성
|
||||
|
||||
- **FastAPI**: API 프레임워크
|
||||
- **Trafilatura**: HTML 추출
|
||||
- **Crawl4AI**: 동적 크롤링 (선택)
|
||||
- **Pydantic**: 데이터 검증
|
||||
- **Neo4j**: 그래프 DB (선택)
|
||||
- **SentenceTransformers**: 벡터 임베딩 (선택)
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
```bash
|
||||
python -m unittest tests.test_smoke_unittest -v
|
||||
# Neo4j만
|
||||
docker-compose -f docker-compose.neo4j.yml up -d
|
||||
|
||||
# 전체 스택 (향후)
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
현재 테스트는 향수 규칙 기반 추출, config 로더, 로컬 HTML fetch 경로를 검증합니다.
|
||||
## 📚 상세 문서
|
||||
|
||||
- [구현 요약](IMPLEMENTATION_SUMMARY.md) - Phase 0-4 전체 개요
|
||||
- [Phase 2](PHASE2_COMPLETION.md) - Crawl4AI 동적 크롤링
|
||||
- [Phase 3A](PHASE3_COMPLETION.md) - 경량 검증
|
||||
- [Phase 3B](PHASE3_OPTION_B.md) - SPARQL 검증
|
||||
- [Phase 4](PHASE4_COMPLETION.md) - Neo4j 그래프 + 벡터 검색
|
||||
|
||||
## 🎓 설계 원칙
|
||||
|
||||
1. **Phase-gated**: 각 Phase는 선택사항
|
||||
2. **Pluggable**: 여러 검증 방식 지원
|
||||
3. **Async**: 높은 동시성
|
||||
4. **Resilient**: 의존성 부재 시에도 동작
|
||||
|
||||
## 💡 다음 단계
|
||||
|
||||
### Phase 5: GraphRAG (선택)
|
||||
- RDF ↔ Property Graph 변환
|
||||
- Entity Resolver
|
||||
- Subgraph retrieval
|
||||
|
||||
### Advanced Features
|
||||
- Critic loop (자동 수정)
|
||||
- Few-shot learning
|
||||
- Zero-shot 분류
|
||||
|
||||
## 🔗 관련 링크
|
||||
|
||||
- [Neo4j 문서](https://neo4j.com/docs/)
|
||||
- [SentenceTransformers](https://www.sbert.net/)
|
||||
- [Trafilatura](https://trafilatura.python-engineering.com/)
|
||||
- [FastAPI](https://fastapi.tiangolo.com/)
|
||||
|
||||
## 📝 라이센스
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
**Version**: 0.4.0 (Phase 0-4 완료)
|
||||
**Updated**: 2026-05-14
|
||||
|
||||
Reference in New Issue
Block a user