Outil de Détection d'Opportunités de Domaines Expirés
Évaluez les candidats de domaines expirés pour un créneau spécifique. Attribuez-leur un score en fonction de l'adéquation thématique, du niveau d'activité historique, de la propreté de l'historique et de la pertinence pour la redirection. Générez une liste restreinte conservatrice et explicable pour un examen humain.
Règle critique : Chaque recommandation doit inclure à la fois une justification positive (why_selected) ET une justification de prudence (why_risky). Ne produisez jamais un score nu sans explication.
Règle de conservatisme par défaut : Lorsque les signaux sont incomplets ou contradictoires, abaissez le niveau de confiance. Ne présentez pas de candidats ambigus comme des opportunités solides. Les données manquantes réduisent la confiance, ne l'augmentent jamais.
Règle anti-abus : N'encouragez jamais les redirections non liées, la construction de PBN ou le détournement de domaine lorsque le sujet historique ne correspond pas au créneau cible. Lisez references/guardrails.md pour la politique anti-abus complète.
Étape 1 : Vérification de l'Environnement
Vérifiez l'environnement avant toute autre action.
Vérifiez que curl et python3 (ou python) sont disponibles :
curl --version > /dev/null 2>&1 && echo "curl: disponible" || echo "curl: MANQUANT"
python3 --version 2>/dev/null || python --version 2>/dev/null || echo "python: MANQUANT"
Vérifiez la présence éventuelle d'une clé API LLM pour une notation améliorée de la pertinence thématique :
echo "LLM_API_KEY: ${LLM_API_KEY:+définie}"
Si curl ou python est manquant :
Arrêtez-vous. Dites à l'utilisateur : "Cette compétence nécessite curl et Python 3.10+. Veuillez les installer et réessayer."
Si LLM_API_KEY n'est pas définie :
Continuez. La compétence utilisera uniquement la notation basée sur des règles (correspondance de chaîne de domaine, analyse de titre Wayback, chevauchement de mots-clés). Notez pour l'utilisateur : "Exécution en mode basé sur les règles uniquement. Définissez LLM_API_KEY pour une notation améliorée de la pertinence thématique."
Si LLM_API_KEY est définie :
La compétence utilisera la notation améliorée par LLM pour l'analyse de la pertinence thématique. Cela fournit une évaluation contextuelle plus approfondie de l'adéquation au créneau.
QA : Indiquez le mode de notation (amélioré par LLM ou basé sur les règles uniquement) et confirmez que les outils sont disponibles.
Étape 2 : Collecte des Entrées
Collectez les entrées requises et facultatives de l'utilisateur.
Obligatoires :
target_niche(chaîne) : Le créneau principal à évaluer. Exemples : "outils de développement", "SaaS IA", "cybersécurité", "fintech".
Facultatifs (demander uniquement s'ils ne sont pas fournis) :
seed_keywords(tableau) : Mots-clés pour affiner la correspondance thématique. S'ils ne sont pas fournis, extrayez automatiquement 3 à 5 mots-clés du nom du créneau.candidate_domains(tableau) : Domaines spécifiques à évaluer. S'ils ne sont pas fournis, invitez l'utilisateur.discovery_source(chaîne) : D'où proviennent les candidats —manual,expireddomains-net,external-feed.min_snapshots(entier) : Seuil minimum d'instantanés historiques. Par défaut : 10.max_risk_level(chaîne) :low,mediumouhigh. Contrôle le niveau d'agressivité du filtrage des candidats risqués. Par défaut :medium.intended_use(chaîne) :rebuild,redirectoueither. Par défaut :either.
Si aucun candidate_domains n'est fourni :
Demandez : "Veuillez fournir une liste de candidats de domaines expirés à évaluer. Vous pouvez :
- Coller les noms de domaine (un par ligne ou séparés par des virgules)
- Fournir un chemin de fichier vers un fichier texte avec un domaine par ligne
- Dire 'exemple' pour exécuter avec un ensemble de démonstration intégré pour le créneau 'outils de développement'"
Si l'utilisateur dit 'exemple' : Utilisez cet ensemble de démonstration :
devtoolsweekly.com
codeshipnews.io
stackforgeapp.com
quickseorank.net
bestcheaphosting247.com
cloudbuildpro.dev
reactwidgetlib.com
megadealsshop.xyz
Après avoir collecté toutes les entrées, confirmez : "Créneau cible : [créneau]. Évaluation de [N] domaines candidats. Mode de notation : [mode]. Utilisation prévue : [utilisation]."
Étape 3 : Normalisation des Candidats
Nettoyez et validez la liste des candidats avant la notation.
python3 -c "
import sys, re
domaines = '''LISTE_DES_CANDIDATS_ICI'''.strip().split('\n')
vu = set()
valides = []
invalides = []
for d in domaines :
d = d.strip().lower()
# Supprimer les protocoles et chemins
d = re.sub(r'^https?://', '', d)
d = d.split('/')[0]
d = d.strip('.')
if not d:
continuer
# Validation de base du TLD
if '.' not in d or len(d) < 4:
invalides.append(d)
continuer
# Déduplication
if d in vu:
continuer
vu.add(d)
valides.append(d)
print(f'Candidats valides : {len(valides)}')
print(f'Supprimés (invalides/doublons) : {len(invalides)}')
for v in valides:
print(f' ✓ {v}')
for i in invalides:
print(f' ✗ {i} (format invalide)')
"
Remplacez LISTE_DES_CANDIDATS_ICI par la liste réelle des domaines de l'Étape 2.
Indiquez : "[N] candidats valides après normalisation. [M] supprimés (invalides/doublons)."
S'il ne reste aucun candidat valide, arrêtez-vous et dites à l'utilisateur : "Aucun candidat de domaine valide trouvé. Veuillez fournir des noms de domaine au format 'exemple.com'."
Étape 4 : Collecte des Signaux
Pour chaque candidat valide, collectez les signaux à partir de sources publiques gratuites. Exécutez ces vérifications séquentiellement pour chaque domaine.
4a : API Wayback CDX — Instantanés d'Historique
Interrogez la Wayback Machine pour tous les instantanés historiques. Nous utilisons limit=100000
et les paramètres from/to sont intentionnellement omis pour que CDX
retourne les instantanés de toute la durée de vie du domaine. Les résultats sont triés
par ordre croissant de timestamp (les plus anciens en premier) afin que first_capture
et last_capture soient précis :
curl -s "https://web.archive.org/cdx/search/cdx?url=DOMAINE_ICI&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': 'DOMAINE_ICI', 'snapshots': 0, 'first_capture': None, 'last_capture': None, 'status_codes': {}, 'years_active': 0}))
else:
lignes = data[1:] # sauter la ligne d'en-tête
timestamps = [r[0] for r in lignes] # déjà ascendants (les plus anciens en premier)
statuts = [r[1] for r in lignes]
comptes_statuts = {}
for s in statuts:
comptes_statuts[s] = comptes_statuts.get(s, 0) + 1
premiere_annee = int(timestamps[0][:4])
derniere_annee = int(timestamps[-1][:4])
print(json.dumps({
'domain': 'DOMAINE_ICI',
'snapshots': len(lignes),
'first_capture': timestamps[0],
'last_capture': timestamps[-1],
'status_codes': comptes_statuts,
'years_active': derniere_annee - premiere_annee + 1
}))
except:
print(json.dumps({'domain': 'DOMAINE_ICI', 'snapshots': 0, 'error': 'wayback_api_echoue'}))
"
Limitation de débit : Attendez 2 secondes entre les appels API Wayback pour être respectueux du service.
4b : Échantillonnage du Contenu Wayback — Titres de Page Historiques
Pour les candidats avec > 0 instantanés, récupérez l'instantané le plus récent pour extraire titre de la page (utilisé pour la notation de pertinence thématique) :
curl -s -L "https://web.archive.org/web/DERNIER_TIMESTAMP/http://DOMAINE_ICI" \
| python3 -c "
import sys, re
html = sys.stdin.read()[:50000]
correspondance_titre = re.search(r'<title[^>]*>(.*?)</title>', html, re.IGNORECASE | re.DOTALL)
titre = correspondance_titre.group(1).strip() if correspondance_titre else 'aucun titre trouvé'
# Extraire aussi la meta description
correspondance_meta = re.search(r'<meta[^>]*name=[\"']description[\"'][^>]*content=[\"'](.*?)[\"']', html, re.IGNORECASE)
desc = correspondance_meta.group(1).strip() if correspondance_meta else 'aucune description trouvée'
print(f'Titre : {titre}')
print(f'Description : {desc}')
"
Remplacez DERNIER_TIMESTAMP par le timestamp le plus récent de l'Étape 4a.
4c : Requête RDAP — Statut d'Enregistrement
Utilisez la norme RDAP HTTP multiplateforme (remplace le WHOIS dépendant de l'OS). Un code HTTP 404 de RDAP signifie que le domaine n'est pas enregistré (c'est-à-dire qu'il est réellement disponible ou non suivi) — ceci est distinct d'une panne réseau. Gérez les deux cas explicitement :
python3 -c "
import urllib.request, urllib.error, json
domaine = 'DOMAINE_ICI'
try:
req = urllib.request.Request(
f'https://rdap.org/domain/{domaine}',
headers={'User-Agent': 'Mozilla/5.0'}
)
with urllib.request.urlopen(req, timeout=10) as reponse:
donnees = json.loads(reponse.read().decode())
registraire = 'inconnu'
cree = 'inconnu'
for entite in donnees.get('entities', []):
if 'registrar' in entite.get('roles', []):
try:
registraire = entite.get('vcardArray', [[]])[1][0][3]
except Exception:
pass
for evenement in donnees.get('events', []):
if evenement.get('eventAction') == 'registration':
cree = evenement.get('eventDate', 'inconnu')
print(json.dumps({
'domain': domaine,
'status': 'enregistre',
'registrar': registraire,
'created': cree
}))
except urllib.error.HTTPError as e:
if e.code == 404:
# Le domaine n'a pas d'objet RDAP — probablement non enregistré ou pas couvert par RDAP
print(json.dumps({'domain': domaine, 'status': 'non_enregistre_ou_pas_d_objet_rdap'}))
else:
print(json.dumps({'domain': domaine, 'error': f'erreur_http_rdap_{e.code}'}))
except Exception:
print(json.dumps({'domain': domaine, 'error': 'requete_rdap_echouee'}))
"
4d : Analyse de la Chaîne de Domaine — Correspondance de Mots-Clés
Scorer le chevauchement de mots-clés entre le nom de domaine et le créneau cible / les mots-clés de départ :
python3 -c "
import re, json
domaine = 'DOMAINE_ICI'
niche = 'NICHE_ICI'
graines = GRAINES_JSON_ICI # p.ex., ['devops', 'ci/cd', 'éditeur de code']
# Extraire les mots du domaine
base_domaine = domaine.rsplit('.', 1)[0] # supprimer le TLD
mots_domaine = re.split(r'[-_.]', base_domaine.lower())
# Vérifier les mots du créneau
mots_niche = niche.lower().split()
tous_mots_cles = set(mots_niche + [s.lower() for s in graines])
correspondances = [m for m in mots_domaine if any(mc in m or m in mc for mc in tous_mots_cles)]
ratio_correspondance = len(correspondances) / max(len(mots_domaine), 1)
print(json.dumps({
'domain': domaine,
'domain_words': mots_domaine,
'keyword_matches': correspondances,
'match_ratio': round(ratio_correspondance, 2)
}))
"
4e : Évaluation de la Pertinence Thématique par LLM Gemini (si LLM_API_KEY est définie)
Si la clé API LLM est configurée, regroupez tous les candidats avec leurs signaux collectés et demandez une évaluation contextuelle de la pertinence thématique.
Remarque : Le format de requête/réponse ci-dessous utilise l'API Gemini (format generateContent).
Il n'est pas compatible avec les points de terminaison de style OpenAI sans modification.
Si vous utilisez un fournisseur différent, vous devez adapter le corps JSON et l'analyse de la réponse.
cat > /tmp/requete-pertinence-domaine.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "Vous êtes un analyste de recherche SEO. Pour chaque candidat de domaine expiré fourni, évaluez sa pertinence thématique par rapport au créneau cible spécifié. Tenez compte du nom de domaine, du titre de page historique et de la meta description. Pour chaque domaine, générez un objet JSON avec : domain (chaîne), relevance_score (entier 1-10), relevance_rationale (une phrase expliquant le score), redirect_plausibility (entier 1-10), redirect_rationale (une phrase). Générez uniquement un tableau JSON. Aucun commentaire avant ou après."
}]
},
"contents": [{
"parts": [{
"text": "SIGNAUX_DOMAINES_ET_CONTEXTE_NICHE_ICI"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
Remplacez SIGNAUX_DOMAINES_ET_CONTEXTE_NICHE_ICI par :
- Le créneau cible et les mots-clés de départ
- Pour chaque candidat : nom de domaine, titre historique, description, données de correspondance de mots-clés
Envoyez la requête à l'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/requete-pertinence-domaine.json \
| python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
texte = d['candidates'][0]['content']['parts'][0]['text']
print(texte)
except (KeyError, IndexError, json.JSONDecodeError) as e:
print(json.dumps({'error': 'analyse_reponse_llm_echouee', 'detail': str(e)}))
"
Si l'appel LLM ou l'analyse de la réponse échoue, enregistrez l'erreur et continuez avec une notation basée sur les règles uniquement. N'arrêtez pas le flux de travail.
Après toute la collecte de signaux, indiquez : "Collecte des signaux terminée pour [N] candidats. [M] hits Wayback, [K] requêtes RDAP réussies."
Étape 5 : Notation et Classification
Lisez references/scoring-model.md pour le cadre de notation complet.
Pour chaque candidat, calculez les scores sur les 6 dimensions :
-
Pertinence Thématique (0–30) : Combinez le ratio de correspondance de mots-clés du domaine, l'analyse du titre/description historique et le score de pertinence LLM (si disponible). Sans LLM : utilisez le ratio de correspondance de mots-clés × 15 + le chevauchement de mots-clés du titre × 15. Avec LLM : utilisez le relevance_score LLM × 3.
-
Niveau d'Activité Historique (0–25) : Basé sur la diversité et la fréquence des instantanés Wayback. Un plus grand nombre d'instantanés capturés de manière cohérente sur plusieurs années indique une activité soutenue plus élevée et une légitimité présumée plus forte.
-
Qualité du Contenu Historique (0–15) : Dérivée de l'analyse du titre de page historique et de la meta description, vérifiant une formulation naturelle par rapport au bourrage de mots-clés. Sans LLM : score de base de 8/15 ajusté par la densité de correspondance exacte.
-
Propreté de l'Historique (0–15) : Basé sur le nombre d'instantanés Wayback, les années d'activité, la cohérence des codes de statut et l'absence d'indicateurs de page de parking.
-
Pertinence pour la Redirection (0–10) : Basé sur la continuité thématique entre le contenu historique et le créneau cible. Utilisez le score redirect_plausibility LLM si disponible ; sinon, utilisez le ratio de chevauchement de mots-clés.
-
Exhaustivité du Signal (0–5) : Comptez le nombre de sources de données ayant retourné des données utilisables (Wayback, RDAP, analyse de domaine, LLM si configuré).
Calculez :
opportunity_score= somme de tous les scores de dimension (0–100)confidence= basé sur le nombre de dimensions disposant de données solides (voir scoring-model.md)recommended_action= basé sur le score + confiance + indicateurs de risque (voir Étape 6)
Étape 6 : Marquage et Filtrage des Risques
Lisez references/risk-flags.md pour les définitions complètes des indicateurs.
Appliquez des indicateurs de risque à chaque candidat :
| Vérification | Indicateur Appliqué |
|---|---|
| Chevauchement thématique historique < 30% avec le créneau cible | risque_detournement |
| Moins de 10 instantanés Wayback | historique_limite |
| Intervalle d'activité < 2 ans | duree_vie_courte |
| Statut RDAP : non enregistré OU erreur | statut_enregistrement_incertain |
| Code de statut 4xx/5xx prédominant | contenu_historique_anormal |
| Ratio de mots-clés du domaine > 0,8 (sur-optimisation suspecte) | risque_domaine_exact |
Logique de filtrage finale :
- Si
max_risk_levelestlow: excluez tout candidat avec UN indicateurrisk_. - Si
max_risk_levelestmedium: excluez tout candidat avec DEUX indicateursrisk_ou plus. - Si
max_risk_levelesthigh: n'excluez que les candidats avec QUATRE indicateursrisk_ou plus.
Exception : Si l'indicateur risque_detournement est présent ET que intended_use est redirect,
excluez toujours le candidat quel que soit max_risk_level. Une redirection thématiquement inappropriée
est une violation de la politique anti-abus.
Étape 7 : Génération de la Liste Restreinte
Générez un rapport sous forme de liste restreinte marquée dans la sortie. Formatez-la en tant que tableau Markdown.
En-tête :
# Résumé des Opportunités de Domaines Expirés
Cible : [créneau] | [N] candidats analysés | [M] retenus après filtrage
Mode de notation : [mode] | Source de données : [source]
---
Colonnes du tableau : | Classement | Domaine | Score d'Opportunité | Confiance | Action Recommandée | Justification Positive | Avertissement de Risque | … |------------|---------|--------------------|-----------|-------------------|------------------------|-------------------------|
Format du tableau pour chaque candidat retenu : | 1 | exemple.com | 78 | élevée | reconstruction | Contenu historique correspondant étroitement ; enregistré depuis 2015 | Aucun instantané au cours des 3 dernières années — contenu potentiellement obsolète | |
Ordre de tri : Par opportunity_score décroissant puis par confidence décroissante.
Si aucun candidat n'est retenu : Générez : "Aucun candidat n'a passé les filtres pour [créneau]. Essayez un niveau de risque plus élevé ou fournissez des candidats différents."
Si la sortie de la liste restreinte est vide après le filtrage de l'indicateur risque_detournement :
Ajoutez une note supplémentaire : "Tous les candidats ont été exclus en raison d'une inadéquation thématique. Envisagez de rechercher des domaines avec une couverture historique plus pertinente pour [créneau]."
Références
Consultez ces fichiers pour plus de détails :
references/scoring-model.md— Cadre de notation completreferences/risk-flags.md— Définitions des indicateurs de risquereferences/guardrails.md— Politique anti-abus