무제

Start date
End date
Modified

Best Keyword - 전체 구조 문서

DB 필드 설명

seed_results.db에 저장되는 6개 테이블의 각 필드가 의미하는 바를 정리.


공통 개념

파이프라인은 원시 키워드를 수집한 뒤 여러 단계를 거쳐 점수화한다. 각 단계에서 생성된 데이터가 서로 다른 테이블에 저장된다.

수집된 키워드 → [clustered] → [scored] → [seed_report]
                  정제/군집화    점수화      최종 리포트

추가로 연속 확장을 위한 영구 테이블 3개가 매 실행마다 누적된다:

[keyword_snapshots]  [discovered_domains]  [expansion_queue]
 수집 이력 누적       자동 발견 도메인       키워드 확장 대기열

1. clustered 테이블

정제 + 군집화까지 완료된 키워드 후보 목록. 동일한 의미의 키워드가 여러 표현으로 수집되면 하나의 클러스터로 묶이고, 가장 적절한 표현이 대표(representative)로 선정된다.

| 필드 | 타입 | 설명 | |------|------|------| | phrase | TEXT | 형태소 분석 또는 정규식으로 추출된 원시 후보 표현 | | normalized | TEXT | phrase를 정규화한 값 (소문자, 특수문자 제거, 공백 정리) | | source | TEXT | 이 키워드가 수집된 출처 | | platform | TEXT | 수집 플랫폼 (naver, google, internal 등) | | created_at | TIMESTAMP | 수집 시각 | | base_text | TEXT | 이 후보가 추출된 원본 문장 전체 | | token_count | INTEGER | 공백으로 분리한 토큰 수 (예: "스킨 케어" → 2) | | char_len | INTEGER | 정규화된 텍스트의 글자 수 | | cluster_id | INTEGER | 군집화 후 할당된 클러스터 번호. 같은 번호면 유사 표현 | | representative | TEXT | 해당 클러스터에서 선정된 대표 키워드. 이후 점수화의 기준이 됨 |

source 값 목록:

| 값 | 의미 | |----|------| | naver_datalab_topic | 네이버 쇼핑인사이트, 데이터랩, 뉴스 인기기사, 연관검색어 | | google_trends_topic | Google Trends suggestions, Google Trends RSS | | autocomplete | 네이버 자동완성 API, 범용 자동완성 API | | internal_search | 내부 검색 로그 | | product_catalog | 상품 카탈로그 텍스트 | | review | 리뷰 텍스트 | | faq | FAQ 텍스트 | | community_title | 커뮤니티 게시글 제목 | | blog_title | 블로그 제목 | | news_title | 뉴스 제목 |

참고: naver_datalab_topic은 meta.type으로 세부 출처를 구분:

  • shopping_keyword: 쇼핑인사이트 카테고리별 인기 키워드
  • news_trending: 네이버 뉴스 인기기사 제목
  • related_search: 네이버 연관검색어

2. scored 테이블

대표 키워드(representative)별로 종합 점수를 계산한 결과. clustered 테이블의 데이터를 대표 키워드 기준으로 집계(aggregation)하여 생성된다.

기본 정보

| 필드 | 타입 | 설명 | |------|------|------| | representative | TEXT | 대표 키워드. clustered의 같은 이름 필드와 연결됨 | | raw_frequency | INTEGER | 이 대표 키워드가 속한 클러스터 내 정규화된 표현의 총 등장 횟수 | | source_diversity | INTEGER | 이 키워드가 등장한 서로 다른 source의 수. 여러 출처에서 보이면 높음 | | platform_diversity | INTEGER | 이 키워드가 등장한 서로 다른 platform의 수 | | latest_seen | TIMESTAMP | 이 키워드가 마지막으로 관측된 시각 | | sample_phrase | TEXT | 이 클러스터에서 처음 등장한 정규화된 표현 (참고용) |

원시 점수 (Raw Scores)

계산 직후 정규화되기 전의 값들. 상대적 크기 비교에 사용.

