Tutoriel : Comment créer un crawler en JS pour rechercher des noms de domaines expirés

Un crawler est un programme qui parcourt automatiquement le web pour collecter des informations. Dans ce tutoriel, nous allons créer un crawler en JavaScript qui cherche des noms de domaines expirés. Ceci est particulièrement utile pour les référenceurs web qui cherchent à profiter des avantages SEO des domaines expirés.

Pré-requis:

  • Avoir une connaissance de base en JavaScript.
  • Node.js installé sur votre machine.

Étapes du Tutoriel:

  1. Mise en place des dépendances
    • Nous allons utiliser plusieurs modules : https, http, fs, htmlparser2, et url.
    • Installez les dépendances avec npm :bashCopy codenpm install htmlparser2
  2. Configuration initiale
    • Définissez la configuration de base, notamment le nombre de requêtes simultanées, le nombre maximal de tentatives, le délai entre les tentatives, etc.
    • Si vous souhaitez restreindre le crawler à un seul site, configurez RESTRICT_TO_SITE à true et fournissez l’URL de départ.
  3. Liste noire des domaines
    • Pour éviter de crawler certains sites tels que Google, Facebook, etc., nous avons créé une liste noire BLACKLISTED_DOMAINS.
    • Vous pouvez ajouter ou supprimer des domaines de cette liste selon vos besoins.
  4. Fonctions utilitaires
    • isValidUrl(url): Vérifie si une URL est valide.
    • isBlacklisted(url): Vérifie si une URL appartient à un domaine de la liste noire.
    • shouldCrawlUrl(url): Vérifie si une URL doit être crawlée (en fonction de la restriction du site).
  5. Fetching des pages
    • La fonction fetchPage(url) est responsable de la récupération du contenu HTML d’une URL donnée.
    • Si une erreur de type « ERR_NAME_NOT_RESOLVED » se produit (ce qui signifie que le domaine est probablement expiré), l’URL est enregistrée.
  6. Extraction des liens
    • extractLinks(html, baseUrl): extrait tous les liens d’une page web. Il convertit également les URL relatives en URL absolues.
  7. Crawling
    • La fonction crawl() est le cœur du crawler. Elle parcourt les URL, vérifie si elles ont été visitées ou non, et ajoute les nouvelles URL à la file d’attente.
    • Le crawler s’exécute de manière récursive jusqu’à ce qu’il n’y ait plus d’URL à visiter ou jusqu’à atteindre la profondeur maximale définie.
  8. Exécution
    • Lancez le crawler avec node votreFichier.js.
    • Une fois le crawling terminé, vous trouverez une liste des noms de domaines expirés dans le fichier expired_domains.txt.

Le code

