Un crawler est un programme automatisé qui explore les pages web pour en extraire des données. C’est exactement ce que font les bots de Google pour indexer les sites dans les résultats de recherche. Comprendre comment fonctionne un robot d’exploration est essentiel en SEO, car le comportement du crawler détermine la visibilité d’un site.
Dans ce tutoriel, nous allons voir comment créer un petit crawler en JavaScript. L’objectif ? Détecter des noms de domaines expirés, une pratique courante chez les référenceurs pour récupérer des backlinks existants et renforcer leur stratégie de référencement naturel.
Pourquoi utiliser JavaScript pour ce type de crawler ?
- Parce qu’il est facile à mettre en place, même pour un développeur débutant.
- Parce que cela permet de mieux comprendre comment un Googlebot interagit avec du contenu généré dynamiquement.
- Parce que l’automatisation en JS peut être adaptée à de nombreux usages (scraping, audit SEO, veille concurrentielle).
Avant de passer au code, il est important de rappeler que les crawlers ne sont pas uniquement réservés aux moteurs de recherche. Vous pouvez en créer un vous-même pour analyser vos propres sites, suivre vos concurrents, ou comme ici, dénicher des domaines expirés à fort potentiel SEO.
Dans la suite, nous allons coder ensemble un crawler en JavaScript, étape par étape, pour que vous puissiez l’adapter à vos besoins.
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 & fans de Google:
- 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.
Avec ce crawler Google, 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 le guide complet sur les NDD’s expirés (bas de page)
Questions Fréquemment Posées
Oui, mais avec des limites. Depuis quelques années, Googlebot est capable d’exécuter du JavaScript. Cependant, le rendu peut prendre plus de temps et certaines pages dynamiques ne sont pas toujours explorées correctement. C’est pourquoi il est recommandé de tester régulièrement ses pages avec l’outil d’inspection d’URL dans la Search Console.
Développer un petit crawler en JavaScript permet de mieux comprendre comment fonctionne l’exploration des pages web. C’est utile pour :
analyser vos propres sites,
détecter des erreurs d’indexation,
rechercher des noms de domaine expirés à potentiel SEO,
automatiser la collecte de données pour vos projets web.
Un crawler parcourt le web de manière automatique pour découvrir et suivre des liens. Un scraper, lui, va extraire des données précises d’une page. En pratique, beaucoup d’outils combinent les deux : le crawler trouve les pages et le scraper en récupère les informations.
Pour faciliter le travail du crawler Google, il est conseillé de :
avoir un fichier sitemap.xml à jour,
vérifier son robots.txt,
éviter le contenu bloqué en JavaScript ou en AJAX,
améliorer la vitesse de chargement,
utiliser des balises structurées (title, h1, meta, etc.).