| 필드 | 타입 | 단위 | 설명 | |------|------|------|------| | weighted_frequency | REAL | 가중합 | raw_frequency에 소스별 가중치를 곱한 합계. 높을수록 자주, 신뢰할 수 있는 출처에서 등장 | | growth_raw | REAL | 로그스케일 | 최근 7일 vs 이전 기간 대비 증가율. 새로 등장한 키워드는 2 + log(등장횟수), 기존 키워드는 (최근-이전)/이전 | | expandability_raw | REAL | 0~ | 이 키워드가 코퍼스 내에서 하위 키워드를 얼마나 많이 품고 있는지. 자식 키워드 수 × 0.65 + 의도어 결합 가능성 × 0.35. 1~2토큰 키워드에 보너스 | | business_raw | REAL | 0.4~1.0 | 비즈니스 관련성. 키워드 내 비즈니스 사전어(추천, 가격, 비교, 후기, 구매, 효능, 사용법, 브랜드, 순위, 가성비) 포함 여부에 따라 0.4 + n×0.2 |

정규화 점수 (Normalized Scores)

원시 점수를 0~100 스케일로 변환 (min-max scaling). 최종 seed_score 계산에 직접 사용됨.

| 필드 | 타입 | 범위 | 설명 | |------|------|------|------| | frequency_norm | REAL | 0~100 | weighted_frequency의 상대적 순위 | | source_diversity_norm | REAL | 0~100 | source_diversity의 상대적 순위 | | growth_norm | REAL | 0~100 | growth_raw의 상대적 순위 | | expandability_norm | REAL | 0~100 | expandability_raw의 상대적 순위 | | business_norm | REAL | 40~100 | business_raw × 100 |

최종 결과

| 필드 | 타입 | 범위 | 설명 | |------|------|------|------| | seed_score | REAL | 0~100 | 종합 점수. 가중 합산 공식으로 계산 (아래 공식 참조) | | is_promoted_seed | INTEGER | 0 또는 1 | seed_score ≥ 65인 경우 1 (승격된 시드). 그 외 0 | | intent | TEXT | 분류값 | 검색 의도 분류 | | trend_type | TEXT | 분류값 | 시간 트렌드 분류 | | domain | TEXT | 문자열 | 자동 발견된 도메인명 (DomainDiscoverer 결과) |

seed_score 공식:

seed_score =
    source_diversity_norm × 0.30
  + frequency_norm        × 0.25
  + growth_norm           × 0.20
  + expandability_norm    × 0.15
  + business_norm         × 0.10

intent 분류값:

| 값 | 의미 | 판정 기준 | |----|------|-----------| | 구매형 | 구매 의도 | 추천, 가격, 할인, 순위, 브랜드, 가성비, 구매 포함 | | 정보형 | 정보 탐색 | 뜻, 방법, 효능, 원인, 정의, 비교, 차이 포함 | | 문제해결형 | 문제 해결 | 안됨, 오류, 해결, 고장, 증상, 안되는 포함 | | 속성형 | 속성 한정 | 저분자, 무선, 고단백, 민감성, 여성, 연령대 등 포함 | | 토픽허브형 | 일반 토픽 | 위 어느 것도 해당하지 않음. 확장 가능한 중심 주제 |

trend_type 분류값:

| 값 | 의미 | 판정 기준 | |----|------|-----------| | 신규급등 | 새롭게 급등한 키워드 | 최근 7일에만 등장. 이전에는 없었음 | | 급상승 | 빠르게 상승 중 | 최근 7일/30일에 등장, 직전 달에는 없었음 | | 지속상승 | 꾸준히 상승 중 | 직전 달에도 있었고, 최근 순위가 10위 이상 상승 | | 안정 | 꾸준히 유지 | 모든 기간에 등장, 순위 변동 크지 않음 | | 보합 | 중간 상태 | 최근 30일에는 있으나 최근 7일에는 없음 | | 하락 | 감소 추세 | 직전 달에만 있었고 최근에는 사라짐 |


3. seed_report 테이블

최종 리포트. 상위 50개 키워드만 저장. scored 테이블에 클러스터 멤버 정보와 출처 정보를 결합한 형태.

