Localizador de Oportunidades de Domínios Expirados
Avalie candidatos a domínios expirados para um nicho específico. Pontue-os por adequação temática, nível de atividade histórica, limpeza do histórico e adequação para redirecionamento. Gere uma lista conservadora e explicável para revisão humana.
Regra crítica: Toda recomendação deve incluir TANTO uma justificativa positiva
(why_selected) QUANTO uma justificativa de cautela (why_risky). Nunca
produza uma pontuação sem explicação.
Regra conservadora por padrão: Quando os sinais forem incompletos ou contraditórios, reduza o nível de confiança. Não apresente candidatos ambíguos como oportunidades fortes. Dados ausentes reduzem a confiança, nunca a inflam.
Regra anti-abuso: Nunca incentive redirecionamentos não relacionados, construção
de PBNs ou reaproveitamento de domínios onde o tópico histórico não corresponde ao
nicho alvo. Leia references/guardrails.md para a política anti-abuso completa.
Etapa 1: Verificação de Configuração
Verifique o ambiente antes de fazer qualquer outra coisa.
Confirme que curl e python3 (ou python) estão disponíveis:
curl --version > /dev/null 2>&1 && echo "curl: disponível" || echo "curl: AUSENTE"
python3 --version 2>/dev/null || python --version 2>/dev/null || echo "python: AUSENTE"
Verifique se há uma chave de API LLM opcional para pontuação aprimorada de relevância de nicho:
echo "LLM_API_KEY: ${LLM_API_KEY:+definida}"
Se curl ou python estiver ausente:
Pare. Informe ao usuário: "Esta skill requer curl e Python 3.10+. Instale-os e tente novamente."
Se LLM_API_KEY não estiver definida:
Continue. A skill usará apenas pontuação baseada em regras (correspondência de strings de domínio,
análise de títulos do Wayback, sobreposição de palavras-chave). Avise ao usuário: "Executando
no modo apenas baseado em regras. Defina LLM_API_KEY para pontuação aprimorada de relevância de nicho."
Se LLM_API_KEY estiver definida:
A skill usará pontuação aprimorada por LLM para análise de relevância temática.
Isso fornece uma avaliação contextual mais profunda da adequação ao nicho.
QA: Informe o modo de pontuação (aprimorado por LLM ou apenas baseado em regras) e confirme se as ferramentas estão disponíveis.
Etapa 2: Coleta de Entradas
Colete as entradas obrigatórias e opcionais do usuário.
Obrigatório:
target_niche(string): O nicho principal a ser avaliado. Exemplos: "ferramentas de desenvolvimento", "SaaS de IA", "cibersegurança", "fintech".
Opcional (pergunte apenas se não for fornecido):
seed_keywords(array): Palavras-chave para refinar a correspondência temática. Se não for fornecido, extraia 3–5 palavras-chave do nome do nicho automaticamente.candidate_domains(array): Domínios específicos a serem avaliados. Se não for fornecido, peça ao usuário.discovery_source(string): De onde os candidatos vieram —manual,expireddomains-net,external-feed.min_snapshots(inteiro): Limiar mínimo de snapshots históricos. Padrão: 10.max_risk_level(string):low,mediumouhigh. Controla o quão agressivamente candidatos arriscados são filtrados. Padrão:medium.intended_use(string):rebuild,redirectoueither. Padrão:either.
Se nenhum candidate_domains for fornecido:
Pergunte: "Por favor, forneça uma lista de candidatos a domínios expirados para avaliar. Você pode:
- Colar nomes de domínio (um por linha ou separados por vírgula)
- Fornecer o caminho de um arquivo de texto com um domínio por linha
- Diga 'exemplo' para executar com um conjunto de demonstração embutido para o nicho 'ferramentas de desenvolvimento'"
Se o usuário disser 'exemplo': Use este conjunto de demonstração:
devtoolsweekly.com
codeshipnews.io
stackforgeapp.com
quickseorank.net
bestcheaphosting247.com
cloudbuildpro.dev
reactwidgetlib.com
megadealsshop.xyz
Após coletar todas as entradas, confirme: "Nicho alvo: [nicho]. Avaliando [N] domínios candidatos. Modo de pontuação: [modo]. Uso pretendido: [uso]."
Etapa 3: Normalização de Candidatos
Limpe e valide a lista de candidatos antes da pontuação.
python3 -c "
import sys, re
domains = '''LISTA_DE_CANDIDATOS_AQUI'''.strip().split('\n')
vistos = set()
validos = []
invalidos = []
for d in domins:
d = d.strip().lower()
# Remove protocolos e caminhos
d = re.sub(r'^https?://', '', d)
d = d.split('/')[0]
d = d.strip('.')
if not d:
continue
# Validação básica de TLD
if '.' not in d or len(d) < 4:
invalidos.append(d)
continue
# Deduplicação
if d in vistos:
continue
vistos.add(d)
validos.append(d)
print(f'Candidatos válidos: {len(validos)}')
print(f'Removidos (inválidos/duplicatas): {len(invalidos)}')
for v in validos:
print(f' ✓ {v}')
for i in invalidos:
print(f' ✗ {i} (formato inválido)')
"
Substitua LISTA_DE_CANDIDATOS_AQUI pela lista real de domínios da Etapa 2.
Informe: "[N] candidatos válidos após normalização. [M] removidos (inválidos/duplicatas)."
Se 0 candidatos válidos restarem, pare e informe ao usuário: "Nenhum candidato a domínio válido encontrado. Forneça nomes de domínio no formato 'exemplo.com'."
Etapa 4: Coleta de Sinais
Para cada candidato válido, colete sinais de fontes públicas gratuitas. Execute essas verificações sequencialmente por domínio.
4a: API Wayback CDX — Snapshots do Histórico
Consulte o Wayback Machine por todos os snapshots históricos. Usamos limit=100000
e os parâmetros from/to são omitidos intencionalmente para que o CDX
retorne snapshots de todo o ciclo de vida do domínio. Os resultados são ordenados
crescentemente por carimbo de data/hora (do mais antigo primeiro) para que first_capture
e last_capture sejam precisos:
curl -s "https://web.archive.org/cdx/search/cdx?url=DOMINIO_AQUI&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': 'DOMINIO_AQUI', 'snapshots': 0, 'first_capture': None, 'last_capture': None, 'status_codes': {}, 'years_active': 0}))
else:
linhas = data[1:] # pula a linha de cabeçalho
timestamps = [r[0] for r in linhas] # já em ordem crescente (mais antigo primeiro)
statuses = [r[1] for r in linhas]
status_counts = {}
for s in statuses:
status_counts[s] = status_counts.get(s, 0) + 1
primeiro_ano = int(timestamps[0][:4])
ultimo_ano = int(timestamps[-1][:4])
print(json.dumps({
'domain': 'DOMINIO_AQUI',
'snapshots': len(linhas),
'first_capture': timestamps[0],
'last_capture': timestamps[-1],
'status_codes': status_counts,
'years_active': ultimo_ano - primeiro_ano + 1
}))
except:
print(json.dumps({'domain': 'DOMINIO_AQUI', 'snapshots': 0, 'error': 'wayback_api_falhou'}))
"
Limitação de taxa: Aguarde 2 segundos entre chamadas da API Wayback para ser educado com o serviço.
4b: Amostragem de Conteúdo do Wayback — Títulos de Páginas Históricas
Para candidatos com > 0 snapshots, busque o snapshot mais recente para extrair o título da página (usado para pontuação de relevância temática):
curl -s -L "https://web.archive.org/web/TIMESTAMP_MAIS_RECENTE/http://DOMINIO_AQUI" \
| python3 -c "
import sys, re
html = sys.stdin.read()[:50000]
corresponde_titulo = re.search(r'<title[^>]*>(.*?)</title>', html, re.IGNORECASE | re.DOTALL)
titulo = corresponde_titulo.group(1).strip() if corresponde_titulo else 'nenhum título encontrado'
# Extrai meta description também
corresponde_meta = re.search(r'<meta[^>]*name=[\"']description[\"'][^>]*content=[\"'](.*?)[\"']', html, re.IGNORECASE)
desc = corresponde_meta.group(1).strip() if corresponde_meta else 'nenhuma descrição encontrada'
print(f'Título: {titulo}')
print(f'Descrição: {desc}')
"
Substitua TIMESTAMP_MAIS_RECENTE pelo carimbo de data/hora mais recente da Etapa 4a.
4c: Consulta RDAP — Status de Registro
Use o padrão RDAP baseado em HTTP independente de plataforma (substitui WHOIS dependente de SO). Um HTTP 404 do RDAP significa que o domínio não está registrado (ou seja, está genuinamente disponível ou não rastreado) — isso é diferente de uma falha de rede. Trate ambos os casos explicitamente:
python3 -c "
import urllib.request, urllib.error, json
dominio = 'DOMINIO_AQUI'
try:
req = urllib.request.Request(
f'https://rdap.org/domain/{dominio}',
headers={'User-Agent': 'Mozilla/5.0'}
)
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
registrador = 'desconhecido'
criado = 'desconhecido'
for entidade in data.get('entities', []):
if 'registrar' in entidade.get('roles', []):
try:
registrador = entidade.get('vcardArray', [[]])[1][0][3]
except Exception:
pass
for evento in data.get('events', []):
if evento.get('eventAction') == 'registration':
criado = evento.get('eventDate', 'desconhecido')
print(json.dumps({
'domain': dominio,
'status': 'registered',
'registrar': registrador,
'created': criado
}))
except urllib.error.HTTPError as e:
if e.code == 404:
# Domínio não tem objeto RDAP — provavelmente não registrado ou fora da cobertura RDAP
print(json.dumps({'domain': dominio, 'status': 'nao_registrado_ou_sem_objeto_rdap'}))
else:
print(json.dumps({'domain': dominio, 'error': f'rdap_erro_http_{e.code}'}))
except Exception:
print(json.dumps({'domain': dominio, 'error': 'rdap_consulta_falhou'}))
"
4d: Análise de String do Domínio — Correspondência de Palavras-chave
Pontue a sobreposição de palavras-chave entre o nome do domínio e o nicho alvo / palavras-chave semente:
python3 -c "
import re, json
dominio = 'DOMINIO_AQUI'
niche = 'NICHE_AQUI'
sementes = SEMENTES_JSON_AQUI # ex.: ['devops', 'ci/cd', 'editor de código']
# Extrai palavras do domínio
base_dominio = dominio.rsplit('.', 1)[0] # remove TLD
palavras_dominio = re.split(r'[-_.]', base_dominio.lower())
# Verifica palavras do nicho
palavras_nicho = niche.lower().split()
todas_palavras_chave = set(palavras_nicho + [s.lower() for s in sementes])
correspondencias = [p for p in palavras_dominio if any(pc in p or p in pc for pc in todas_palavras_chave)]
proporcao_correspondencia = len(correspondencias) / max(len(palavras_dominio), 1)
print(json.dumps({
'domain': dominio,
'domain_words': palavras_dominio,
'keyword_matches': correspondencias,
'match_ratio': round(proporcao_correspondencia, 2)
}))
"
4e: Avaliação de Relevância de Nicho por LLM Gemini (se LLM_API_KEY estiver definida)
Se a chave de API LLM estiver configurada, agrupe todos os candidatos com seus sinais coletados e peça uma avaliação contextual de relevância de nicho.
Nota: O formato de requisição/resposta abaixo usa a API Gemini (formato generateContent).
Não é compatível com endpoints estilo OpenAI sem modificação.
Se você usar um provedor diferente, deve adaptar o corpo JSON e a análise da resposta.
cat > /tmp/requisicao-relevancia-dominio.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "Você é um analista de pesquisa SEO. Para cada candidato a domínio expirado fornecido, avalie sua relevância temática para o nicho alvo especificado. Considere o nome do domínio, o título da página histórica e a meta descrição. Para cada domínio, produza um objeto JSON com: domain (string), relevance_score (inteiro 1-10), relevance_rationale (uma frase explicando a pontuação), redirect_plausibility (inteiro 1-10), redirect_rationale (uma frase). Produza apenas um array JSON. Nenhum comentário antes ou depois."
}]
},
"contents": [{
"parts": [{
"text": "SINAIS_DO_DOMINIO_E_CONTEXTO_DO_NICHO_AQUI"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
Substitua SINAIS_DO_DOMINIO_E_CONTEXTO_DO_NICHO_AQUI por:
- O nicho alvo e palavras-chave semente
- Para cada candidato: nome do domínio, título histórico, descrição, dados de correspondência de palavras-chave
Envie a requisição para a API Gemini:
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/requisicao-relevancia-dominio.json \
| python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
texto = d['candidates'][0]['content']['parts'][0]['text']
print(texto)
except (KeyError, IndexError, json.JSONDecodeError) as e:
print(json.dumps({'error': 'falha_na_analise_da_resposta_llm', 'detail': str(e)}))
"
Se a chamada LLM ou a análise da resposta falhar, registre o erro e continue com pontuação apenas baseada em regras. Não pare o fluxo de trabalho.
Após toda a coleta de sinais, informe: "Coleta de sinais completa para [N] candidatos. [M] hits do Wayback, [K] consultas RDAP bem-sucedidas."
Etapa 5: Pontuação e Classificação
Leia references/scoring-model.md para o framework completo de pontuação.
Para cada candidato, calcule pontuações nas 6 dimensões:
-
Relevância Temática (0–30): Combine a proporção de correspondência de palavras-chave do domínio, análise de título/descrição histórica e pontuação de relevância do LLM (se disponível). Sem LLM: use proporção de correspondência de palavras-chave × 15 + sobreposição de palavras-chave do título × 15. Com LLM: use relevance_score do LLM × 3.
-
Nível de Atividade Histórica (0–25): Baseado na diversidade e frequência de snapshots do Wayback. Mais snapshots capturados consistentemente em vários anos indicam maior atividade sustentada e legitimidade inferida.
-
Qualidade do Conteúdo Histórico (0–15): Derivada da análise do título da página histórica e da meta descrição, verificando se há redação natural versus empilhamento de palavras-chave. Sem LLM: pontuação base de 8/15 ajustada pela densidade de correspondência exata.
-
Limpeza do Histórico (0–15): Baseado na contagem de snapshots do Wayback, anos ativos, consistência do código de status e ausência de indicadores de página de estacionamento.
-
Adequação para Redirecionamento (0–10): Baseado na continuidade temática entre o conteúdo histórico e o nicho alvo. Use a pontuação redirect_plausibility do LLM se disponível; caso contrário, use a proporção de sobreposição de palavras-chave.
-
Completude dos Sinais (0–5): Conte quantas fontes de dados retornaram dados utilizáveis (Wayback, RDAP, análise de domínio, LLM se configurado).
Calcule:
opportunity_score= soma de todas as pontuações das dimensões (0–100)confidence= baseado em quantas dimensões têm dados fortes (consulte scoring-model.md)recommended_action= baseado na pontuação + confiança + sinalizadores de risco (consulte Etapa 6)
Etapa 6: Sinalização e Filtragem de Risco
Leia references/risk-flags.md para as definições completas de sinalizadores.
Aplique sinalizadores de risco a cada candidato:
| Verificação | Sinalizador Aplicado |
|---|---|
| Sobreposição de tópico histórico < 30% com o nicho alvo | risk_flag: topic_mismatch |
| Nenhum snapshot do Wayback encontrado | risk_flag: no_history |
| Domínio atualmente registrado (não expirado) | risk_flag: currently_registered |
| Mais de 30% dos snapshots retornam erro 404 ou 500 | risk_flag: high_error_rate |
| Domínio com apenas alguns snapshots (menos que min_snapshots) | risk_flag: sparse_history |
| Domínio parece ser página de estacionamento (título contém "parked", "domain", "coming soon", etc.) | risk_flag: parked_page |
| Título/descrição empilhada com palavras-chave | risk_flag: keyword_stuffed |
| Domínio registrado recentemente (menos de 6 meses) | risk_flag: recently_registered |
| Erro na consulta RDAP | risk_flag: rdap_error |
| Modo LLM sem resposta do LLM (falha na análise) | risk_flag: llm_unavailable |
Regra de filtragem:
- Se
max_risk_level= low: Remova candidatos com ANY risk_flag (excetollm_unavailable) - Se
max_risk_level= medium: Remova candidatos comtopic_mismatch,currently_registered,no_history, ouparked_page - Se
max_risk_level= high: Não remova com base em sinalizadores de risco, apenas marque-os
Após a sinalização, informe: "Filtragem de risco concluída. [N] candidatos permanecem de [M] totais. [K] removidos pelos sinalizadores de risco."
Etapa 7: Geração da Lista Classificada
Classifique os candidatos restantes por opportunity_score (decrescente).
Para cada candidato no top 20 (ou todos se menos de 20), produza:
### [domain_name]
- **Pontuação de Oportunidade:** [score]/100
- **Confiança:** [high/medium/low]
- **Ação Recomendada:** [rebuild/redirect/review]
**Por que foi selecionado:**
[why_selected] — texto explicativo claro cobrindo correspondência temática, força do histórico,
dados de tráfego histórico, e por que este domínio se encaixa no nicho.
**Por que é arriscado:**
[why_risky] — texto explicativo claro cobrindo quaisquer sinalizadores de risco, sinais fracos,
incertezas ou lacunas de dados que justificam cautela.
**Sinalizadores de Risco:**
- [risk_flag_1]
- [risk_flag_2]
- (Nenhum se não houver)
**Detalhes da Pontuação:**
| Dimensão | Pontuação | Fonte de Dados |
|---|---|---|
| Relevância Temática | [0–30] | Análise de strings + Wayback + [LLM se disponível] |
| Atividade Histórica | [0–25] | N=XX snapshots, XX anos |
| Qualidade do Conteúdo | [0–15] | Análise de título/descrição |
| Limpeza do Histórico | [0–15] | XX% de códigos de sucesso |
| Adequação para Redirecionamento | [0–10] | Continuidade temática |
| Completude dos Sinais | [0–5] | X de Y fontes |
---
Nota: Se um candidato tiver confidence baixa mas opportunity_score alta (por exemplo, > 70), ainda
inclua-o na lista classifica, mas marque a recommended_action como review com uma nota explícita:
"Alta pontuação com baixa confiança. Requer verificação manual."
Após gerar todas as entradas: "Lista classifica gerada com [N] candidatos. Use a ação recomendada e os sinalizadores de risco para priorizar a revisão manual."
Referências
Consulte references/guardrails.md para regras anti-abuso e diretrizes éticas.
Consulte references/scoring-model.md para o modelo detalhado de pontuação.
Consulte references/risk-flags.md para definições completas de sinalizadores de risco.