만료된 도메인 기회 찾기
특정 틈새시장에 대한 만료된 도메인 후보를 평가합니다. 주제 적합성, 과거 활동 수준, 이력 청결도, 리다이렉트 적합성에 따라 점수를 매깁니다. 인간의 검토를 위해 보수적이고 설명 가능한 후보 목록을 출력합니다.
중요 규칙: 모든 추천에는 긍정적인 근거(why_selected)와 주의 근거(why_risky)가 모두 포함되어야 합니다. 설명 없이 점수만 출력하지 마십시오.
보수적 기본 원칙: 신호가 불완전하거나 모순될 경우, 신뢰 수준을 낮춥니다. 모호한 후보를 강력한 기회로 표시하지 마십시오. 데이터가 부족하면 신뢰도가 낮아지며, 절대 부풀려지지 않습니다.
남용 방지 규칙: 과거 주제가 대상 틈새시장과 일치하지 않는 경우, 관련 없는 리다이렉트, PBN 구축, 도메인 용도 변경을 절대 장려하지 마십시오. 전체 남용 방지 정책은 references/guardrails.md를 참조하세요.
단계 1: 설정 확인
다른 작업을 하기 전에 환경을 확인합니다.
curl과 python3(또는 python)이 사용 가능한지 확인합니다:
curl --version > /dev/null 2>&1 && echo "curl: 사용 가능" || echo "curl: 없음"
python3 --version 2>/dev/null || python --version 2>/dev/null || echo "python: 없음"
향상된 틈새시장 관련성 점수 매기기를 위한 선택적 LLM API 키를 확인합니다:
echo "LLM_API_KEY: ${LLM_API_KEY:+설정됨}"
curl 또는 python이 없는 경우:
중단합니다. 사용자에게 다음 메시지를 표시합니다: "이 스킬은 curl과 Python 3.10+가 필요합니다. 설치 후 다시 시도해 주세요."
LLM_API_KEY가 설정되지 않은 경우:
계속 진행합니다. 이 스킬은 규칙 기반 점수 매기기(도메인 문자열 매칭, Wayback 제목 분석, 키워드 중복)만 사용합니다. 사용자에게 다음 메시지를 알립니다: "규칙 기반 전용 모드로 실행 중입니다. 향상된 틈새시장 관련성 점수 매기기를 위해 LLM_API_KEY를 설정하세요."
LLM_API_KEY가 설정된 경우:
이 스킬은 주제 관련성 분석을 위해 LLM 기반 점수 매기기를 사용합니다. 이를 통해 틈새시장 적합성에 대한 더 깊은 맥락적 평가가 가능합니다.
QA: 점수 매기기 모드(llm-향상 또는 규칙-기반-전용)를 명시하고 도구 사용 가능 여부를 확인합니다.
단계 2: 입력 수집
사용자로부터 필수 및 선택적 입력을 수집합니다.
필수:
target_niche(문자열): 평가할 핵심 틈새시장. 예: "developer tools", "AI SaaS", "cybersecurity", "fintech".
선택적(제공되지 않은 경우에만 질문):
seed_keywords(배열): 주제 매칭을 구체화할 키워드. 제공되지 않으면, 틈새시장 이름에서 자동으로 3-5개의 키워드를 추출합니다.candidate_domains(배열): 평가할 특정 도메인. 제공되지 않으면 사용자에게 요청합니다.discovery_source(문자열): 후보가 유래된 출처 —manual,expireddomains-net,external-feed.min_snapshots(정수): 최소 과거 스냅샷 임계값. 기본값: 10.max_risk_level(문자열):low,medium, 또는high. 위험한 후보를 얼마나 적극적으로 필터링할지 제어합니다. 기본값:medium.intended_use(문자열):rebuild,redirect, 또는either. 기본값:either.
candidate_domains가 제공되지 않은 경우:
질문: "평가할 만료된 도메인 후보 목록을 제공해 주세요. 다음 중 하나를 선택할 수 있습니다:
- 도메인 이름 붙여넣기 (줄당 하나 또는 쉼표로 구분)
- 줄당 하나의 도메인이 포함된 텍스트 파일 경로 제공
- 'example' 입력하여 'developer tools' 틈새시장에 대한 내장 데모 세트로 실행"
사용자가 'example'을 입력한 경우: 다음 데모 세트를 사용합니다:
devtoolsweekly.com
codeshipnews.io
stackforgeapp.com
quickseorank.net
bestcheaphosting247.com
cloudbuildpro.dev
reactwidgetlib.com
megadealsshop.xyz
모든 입력을 수집한 후, 다음을 확인합니다: "대상 틈새시장: [niche]. [N]개의 후보 도메인을 평가합니다. 점수 매기기 모드: [mode]. 의도된 용도: [use]."
단계 3: 후보 정규화
점수 매기기 전에 후보 목록을 정리하고 검증합니다.
python3 -c "
import sys, re
domains = '''CANDIDATE_LIST_HERE'''.strip().split('\n')
seen = set()
valid = []
invalid = []
for d in domains:
d = d.strip().lower()
# 프로토콜 및 경로 제거
d = re.sub(r'^https?://', '', d)
d = d.split('/')[0]
d = d.strip('.')
if not d:
continue
# 기본 TLD 검증
if '.' not in d or len(d) < 4:
invalid.append(d)
continue
# 중복 제거
if d in seen:
continue
seen.add(d)
valid.append(d)
print(f'유효한 후보: {len(valid)}')
print(f'제거됨(유효하지 않음/중복): {len(invalid)}')
for v in valid:
print(f' ✓ {v}')
for i in invalid:
print(f' ✗ {i} (잘못된 형식)')
"
CANDIDATE_LIST_HERE를 단계 2의 실제 도메인 목록으로 바꿉니다.
상태: "정규화 후 [N]개의 유효한 후보. [M]개 제거됨(유효하지 않음/중복)."
0개의 유효한 후보가 남으면 중단하고 사용자에게 다음 메시지를 표시합니다: "유효한 도메인 후보를 찾을 수 없습니다. 'example.com' 형식으로 도메인 이름을 제공해 주세요."
단계 4: 신호 수집
각 유효한 후보에 대해 무료 공개 소스에서 신호를 수집합니다. 도메인별로 순차적으로 다음 확인을 실행합니다.
4a: Wayback CDX API — 기록 스냅샷
Wayback Machine에서 모든 과거 스냅샷을 쿼리합니다. limit=100000을 사용하고 from/to 매개변수를 의도적으로 생략하여 CDX가 도메인의 전체 수명 기간 동안 스냅샷을 반환하도록 합니다. 결과는 타임스탬프 기준 오름차순(가장 오래된 것 먼저)으로 정렬되므로 first_capture 및 last_capture가 정확합니다:
curl -s "https://web.archive.org/cdx/search/cdx?url=DOMAIN_HERE&output=json&fl=timestamp,statuscode&collapse=timestamp:6&limit=100000" \
| python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
if len(data) <= 1:
print(json.dumps({'domain': 'DOMAIN_HERE', 'snapshots': 0, 'first_capture': None, 'last_capture': None, 'status_codes': {}, 'years_active': 0}))
else:
rows = data[1:] # 헤더 행 건너뛰기
timestamps = [r[0] for r in rows] # 이미 오름차순 (가장 오래된 것 먼저)
statuses = [r[1] for r in rows]
status_counts = {}
for s in statuses:
status_counts[s] = status_counts.get(s, 0) + 1
first_year = int(timestamps[0][:4])
last_year = int(timestamps[-1][:4])
print(json.dumps({
'domain': 'DOMAIN_HERE',
'snapshots': len(rows),
'first_capture': timestamps[0],
'last_capture': timestamps[-1],
'status_codes': status_counts,
'years_active': last_year - first_year + 1
}))
except:
print(json.dumps({'domain': 'DOMAIN_HERE', 'snapshots': 0, 'error': 'wayback_api_failed'}))
"
속도 제한: 서비스에 예의를 갖추기 위해 Wayback API 호출 사이에 2초를 기다립니다.
4b: Wayback 콘텐츠 샘플링 — 과거 페이지 제목
스냅샷이 0개보다 많은 후보의 경우 가장 최근 스냅샷을 가져와 페이지 제목(주제 관련성 점수 매기에 사용됨)을 추출합니다:
curl -s -L "https://web.archive.org/web/LATEST_TIMESTAMP/http://DOMAIN_HERE" \
| python3 -c "
import sys, re
html = sys.stdin.read()[:50000]
title_match = re.search(r'<title[^>]*>(.*?)</title>', html, re.IGNORECASE | re.DOTALL)
title = title_match.group(1).strip() if title_match else 'no title found'
# 메타 설명도 추출
meta_match = re.search(r'<meta[^>]*name=[\"']description[\"'][^>]*content=[\"'](.*?)[\"']', html, re.IGNORECASE)
desc = meta_match.group(1).strip() if meta_match else 'no description found'
print(f'Title: {title}')
print(f'Description: {desc}')
"
LATEST_TIMESTAMP를 단계 4a의 가장 최근 타임스탬프로 바꿉니다.
4c: RDAP 조회 — 등록 상태
OS 종속 WHOIS를 대체하는 크로스 플랫폼 HTTP 기반 RDAP 표준을 사용합니다. RDAP에서 HTTP 404는 도메인이 등록되지 않았음을 의미합니다(즉, 진정으로 사용 가능하거나 추적되지 않음). 이는 네트워크 오류와 구별됩니다. 두 경우를 모두 명시적으로 처리합니다:
python3 -c "
import urllib.request, urllib.error, json
domain = 'DOMAIN_HERE'
try:
req = urllib.request.Request(
f'https://rdap.org/domain/{domain}',
headers={'User-Agent': 'Mozilla/5.0'}
)
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
registrar = 'unknown'
created = 'unknown'
for entity in data.get('entities', []):
if 'registrar' in entity.get('roles', []):
try:
registrar = entity.get('vcardArray', [[]])[1][0][3]
except Exception:
pass
for event in data.get('events', []):
if event.get('eventAction') == 'registration':
created = event.get('eventDate', 'unknown')
print(json.dumps({
'domain': domain,
'status': 'registered',
'registrar': registrar,
'created': created
}))
except urllib.error.HTTPError as e:
if e.code == 404:
# 도메인에 RDAP 객체가 없음 — 등록되지 않았거나 RDAP 범위에 없음
print(json.dumps({'domain': domain, 'status': 'unregistered_or_no_rdap_object'}))
else:
print(json.dumps({'domain': domain, 'error': f'rdap_http_error_{e.code}'}))
except Exception:
print(json.dumps({'domain': domain, 'error': 'rdap_lookup_failed'}))
"
4d: 도메인 문자열 분석 — 키워드 매칭
도메인 이름과 대상 틈새시장/시드 키워드 간의 키워드 중복을 점수화합니다:
python3 -c "
import re, json
domain = 'DOMAIN_HERE'
niche = 'NICHE_HERE'
seeds = SEEDS_JSON_HERE # 예: ['devops', 'ci/cd', 'code editor']
# 도메인에서 단어 추출
domain_base = domain.rsplit('.', 1)[0] # TLD 제거
domain_words = re.split(r'[-_.]', domain_base.lower())
# 틈새시장 단어 확인
niche_words = niche.lower().split()
all_keywords = set(niche_words + [s.lower() for s in seeds])
matches = [w for w in domain_words if any(kw in w or w in kw for kw in all_keywords)]
match_ratio = len(matches) / max(len(domain_words), 1)
print(json.dumps({
'domain': domain,
'domain_words': domain_words,
'keyword_matches': matches,
'match_ratio': round(match_ratio, 2)
}))
"
4e: Gemini LLM 틈새시장 관련성 평가 (LLM_API_KEY가 설정된 경우)
LLM API 키가 구성된 경우, 수집된 신호와 함께 모든 후보를 배치로 처리하고 맥락적 틈새시장 관련성 평가를 요청합니다.
참고: 아래 요청/응답 형식은 Gemini API(generateContent 형식)를 사용합니다. 수정 없이 OpenAI 스타일 엔드포인트와 호환되지 않습니다. 다른 공급자를 사용하는 경우 JSON 본문과 응답 구문 분석을 조정해야 합니다.
cat > /tmp/domain-relevance-request.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "You are an SEO research analyst. For each expired domain candidate provided, assess its topical relevance to the specified target niche. Consider the domain name, historical page title, and meta description. For each domain, output a JSON object with: domain (string), relevance_score (integer 1-10), relevance_rationale (one sentence explaining the score), redirect_plausibility (integer 1-10), redirect_rationale (one sentence). Output only a JSON array. No commentary before or after."
}]
},
"contents": [{
"parts": [{
"text": "DOMAIN_SIGNALS_AND_NICHE_CONTEXT_HERE"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
DOMAIN_SIGNALS_AND_NICHE_CONTEXT_HERE를 다음으로 바꿉니다:
- 대상 틈새시장 및 시드 키워드
- 각 후보에 대해: 도메인 이름, 과거 제목, 설명, 키워드 매칭 데이터
요청을 Gemini API로 보냅니다:
curl -s -X POST \
"${LLM_API_ENDPOINT:-https://generativelanguage.googleapis.com/v1beta}/models/${LLM_MODEL:-gemini-2.0-flash}:generateContent?key=$LLM_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/domain-relevance-request.json \
| python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
text = d['candidates'][0]['content']['parts'][0]['text']
print(text)
except (KeyError, IndexError, json.JSONDecodeError) as e:
print(json.dumps({'error': 'llm_response_parse_failed', 'detail': str(e)}))
"
LLM 호출 또는 응답 구문 분석에 실패하면 오류를 기록하고 규칙 기반 점수 매기기만으로 계속 진행합니다. 워크플로를 중단하지 마십시오.
모든 신호 수집 후 상태 표시: "[N]개의 후보에 대한 신호 수집 완료. [M]개의 Wayback 히트, [K]개의 RDAP 조회 성공."
단계 5: 점수 매기기 및 분류
전체 점수 매기기 프레임워크는 references/scoring-model.md를 참조하세요.
각 후보에 대해 6가지 차원에 걸쳐 점수를 계산합니다:
-
주제 관련성 (0–30): 도메인 키워드 매칭 비율, 과거 제목/설명 분석, LLM 관련성 점수(사용 가능한 경우)를 결합합니다. LLM 없음: 키워드 매칭 비율 × 15 + 제목 키워드 중복 × 15를 사용합니다. LLM 있음: LLM relevance_score × 3을 사용합니다.
-
과거 활동 수준 (0–25): Wayback 스냅샷 다양성 및 빈도를 기반으로 합니다. 여러 해에 걸쳐 일관되게 캡처된 더 많은 스냅샷은 지속적인 활동과 추론된 합법성을 나타냅니다.
-
과거 콘텐츠 품질 (0–15): 과거 페이지 제목 및 메타 설명 분석에서 파생되며, 자연스러운 표현 대 키워드 채우기를 확인합니다. LLM 없음: 정확한 일치 밀도에 따라 조정된 기본 점수 8/15.
-
이력 청결도 (0–15): Wayback 스냅샷 수, 활성 연도, 상태 코드 일관성 및 파킹 페이지 표시기 부재를 기반으로 합니다.
-
리다이렉트 적합성 (0–10): 과거 콘텐츠와 대상 틈새시장 간의 주제 연속성을 기반으로 합니다. 사용 가능한 경우 LLM redirect_plausibility 점수를 사용하고, 그렇지 않으면 키워드 중복 비율을 사용합니다.
-
신호 완전성 (0–5): 사용 가능한 데이터를 반환한 데이터 소스(Wayback, RDAP, 도메인 분석, LLM(구성된 경우))의 수를 계산합니다.
계산:
opportunity_score= 모든 차원 점수의 합계 (0–100)confidence= 강력한 데이터가 있는 차원의 수를 기반으로 함 (scoring-model.md 참조)recommended_action= 점수 + 신뢰도 + 위험 플래그를 기반으로 함 (단계 6 참조)
단계 6: 위험 플래그 지정 및 필터링
전체 플래그 정의는 references/risk-flags.md를 참조하세요.
각 후보에 위험 플래그를 적용합니다:
| 확인 사항 | 적용된 플래그 |
|---|---|
| 과거 주제 중복 < 30%, 대상 틈새시장과 일치하지 않음 | topic_mismatch |
| Wayback 스냅샷 없음 또는 2개 미만 | no_history |
| 등록 상태 'unregistered_or_no_rdap_object' | likely_dropped |
| 404/5xx 오류가 스냅샷의 50% 이상 | error_history |
| 키워드 매칭 비율 = 0, 일치하는 단어 없음 | keyword_mismatch |
점수에 따른 권장 조치:
| opportunity_score | confidence | recommended_action |
|---|---|---|
| ≥ 70 | 높음 또는 중간 | strong_opportunity |
| 50–69 | 높음 | moderate_opportunity |
| 50–69 | 중간 또는 낮음 | cautious_opportunity |
| < 50 | 모든 신뢰도 | weak_opportunity |
| 모든 점수 | 낮음, 위험 플래그 > 1 | high_risk |
단계 7: 출력 생성
각 후보에 대해 JSON 객체를 생성하고 모든 후보를 순위가 매겨진 배열로 결합합니다.
[
{
"domain": "example.com",
"opportunity_score": 78,
"confidence": "high",
"recommended_action": "strong_opportunity",
"why_selected": "9년간의 일관된 Wayback 스냅샷, 높은 주제 관련성 (점수 27/30), 깨끗한 이력",
"why_risky": "도메인이 2023년에 만료됨, 파킹 상태 확인 필요",
"risk_flags": [],
"dimension_scores": {
"topical_relevance": 27,
"historical_activity": 22,
"content_quality": 12,
"history_cleanliness": 11,
"redirect_suitability": 4,
"signal_completeness": 2
}
},
...
]
출력은 opportunity_score 내림차순으로 정렬됩니다.
사용자에게 표시:
- 순위 목록의 상위 5개 후보에 대해: 도메인, 점수, 신뢰도, 권장 조치, why_selected, why_risky, 위험 플래그 요약을 표시합니다.
- 나머지 후보에 대해: 간결한 테이블 형식으로 표시합니다.
- 점수 매기기 모드와 평가된 총 후보 수를 알립니다.
- 신호가 불완전한 경우(예: 일부 후보에서 Wayback 오류) 후보별 주의 사항을 포함합니다.
오류 처리 참고 사항
| 단계 | 오류 시나리오 | 핸들러 동작 |
|---|---|---|
| 1 | curl/Python 없음 | 중단, 사용자에게 알림 |
| 3 | 유효한 후보 없음 | 중단, 사용자에게 알림 |
| 4a | Wayback API 시간 초과/실패 | 단일 후보 건너뛰기, 오류 기록, 계속 진행 |
| 4b | Wayback 스냅샷 없음 | 건너뛰기, 신호 불완전성 기록 |
| 4c | RDAP 시간 초과/실패 | 건너뛰기, 신호 불완전성 기록 |
| 4e | LLM 호출 실패 | 로그, 규칙 기반 점수 매기기로 대체 |
| 모든 단계 | JSON 구문 분석 오류 | 오류 기록, 가능한 경우 우아하게 복구 |
오류가 발생해도 가차 없이 진행하십시오. 단일 후보에 대한 부분 데이터로도 작업을 완료하십시오.