| 필드 | 타입 | 설명 | |------|------|------| | representative | TEXT | 대표 키워드 | | intent | TEXT | 검색 의도 분류 | | trend_type | TEXT | 시간 트렌드 분류 | | seed_score | REAL | 종합 점수 (0~100) | | is_promoted_seed | INTEGER | 시드 승격 여부 (0=미승격, 1=승격) | | weighted_frequency | REAL | 가중 출현 빈도 | | source_diversity | INTEGER | 출처 다양성 (서로 다른 source 수) | | platform_diversity | INTEGER | 플랫폼 다양성 | | growth_raw | REAL | 성장률 원시값 | | expandability_raw | REAL | 확장성 원시값 | | cluster_members | TEXT | 동일 클러스터에 속한 다른 표현들 (최대 8개, 쉼표 구분) | | sources | TEXT | 이 키워드가 수집된 출처 목록 (쉼표 구분) | | latest_seen | TIMESTAMP | 마지막 관측 시각 |


4. keyword_snapshots 테이블 (영구 누적)

매 실행마다 수집된 키워드를 누적 저장. 동일 키워드가 여러 실행에서 관측되면 여러 행이 존재함.

| 필드 | 타입 | 설명 | |------|------|------| | id | INTEGER PK | 자동 증가 ID | | keyword | TEXT | 수집된 키워드 | | source | TEXT | 수집 출처 | | domain | TEXT | 자동 발견된 도메인명 (초기 NULL, 도메인 발견 후 업데이트) | | rank | INTEGER | 해당 카테고리/기간에서의 순위 | | period | TEXT | 시간 기간 (recent_7d, recent_30d, prev_month 등) | | category | TEXT | 쇼핑 카테고리명 (화장품/미용, 식품 등) | | collected_at | TIMESTAMP | 수집 시각 |

인덱스: keyword, domain


5. discovered_domains 테이블 (영구 누적, UPSERT)

DomainDiscoverer가 자동으로 발견한 도메인(세부 카테고리).

| 필드 | 타입 | 설명 | |------|------|------| | domain_name | TEXT PK | 발견된 도메인명 (핵심 키워드 기반 자동 생성) | | core_keywords | TEXT | 도메인을 구성하는 핵심 키워드 목록 (쉼표 구분) | | keyword_count | INTEGER | 도메인에 속한 키워드 수 | | discovered_at | TIMESTAMP | 최초 발견 시각 | | last_expanded_at | TIMESTAMP | 마지막 업데이트 시각 |


6. expansion_queue 테이블 (영구 누적)

키워드 확장 대기열. 매 실행 시 pending 상태의 키워드를 꺼내서 자동완성/연관검색어로 확장한 뒤 done으로 표시.

| 필드 | 타입 | 설명 | |------|------|------| | id | INTEGER PK | 자동 증가 ID | | keyword | TEXT | 확장 대상 키워드 | | source | TEXT | 이 키워드가 발견된 출처 | | status | TEXT | pending 또는 done | | created_at | TIMESTAMP | 큐 등록 시각 |

인덱스: status

동작 흐름:

  1. 매 실행 시 새로 발견된 키워드 → status='pending'으로 INSERT
  2. get_pending_keywords(limit=30) → pending 상태 키워드 최대 30개 조회
  3. 자동완성 + 연관검색어로 확장 → 새 키워드 발견
  4. mark_queue_done(keywords)status='done'으로 업데이트
  5. 새로 발견된 키워드는 다시 pending으로 등록 (다음 실행 시 확장)

테이블 간 관계

clustered                        scored                         seed_report
┌──────────────────┐    ┌─────────────────────┐    ┌──────────────────────┐
│ phrase           │    │ representative (PK) │◄───│ representative       │
│ normalized       │    │ raw_frequency       │    │ intent               │
│ source           │──┐ │ source_diversity    │    │ trend_type           │
│ platform         │  │ │ ...                 │    │ seed_score           │
│ created_at       │  │ │ seed_score          │    │ is_promoted_seed     │
│ base_text        │  │ │ is_promoted_seed    │    │ ...                  │
│ token_count      │  │ │ intent              │    │ cluster_members      │
│ char_len         │  │ │ trend_type          │    │ sources              │
│ cluster_id       │  └─│ representative 기준  │    └──────────────────────┘
│ representative ──│────│   으로 groupby 집계   │
└──────────────────┘    └─────────────────────┘