const https = require('https');
const http = require('http');
const fs = require('fs');
const htmlparser = require('htmlparser2');
const urlModule = require('url');
const MAX_CONCURRENT_REQUESTS = 5;
const MAX_RETRIES = 1;
const RETRY_DELAY = 5000;  // Delay in milliseconds (5 seconds)
const EXPIRED_DOMAINS_FILE = 'expired_domains.txt';
const MAX_DEPTH = 10;  // Par exemple, pour 2 niveaux de profondeur
const START_URL = false;
if(START_URL) {
const INITIAL_HOSTNAME = new URL(START_URL).hostname;
}
const RESTRICT_TO_SITE = false;  // Mettez à false pour crawler le web entier
//si START_URL est FALSE, on charge le fichier urls.txt et on le parse pour l'ajouter à urlsToVisit
let urlsToVisit = [];
if (START_URL === false) {
    // Vérifier si le fichier urls.txt existe
    if (fs.existsSync('urls.txt')) {
        const urls = fs.readFileSync('urls.txt', 'utf8');
        const urlsArray = urls.split('\n').map(url => url.trim()); // Nettoyer chaque URL
        
        urlsArray.forEach(url => {
            // Ajouter seulement les URLs valides à urlsToVisit
            if (isValidUrl(url)) {
                urlsToVisit.push({ url: url, depth: 0 });
            }
        });
    } else {
        console.error("Le fichier urls.txt n'existe pas.");
    }
} else {
    urlsToVisit = [{ url: START_URL, depth: 0 }];
}
let visitedUrls = new Set();
let checkedDomains = new Set();
// Blacklist of domain patterns
const BLACKLISTED_DOMAINS = [
    /google\./,
    /apple\./,
    /adobe\./,
    /youtube\./,
    /facebook\./,
    /twitter\./,
    /linkedin\./,
    /pinterest\./,
    /bing\./,
    /yahoo\./,
    /instagram\./,
    /amazon\./,
    /tiktok\./,
    /gouv\./,
    // Add more as needed
];
function isBlacklisted(url) {
    const hostname = urlModule.parse(url).hostname || '';
    return BLACKLISTED_DOMAINS.some(pattern => pattern.test(hostname));
}
function logExpiredDomain(domain) {
    fs.appendFileSync(EXPIRED_DOMAINS_FILE, domain + '\n');
}
async function fetchPage(url, retries = MAX_RETRIES) {
    return new Promise((resolve, reject) => {
        const requester = url.startsWith('https:') ? https : http;
        requester.get(url,{
            rejectUnauthorized: false  // Ajoutez cette ligne pour ignorer les erreurs de certificat SSL
            
        },(res) => {
            let data = '';
            res.on('data', (chunk) => {
                data += chunk;
            });
            res.on('end', () => {
                resolve(data);
            });
        }).on('error', async (err) => {
            if (err.message.includes('ERR_NAME_NOT_RESOLVED')) {
                console.log(`Domain expired or inaccessible: ${url}`);
                logExpiredDomain(new URL(url).hostname);
            }else{
                //ignore all others erros
                resolve("");
            }
            reject(err);
        });
    });
}
function isValidUrl(url) {
    try {
        // Tentez de créer un nouvel objet URL
        new URL(url);
        // Assurez-vous que l'URL commence par http:// ou https://
        // et qu'elle n'a pas de protocole imbriqué.
        const pattern = /^https?:\/\/(?!https?:\/\/)/;
        return pattern.test(url);
    } catch (err) {
        // Si une erreur se produit lors de la création de l'objet URL,
        // cela signifie que l'URL est invalide.
        return false;
    }
}
function shouldCrawlUrl(url) {
    const hostname = new URL(url).hostname;
    return !RESTRICT_TO_SITE || hostname === INITIAL_HOSTNAME;
}
function addUrlToQueue(url, currentDepth) {
    if (isValidUrl(url) && shouldCrawlUrl(url) && !visitedUrls.has(url) && currentDepth <= MAX_DEPTH) {
        urlsToVisit.push({ url: url, depth: currentDepth + 1 });
    }
}
function extractLinks(html, baseUrl) {
    const links = [];
    const parser = new htmlparser.Parser({
        onopentag: (name, attribs) => {
            if (name === "a" && attribs.href) {
                // Convert relative URLs to absolute URLs
                let absoluteUrl = urlModule.resolve(baseUrl, attribs.href);
                // Check if the URL is a malformed one with nested protocols
                if (absoluteUrl.includes("https://https/") || absoluteUrl.includes("http://http/")) {
                    return;  // Skip this URL
                }
                // Check if the URL is valid and not blacklisted
                if (isValidUrl(absoluteUrl) && !isBlacklisted(absoluteUrl)) {
                    links.push(absoluteUrl);
                }
            }
        }
    }, { decodeEntities: true });
    parser.write(html);
    parser.end();
    return links;
}
async function crawl() {
    while (urlsToVisit.length > 0) {
        const currentUrlObj = urlsToVisit.shift();
        const currentUrl = currentUrlObj.url;
        const currentDepth = currentUrlObj.depth;
        //console.log(`Visiting ${currentUrl}`);
        const domain = new URL(currentUrl).hostname;
        if (!visitedUrls.has(currentUrl)) {
            if (!checkedDomains.has(domain)) {
                checkedDomains.add(domain);
                visitedUrls.add(currentUrl);
                const html = await fetchPage(currentUrl);
                const newUrls = extractLinks(html, currentUrl);
                for (const newUrl of newUrls) {
                    if (!visitedUrls.has(newUrl)) {
                        addUrlToQueue(newUrl, currentDepth);
                    }
                }
            }
        }
        const promises = [];
        for (let i = 0; i < Math.min(MAX_CONCURRENT_REQUESTS - 1, urlsToVisit.length); i++) {
            const nextUrlObj = urlsToVisit.shift();
            const nextUrl = nextUrlObj.url;
            console.log(`Visiting ${nextUrl}`);
            const nextDomain = new URL(nextUrl).hostname;
            if (!visitedUrls.has(nextUrl)) {
                if (!checkedDomains.has(nextDomain)) {
                    checkedDomains.add(nextDomain);
                    visitedUrls.add(nextUrl);
                    promises.push(fetchPage(nextUrl).then(html => extractLinks(html, nextUrl)));
                }
            }
        }
        const newUrlsArrays = await Promise.all(promises);
        for (const newUrls of newUrlsArrays) {
            for (const newUrl of newUrls) {
                if (!visitedUrls.has(newUrl)) {
                    addUrlToQueue(newUrl, currentDepth + 1);
                }
            }
        }
    }
}
crawl().then(() => {
    console.log('Crawling completed.');
}).catch((err) => {
    console.error('An error occurred:', err);
});

Visible sur Gist

Conseils pour les référenceurs web:

  • Avant d’acheter un domaine expiré, assurez-vous de vérifier son historique, son classement, son profil de backlink, etc.
  • Utilisez un outil comme Wayback Machine pour voir les anciennes versions du site. Cela vous donnera une idée du contenu précédent du site.
  • Faites attention aux sanctions de Google. Si un domaine a été pénalisé dans le passé, il peut ne pas être bénéfique pour le SEO.

Conclusion: Avec ce crawler, vous avez un outil puissant pour trouver des domaines expirés qui peuvent être bénéfiques pour vos efforts de référencement. Adaptez le code selon vos besoins et bonne chance dans vos recherches! Vous trouverez une version améliorée dans La Tribu™

Vous souhaitez recevoir davantage de trucs & d'astuces ? Tous les jours j'envois un mail à mes 6000 (et quelques) abonnés. Si ça vous dit, cliquez-ici pour vous inscrire