keyword_snapshots              discovered_domains           expansion_queue
┌──────────────────┐    ┌───────────────────────┐    ┌──────────────────┐
│ keyword          │◄───│ domain_name (PK)      │    │ keyword          │
│ source           │    │ core_keywords         │    │ source           │
│ domain ──────────│───►│ keyword_count         │    │ status           │
│ rank             │    │ discovered_at         │    │ created_at       │
│ period           │    │ last_expanded_at      │    └──────────────────┘
│ category         │    └───────────────────────┘
│ collected_at     │
└──────────────────┘
  • clustered → scored: 같은 representative 값을 가진 행들을 groupby하여 집계
  • scored → seed_report: scored에 클러스터 멤버/출처 정보를 조인하여 상위 50개만 추출
  • keyword_snapshots ↔ discovered_domains: snapshots의 domain이 domains의 domain_name을 참조
  • expansion_queue → keyword_snapshots: 큐에서 확장된 키워드가 새 snapshots에 기록됨

프로젝트 개요

네이버/구글에서 키워드를 자동 수집하여, 끝없이 연관 키워드와 도메인을 발견하는 연속 파이프라인. 매 실행마다 이전에 발견한 키워드에서 확장하여 새 키워드와 도메인을 계속 찾아냄. 수동 키워드 입력 없이 전체 과정이 자동으로 동작.

실행

uv run python main.py

출력

  • 터미널: AUTO SEED REPORT (순위표) + 도메인 발견 결과 + 확장 큐 상태
  • SQLite: seed_results.db
    • 매 실행 덮어쓰기: clustered, scored, seed_report
    • 영구 누적: keyword_snapshots, discovered_domains, expansion_queue

전체 파이프라인 흐름

┌─────────────────────────────────────────────────────────────────┐
│                 A. 트렌드 키워드 수집 (Collectors)               │
│                                                                 │
│  네이버 쇼핑인사이트 (9카테고리 × 3기간 × 2페이지) ──┐          │
│  Google Trends suggestions (pytrends)             ──┤          │
│  Google Trends RSS (실시간 급상승)                 ──┤──→ TextRecord[]│
│  네이버 뉴스 인기기사 (실시간)                     ──┘          │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│             B. 확장 큐 처리 (Expansion Queue)                    │
│                                                                 │
│  이전 실행에서 pending 상태로 남은 키워드 꺼냄                   │
│  → 네이버 자동완성 수집                                         │
│  → 네이버 연관검색어 수집                                       │
│  → done 표시                                                    │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│          C. 상위 키워드 확장 (Top Keyword Expansion)             │
│                                                                 │
│  이번에 수집한 상위 키워드에 대해                                │
│  → 네이버 자동완성 수집                                         │
│  → 네이버 연관검색어 수집                                       │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│             D. 도메인 자동 발견 (Domain Discovery)               │
│                                                                 │
│  수집된 전체 데이터에서 공동출현 네트워크 구성                   │
│  → BFS 연결요소 탐색 → 도메인(세부 카테고리) 자동 추출          │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│                  후보 생성 (Generator)                           │
│                                                                 │
│  TextRecord → 형태소 분석/정규식 → 후보 phrase 추출             │
│  kiwipiepy 있으면 형태소 분석, 없으면 정규식 fallback           │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│                  정제 (Cleaner)                                  │
│                                                                 │
│  불용어 제거 / 길이 필터 / 숫자만 제거 / URL 제거               │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│                 군집화 (Clusterer)                               │
│                                                                 │
│  유사 표현을 하나로 묶고 대표 키워드 선정                       │
│  - 포함관계 (a in b)                                           │
│  - Fuzzy matching (rapidfuzz ≥ 90)                              │
│  - Cosine similarity (TF-IDF ≥ 0.82)                           │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│                 점수화 (Scorer)                                  │
│                                                                 │
│  source_diversity  × 0.30                                       │
│  frequency         × 0.25                                       │
│  growth            × 0.20                                       │
│  expandability     × 0.15                                       │
│  business_relevance × 0.10                                      │
│                       ─────                                      │
│  seed_score (0~100)                                              │
│  is_promoted_seed ≥ 65                                          │
└────────────────────────────────┬────────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────────┐
│              분류 + 리포트 (Classifiers)                         │
│                                                                 │
│  intent:     구매형 / 정보형 / 문제해결형 / 속성형 /            │
│              토픽허브형                                         │
│  trend_type: 신규급등 / 급상승 / 지속상승 / 안정 /             │
│              보합 / 하락                                        │
└────────────────────────────────┬────────────────────────────────┘
                                 │
                        ▼
          E. 영구 저장 + 다음 실행 준비
          ├── keyword_snapshots: 누적 키워드 저장
          ├── discovered_domains: 발견된 도메인 UPSERT
          ├── expansion_queue: 새 키워드 pending 추가
          ├── clustered / scored / seed_report: 이번 실행 결과 덮어쓰기
          └── 터미널 출력 (리포트 + 통계)

파일 구성

best_keyword/
├── main.py              # 전체 파이프라인 (단일 파일)
├── pyproject.toml        # 의존성 관리 (uv)
├── .env                  # 환경변수 (API 키 등)
├── seed_results.db       # 출력 SQLite (실행 후 생성)
└── docs/
    ├── architecture.md   # 자동 시드 개념 설계 문서
    ├── structure.md      # 이 파일 - 구조 문서
    └── db_fields.md      # DB 필드 설명

데이터 모델

TextRecord

수집된 원시 텍스트 레코드.

| 필드 | 타입 | 설명 | |------|------|------| | text | str | 원문 | | source | str | 출처 (naver_datalab_topic, google_trends_topic, autocomplete 등) | | created_at | datetime | 수집 시각 | | platform | str | 플랫폼 (naver, google, internal) | | meta | dict | 부가 정보 (카테고리, 기간, 순위, seed 키워드 등) |

KeywordTrendPoint

시계열 트렌드 데이터 포인트.

| 필드 | 타입 | 설명 | |------|------|------| | keyword | str | 키워드 | | date | str | 날짜 (YYYY-MM-DD) | | value | float | 트렌드 값 | | source | str | 출처 | | platform | str | 플랫폼 |

TopicGroup

네이버 DataLab 조회용 키워드 그룹.

| 필드 | 타입 | 설명 | |------|------|------| | group_name | str | 그룹명 | | keywords | list[str] | 그룹에 포함된 키워드 목록 |

DomainCluster

도메인 자동 발견 결과.

| 필드 | 타입 | 설명 | |------|------|------| | domain_name | str | 발견된 도메인명 (핵심 키워드 기반) | | core_keywords | list[str] | 도메인을 구성하는 핵심 키워드 | | member_count | int | 도메인에 속한 키워드 수 |


수집기 (Collectors)

1. NaverShoppingInsightCollector

네이버 쇼핑인사이트에서 카테고리별 인기 검색어 자동 수집. API 키 불필요. 내부 엔드포인트 직접 호출.

수집 범위:

  • 9개 대분류 카테고리 전체
  • 3개 시간 기간 (최근 7일 / 최근 30일 / 직전 달)
  • 카테고리당 2페이지 (40개 키워드)
  • 총 9 × 3 × 40 = 최대 1,080개 키워드

카테고리 목록:

| 코드 | 카테고리 | |------|----------| | 50000000 | 패션의류 | | 50000001 | 패션잡화 | | 50000002 | 화장품/미용 | | 50000003 | 디지털/가전 | | 50000004 | 가구/인테리어 | | 50000005 | 출산/육아 | | 50000006 | 식품 | | 50000007 | 스포츠/레저 | | 50000008 | 생활/건강 |

시간 기간 구조:

| 기간 라벨 | 범위 | timeUnit | 용도 | |-----------|------|----------|------| | recent_7d | 최근 7일 | date | 단기 급등 감지 | | recent_30d | 최근 30일 | month | 중기 트렌드 | | prev_month | 직전 달 전체 | month | 비교 기준선 |

엔드포인트:

POST https://datalab.naver.com/shoppingInsight/getCategoryKeywordRank.naver

meta 구조:

{
    "type": "shopping_keyword",
    "category": "화장품/미용",
    "period": "recent_7d",
    "rank": 1,
    "page": 1,
}

2. GoogleTrendingRSSCollector

Google Trends RSS 피드에서 실시간 급상승 검색어 수집. API 키 불필요.

엔드포인트:

GET https://trends.google.com/trending/rss?geo=KR

특징:

  • RSS/Atom 형식 모두 파싱 지원
  • 약 10~20개 일일 급상승 검색어 반환
  • 트래픽 수치, 관련 뉴스, 발행 시간 포함
  • source="google_trends_topic", meta={"provider": "rss"}

3. NaverNewsTrendingCollector

네이버 뉴스 인기기사 제목에서 트렌드 키워드 추출. API 키 불필요. HTML 파싱.

엔드포인트:

GET https://news.naver.com/main/ranking/popularMemo.naver

특징:

  • 약 250~370개 인기 뉴스 제목 추출
  • class="list_title" 파싱으로 제목 추출
  • source="naver_datalab_topic", meta={"type": "news_trending"}

4. PyTrendsCollector

Google Trends에서 관련 키워드 수집. pytrends 라이브러리 사용. suggestions() 메서드만 안정적으로 동작.

동작:

  • seed_terms 목록의 각 단어에 대해 suggestions() 호출
  • 각 seed당 약 4~5개 관련 키워드 반환
  • 429 방지를 위해 요청 간 0.5초 대기

기본 seed_terms: 스킨케어, 건강, 다이어트, 운동, 뷰티, 식품, 영양


5. NaverAutocompleteCollector

네이버 자동완성 API에서 연관 키워드 수집. API 키 불필요.

엔드포인트:

GET https://ac.search.naver.com/nx/ac?st=10&r_format=json&q={키워드}

특징:

  • 키워드당 약 8개 자동완성 결과 반환
  • 요청 간 0.3초 대기
  • source="autocomplete", meta={"type": "naver_autocomplete", "seed": seed}
  • 확장 큐 처리와 상위 키워드 확장에 사용

6. NaverRelatedSearchCollector

네이버 검색 결과의 연관검색어 수집. API 키 불필요. HTML 파싱.

엔드포인트:

GET https://search.naver.com/search.naver?query={키워드}

특징:

  • <div class="related_srch"> 에서 연관검색어 추출
  • 요청 간 0.5초 대기
  • source="naver_datalab_topic", meta={"type": "related_search", "seed": seed}
  • 확장 큐 처리와 상위 키워드 확장에 사용

7. NaverDataLabDirectCollector

네이버 데이터랩 검색어 트렌드 직접 수집. API 키 불필요. 웹페이지 내부 흐름을 모방.

동작 원리:

  1. POST /qcHash.naverhashKey 발급
  2. GET /keyword/trendResult.naver?hashKey=... → 결과 HTML
  3. HTML에서 <div id="graph_data"> 내 JSON 추출

용도: 쇼핑인사이트에서 발견한 상위 키워드의 트렌드 검증 (시계열 데이터).


8. GenericJSONAutocompleteCollector (선택)

자동완성 API 범용 수집기. 특정 엔드포인트를 하드코딩하지 않고 .env 설정으로 주입.

활성화 조건: .env에서 AUTOCOMPLETE_ENABLED=true 설정 시에만 동작.


연속 확장 시스템

파이프라인은 단발성이 아니라 매 실행마다 누적되는 구조.

확장 큐 (expansion_queue)

실행 1: 트렌드 수집 → 1,000개 키워드 발견 → 500개 pending 등록
실행 2: 500개 pending → 자동완성/연관검색어 확장 → 300개 새 키워드 → 200개 pending 등록
실행 3: 200개 pending → 확장 → 150개 새 키워드 → ...
  • 매 실행 시 pending 상태의 키워드를 최대 30개 꺼내서 확장
  • 확장 완료 후 done 표시
  • 새로 발견된 키워드는 다시 pending으로 등록
  • 큐에 이미 있는 키워드는 중복 등록하지 않음

도메인 자동 발견 (DomainDiscoverer)

수집된 키워드 간 공동출현 네트워크를 구성하여 자동으로 세부 카테고리를 발견.

알고리즘:

  1. 자동완성/연관검색어 결과에서 키워드-시드 간 그래프 구성
  2. BFS로 연결요소(connected components) 탐색
  3. 각 연결요소를 하나의 도메인으로 간주
  4. 중심성(centrality) 기반으로 핵심 키워드 선정 → 도메인명 생성
  5. 그래프가 없을 경우 카테고리 기반 폴백

처리 파이프라인 (AutoSeedPipeline)

1단계: 후보 생성 (SeedCandidateGenerator)

TextRecordtext에서 키워드 후보를 추출.

형태소 분석기 (kiwipiepy 설치 시):

  • 명사(NNG, NNP), 외국어(SL), 숫자(SN) 토큰 추출
  • 연속 명사 구합 (1~4단어)
  • bi-gram, tri-gram 생성

정규식 fallback (kiwipiepy 미설치 시):

  • [0-9a-zA-Z가-힣]{2,} 패턴으로 토큰 추출
  • bi-gram, tri-gram 생성

2단계: 정제 (KeywordCleaner)

후보에서 노이즈 제거.

필터 규칙:

  • 텍스트 길이 < 2 제거
  • 토큰 수 1~4개만 유지
  • 불용어 제거 (방법, 후기, 정리, 진짜, 너무 등)
  • 숫자만 있는 표현 제거
  • URL 포함 표현 제거
  • 모든 토큰이 불용어인 경우 제거

3단계: 군집화 (KeywordClusterer)

유사한 표현을 하나의 클러스터로 묶음.

유사도 판정 (셋 중 하나라도 만족하면 동일 클러스터):

  1. 포함관계: 짧은 쪽이 긴 쪽의 75% 이상 차지
  2. Fuzzy matching: rapidfuzz.ratio() ≥ 90
  3. Cosine similarity: TF-IDF char_wb ngram 코사인 ≥ 0.82

대표 키워드 선정 기준:

  • 1~2 토큰이 유리 (보너스 1.0 vs 0.7)
  • 너무 긴 표현은 패널티
  • 비즈니스 관련어 포함 시 우대

4단계: 점수화 (SeedScorer)

각 대표 키워드에 종합 점수 부여.

점수 공식:

seed_score =
    source_diversity_norm × 0.30  ← 여러 소스에서 등장
  + frequency_norm        × 0.25  ← 자주 등장
  + growth_norm           × 0.20  ← 최근 증가 추세
  + expandability_norm    × 0.15  ← 하위 키워드 확장성
  + business_norm         × 0.10  ← 비즈니스 관련성

소스별 가중치:

| 소스 | 가중치 | |------|--------| | internal_search | 1.00 | | naver_datalab_topic | 0.90 | | google_trends_topic | 0.90 | | autocomplete | 0.85 | | community_title | 0.75 | | faq | 0.70 | | product_catalog | 0.70 | | review | 0.65 | | blog_title | 0.60 | | news_title | 0.55 |

승격 기준: seed_score ≥ 65is_promoted_seed = True

5단계: 분류 (Classifiers)

IntentClassifier (검색 의도)

| 분류 | 힌트 키워드 | |------|------------| | 구매형 | 추천, 가격, 할인, 순위, 브랜드, 가성비, 구매 | | 정보형 | 뜻, 방법, 효능, 원인, 정의, 비교, 차이 | | 문제해결형 | 안됨, 오류, 해결, 고장, 증상 | | 속성형 | 저분자, 무선, 고단백, 민감성, 여성, 30대 등 | | 토픽허브형 | 위 분류에 해당하지 않는 일반 토픽 |

TrendPeriodClassifier (시간 트렌드)

3개 기간(최근 7일 / 최근 30일 / 직전 달)의 출현 패턴 비교.

| 분류 | 조건 | |------|------| | 신규급등 | recent_7d에만 등장 | | 급상승 | recent에 등장, prev_month에 없음 | | 지속상승 | prev_month 대비 순위 10위 이상 상승 | | 안정 | 모든 기간에 꾸준히 등장 | | 보합 | recent_30d에만 등장 | | 하락 | prev_month에만 있고 recent에 없음 |


SQLite 테이블

매 실행 덮어쓰기 (seed_results.db)

| 테이블 | 설명 | |--------|------| | clustered | 군집화 결과 (정규화된 키워드 → 클러스터 매핑) | | scored | 점수화 결과 (대표 키워드별 종합 점수) | | seed_report | 최종 리포트 (상위 50개) |

영구 누적 (seed_results.db)

| 테이블 | 설명 | |--------|------| | keyword_snapshots | 매 실행 수집 키워드 이력 (keyword, source, domain, rank, period, category) | | discovered_domains | 자동 발견된 도메인 (domain_name PK, core_keywords, keyword_count) | | expansion_queue | 키워드 확장 대기열 (keyword, status: pending/done) |


환경변수 (.env)

# 네이버 DataLab 공식 API (현재 미사용, 직접 수집 방식 사용 중)
NAVER_CLIENT_ID=...
NAVER_CLIENT_SECRET=...

# Google Trends 공식 API alpha (접근 권한 필요시)
GOOGLE_TRENDS_ALPHA_ENABLED=false
GOOGLE_TRENDS_ALPHA_BASE_URL=
GOOGLE_TRENDS_ALPHA_TOKEN=

# 자동완성 수집기 (선택, GenericJSONAutocompleteCollector용)
AUTOCOMPLETE_ENABLED=false
AUTOCOMPLETE_URL=
AUTOCOMPLETE_METHOD=GET
AUTOCOMPLETE_QUERY_PARAM=q
AUTOCOMPLETE_PLATFORM=

의존성 (pyproject.toml)

| 패키지 | 용도 | 필수 | |--------|------|------| | pandas | 데이터 처리 | O | | numpy | 수치 계산 | O | | requests | HTTP 요청 | O | | rapidfuzz | 퍼지 문자열 매칭 | O | | scikit-learn | TF-IDF 코사인 유사도 | O | | python-dateutil | 날짜 처리 | O | | python-dotenv | 환경변수 로드 | O | | pytrends | Google Trends 수집 | O | | beautifulsoup4 | HTML 파싱 | O | | kiwipiepy | 한국어 형태소 분석 | X (없으면 정규식 fallback) |


클래스 관계도

HTTPClient ──────────────────────────────────────────────────┐
    │                                                        │
    ├── NaverShoppingInsightCollector                        │
    │       → list[TextRecord] (9카테고리 × 3기간 × 2페이지)  │
    │                                                        │
    ├── GoogleTrendingRSSCollector                           │
    │       → list[TextRecord] (실시간 급상승 검색어)        │
    │                                                        │
    ├── NaverNewsTrendingCollector                           │
    │       → list[TextRecord] (인기 뉴스 제목)              │
    │                                                        │
    ├── PyTrendsCollector                                    │
    │       → list[TextRecord] (Google 관련 키워드)          │
    │                                                        │
    ├── NaverAutocompleteCollector                           │
    │       → list[TextRecord] (네이버 자동완성)             │
    │                                                        │
    ├── NaverRelatedSearchCollector                          │
    │       → list[TextRecord] (네이버 연관검색어)           │
    │                                                        │
    ├── NaverDataLabDirectCollector                          │
    │       → list[TextRecord] + list[KeywordTrendPoint]     │
    │                                                        │
    └── GenericJSONAutocompleteCollector (선택)              │

AutoSeedPipeline
    ├── KoreanPhraseExtractor  (형태소/정규식)
    ├── SeedCandidateGenerator (후보 생성)
    ├── KeywordCleaner         (정제)
    ├── KeywordClusterer       (군집화: fuzzy + TF-IDF)
    ├── ExpandabilityEstimator (확장성 평가)
    ├── BusinessRelevanceScorer(비즈니스 관련성)
    ├── GrowthCalculator       (성장률)
    ├── SeedScorer             (종합 점수)
    ├── IntentClassifier       (검색 의도)
    └── TrendPeriodClassifier  (시간 트렌드)

DomainDiscoverer
    └── discover(records) → list[DomainCluster]
        (공동출현 네트워크 → BFS 연결요소 → 도메인 추출)

run_cycle()  ← main()에서 호출
    ├── A. 트렌드 수집 (쇼핑인사이트 + pytrends + Google RSS + 뉴스)
    ├── B. 확장 큐 처리 (pending → 자동완성 + 연관검색어 → done)
    ├── C. 상위 키워드 확장 (자동완성 + 연관검색어)
    ├── D. 도메인 자동 발견 (DomainDiscoverer)
    └── E. 점수화 + 영구 저장 (keyword_snapshots + expansion_queue)

Rate Limit 대응

| 수집기 | 대응 방식 | |--------|----------| | NaverShoppingInsightCollector | 요청 간 0.8초 대기 | | NaverDataLabDirectCollector | 요청 간 1.0초 대기 | | PyTrendsCollector | suggestions() 호출 간 0.5초 대기 | | NaverAutocompleteCollector | 요청 간 0.3초 대기 | | NaverRelatedSearchCollector | 요청 간 0.5초 대기 | | NaverNewsTrendingCollector | 단일 요청 (페이지당 1회) | | GoogleTrendingRSSCollector | 단일 요청 (피드당 1회) | | 공통 | HTTPClient 재시도 2회, 지수 백오프 |

← View in Project