Quand j’ai décidé de remettre ce blog en ligne, j’ai retrouvé un petit problème : j’avais perdu mon backup de l’ancien blog… Mes articles avaient disparu.
C’était sans compter sur la Wayback Machine.
Autrement dit, Internet n’oublie jamais… sauf parfois le CSS, les images et précisément la page dont vous avez besoin.
J’aurais pu ouvrir chaque capture à la main, copier le texte, corriger les liens et recommencer quelques dizaines de fois. J’ai préféré la solution raisonnable : écrire un outil en C# qui interroge des archives vieilles de plusieurs années et espérer que toutes les pages respectent encore leur structure HTML d’origine.
J’ai donc créé NecroBlog CLI, un outil en ligne de commande dont le rôle tient en quatre verbes :
Discover → Inspect → Recover → Report
Son objectif est de retrouver les anciens articles de jeromegiacomini.net et de mon profil sur blogs.infinitesquare.com, d’en extraire le vrai contenu, puis de les reconstruire sous forme de fichiers .md avec leurs métadonnées et, lorsque cela reste possible, leurs images.

Et oui, on est en 2026 : le Markdown est le format du moment.
Oui, j’ai appelé le projet NecroBlog. À partir du moment où l’on passe ses soirées à relever des billets morts, autant assumer le thème jusqu’au bout.
La Wayback Machine n’est pas une liste d’articles
La première difficulté n’est pas de télécharger une page. Elle consiste à savoir quelles pages ont existé.
Pour cela, NecroBlog utilise l’API CDX de la Wayback Machine. CDX est le catalogue des captures : pour une URL donnée — qui peut contenir un joker — il retourne notamment l’URL d’origine, la date de la capture, le type de contenu, le code HTTP et une empreinte du document.
La construction de la requête ressemble à ceci dans WaybackClient :
var query = new StringBuilder("https://web.archive.org/cdx/search/cdx?");
query.Append("url=").Append(Uri.EscapeDataString(url));
query.Append("&output=json");
query.Append("&fl=urlkey,timestamp,original,mimetype,statuscode,digest,length");
foreach (var filter in filters ?? [])
{
query.Append("&filter=").Append(Uri.EscapeDataString(filter));
}
query.Append("&collapse=digest");
var body = await GetStringWithRetryAsync(query.ToString(), ct);
var captures = CdxParser.ParseJson(body);
Le collapse=digest permet d’écarter une partie des captures strictement identiques. C’est important : un article archivé vingt fois n’est toujours qu’un article, même s’il a reçu beaucoup plus de sauvegardes que de commentaires.
Il faut ensuite normaliser les URL pour ne pas traiter séparément les variantes en http, https, avec ou sans www, avec un slash final ou quelques paramètres de tracking oubliés là par une ancienne newsletter.
Enfin, toutes les URL archivées ne sont pas des articles. Un WordPress contient aussi des pages d’accueil, des catégories, des tags, des flux RSS, des pages de pagination, des scripts et une quantité surprenante de chemins qui n’ont aucune envie de devenir du Markdown. NecroBlog classe donc chaque URL et ne conserve comme candidates que celles qui correspondent aux structures des anciens blogs.
Pour mon WordPress, un article suivait par exemple ce modèle :
[GeneratedRegex(
@"^/Blog/\d{4}/\d{2}/\d{2}/[^/]+/?$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex ArticlePathRegex();
Ce n’est pas universel, mais c’est volontaire. Pour récupérer proprement un site disparu, connaître sa structure vaut souvent mieux qu’un algorithme persuadé que la page « Mot de passe oublié » est un excellent billet technique.
Choisir une capture qui existe vraiment
CDX peut annoncer plusieurs captures pour une même page. NecroBlog écarte celles qui ne sont pas du HTML, les erreurs HTTP et les enregistrements de type revisit, puis privilégie une capture récente.
Il subsiste une subtilité charmante : une capture peut être présente dans le catalogue mais impossible à télécharger. Le catalogue dit « oui », le serveur de replay dit « non », et vous voilà à négocier avec un fantôme.
L’outil essaie donc plusieurs captures au lieu de tout abandonner à la première erreur :
foreach (var capture in candidates.Take(6))
{
html = await _wayback.FetchCaptureHtmlAsync(
capture.Timestamp,
candidate.OriginalUrl,
cancellationToken);
if (!string.IsNullOrWhiteSpace(html))
{
usedTimestamp = capture.Timestamp;
break;
}
}
Pour récupérer le document, j’utilise une URL de replay avec le suffixe id_ :
public static string BuildRawReplay(string timestamp, string originalUrl) =>
$"https://web.archive.org/web/{timestamp}id_/{originalUrl}";
Ce suffixe demande les octets archivés sans la barre de navigation Wayback ni la réécriture habituelle de la page. C’est beaucoup plus pratique pour analyser le HTML. Mon outil cherche des articles, pas le code de l’interface de la machine à remonter le temps.
Le client est conçu pour bien gérer les réponses 429 et 503. J’y ai également ajouté un délai entre chaque appel et une attente progressive entre les nouvelles tentatives afin d’éviter d’être bloqué par les limitations du site.
Extraire l’article sans emporter tout le cimetière
Une fois le HTML récupéré, il ne suffit pas de supprimer les balises. La page contient encore le menu, le pied de page, les boutons de partage, les commentaires et parfois des scripts ajoutés par la Wayback Machine.
J’ai créé une chaîne d’extracteurs. Elle essaie d’abord l’extracteur propre au site, puis utilise un extracteur générique en dernier recours :
_extractors =
[
new JeromeGiacominiExtractor(),
new InfiniteSquareExtractor(),
new GenericArticleExtractor(),
];
var extractor = _extractors
.FirstOrDefault(e => e.CanHandle(sourceId, html))
?? _extractors[^1];
return extractor.Extract(html, pageUrl);
Pour l’ancien WordPress, l’extracteur recherche principalement un élément article, puis son bloc .entry-content. Il récupère aussi le titre, la date et l’auteur depuis le HTML ou les métadonnées Open Graph et JSON-LD.
Cette séparation a deux avantages. Elle évite de remplir les billets avec la navigation du site et elle permet de signaler un résultat suspect lorsque le titre manque ou que le contenu extrait est anormalement court. Dans ce cas, NecroBlog ne complète pas les trous avec beaucoup d’imagination et un aplomb remarquable : il inscrit l’article dans le rapport pour vérification manuelle.
Passer du HTML au Markdown sans massacrer le code
Le contenu extrait traverse ensuite un convertisseur HTML vers Markdown écrit pour le projet. Il prend en charge les titres, paragraphes, listes, tableaux, citations, liens, images, balises de code en ligne et blocs <pre><code>.
Les anciens articles utilisaient parfois SyntaxHighlighter, qui indiquait le langage dans une classe comme brush: csharp. Le convertisseur détecte cette information et produit une clôture Markdown typée :
```csharp
public void Foo()
{
Console.WriteLine("Je revis !");
}
```
Préserver les blocs de code était prioritaire. Un paragraphe légèrement mal espacé reste lisible ; un exemple C# dont les caractères ont été encodés trois fois devient rapidement de l’art contemporain.
Les liens réécrits par la Wayback Machine sont également ramenés vers leur URL d’origine. Cela évite que chaque lien interne continue de faire un détour par une capture datée alors que l’article vient justement d’être remis en ligne.
Enfin, le fichier est écrit avec un front matter YAML qui conserve la provenance :
sb.AppendLine("---");
sb.AppendLine($"title: {Quote(article.Title ?? article.Slug)}");
sb.AppendLine($"date: {Quote(article.Date ?? "")}");
sb.AppendLine($"author: {Quote(article.Author ?? "")}");
sb.AppendLine($"original_url: {Quote(article.OriginalUrl)}");
sb.AppendLine($"archive_url: {Quote(article.ArchiveUrl ?? "")}");
sb.AppendLine($"wayback_timestamp: {Quote(article.WaybackTimestamp ?? "")}");
sb.AppendLine("---");
Je peux ainsi savoir d’où vient chaque billet, quelle capture a été retenue et quelles autres captures étaient connues. Les dates manquantes restent vides : lorsqu’on restaure des archives, une absence d’information est préférable à une fausse certitude très bien formatée.
Les images : le vrai niveau difficile
Les pages HTML ont souvent été archivées correctement. Les images, elles, ont parfois décidé de suivre un autre chemin spirituel.
Dans les archives de mes anciens blogs, la Wayback Machine avait conservé beaucoup moins d’images que de pages HTML. Une page archivée ne constitue pas une photographie complète du site : son document HTML et chacune de ses ressources sont capturés séparément. Il est donc tout à fait possible de retrouver un article avec ses balises <img>, mais aucune copie du fichier auquel elles font référence. On possède alors le cadre, la légende et l’emplacement de la photo — il ne manque que ce détail assez secondaire qu’est la photo elle-même.
Plusieurs raisons peuvent expliquer ces absences. Certaines images étaient hébergées sur un autre domaine ou un CDN, d’autres étaient chargées tardivement par JavaScript, et le serveur pouvait aussi refuser la requête ou ne plus répondre au moment du passage du robot d’archivage. Les fichiers binaires, plus lourds que le HTML, n’ont pas non plus été capturés à chaque visite. Le fait qu’un article apparaisse dans CDX ne garantit donc absolument pas que toutes ses illustrations y figurent également.
C’est ce qui rend leur récupération plus difficile : pour un article, je dispose généralement de plusieurs captures parmi lesquelles choisir ; pour une image, il n’existe parfois aucune capture, quelle que soit la date essayée.
Pour chacune des images référencées dans le Markdown, NecroBlog cherche une capture dédiée dans CDX et la télécharge avec le suffixe im_, qui demande les octets de l’image. Le fichier est ensuite enregistré localement et son URL est remplacée dans le billet.
Quand la Wayback Machine n’a aucune copie exploitable, l’outil tente successivement :
- l’URL d’origine sur le web encore accessible ;
archive.today;- les index et fichiers WARC de Common Crawl.
La première source qui retourne de vrais octets gagne, et sa provenance est conservée. Si aucune ne répond, l’image reste signalée comme manquante sans faire échouer l’article complet. Perdre une capture d’écran de Visual Studio 2015 est triste, mais pas au point d’enterrer avec elle trois pages de texte et deux exemples C#.
Utiliser NecroBlog CLI
Dans sa version actuelle, NecroBlog CLI connaît uniquement les deux sources pour lesquelles je l’ai créé : jeromegiacomini.net et blogs.infinitesquare.com. Il ne suffit donc pas de lui passer l’adresse de n’importe quel blog en paramètre en espérant que la nécromancie fasse le reste.
Pour l’utiliser avec votre ancien blog, il faut commencer par forker le projet sur GitHub, cloner votre fork, puis ajouter votre propre source dans le code. Le projet cible .NET 10.
git clone https://github.com/votre-compte/NecroBlogCLI.git
cd NecroBlogCLI
dotnet build
Déclarer votre source
Les sources sont définies dans Models/SourceDefinition.cs. Une source indique à NecroBlog les domaines à rechercher dans la Wayback Machine et surtout la forme d’une URL d’article. Il faut ajouter la nouvelle entrée dans le tableau retourné par la méthode BuildAll().
Pour un ancien blog fictif dont les articles utilisaient des URL comme https://monancienblog.fr/blog/2020/06/15/mon-article/, la définition pourrait ressembler à ceci :
new SourceDefinition
{
Id = "monblog",
DisplayName = "monancienblog.fr",
Author = "Votre nom",
SeedUrls =
[
"monancienblog.fr/*",
"www.monancienblog.fr/*",
],
OwnHosts =
[
"monancienblog.fr",
"www.monancienblog.fr",
],
ArticlePathRegex = MonBlogArticlePathRegex(),
},
Il faut ensuite ajouter l’expression régulière correspondant au chemin des articles :
[GeneratedRegex(
@"^/blog/\d{4}/\d{2}/\d{2}/[^/]+/?$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex MonBlogArticlePathRegex();
SeedUrls indique les domaines que l’API CDX doit explorer. OwnHosts permet de reconnaître les liens internes, tandis que ArticlePathRegex évite de prendre les catégories, les tags, les flux RSS et l’inévitable page de contact pour des articles.
Cette expression doit naturellement être adaptée à votre ancien moteur de blog. Si vos articles ressemblaient plutôt à /posts/mon-article ou /archives/42, c’est ce format qu’il faut décrire. Une bonne expression régulière à cette étape épargne beaucoup de tri manuel ensuite.
Adapter l’extraction du contenu si nécessaire
NecroBlog essaie ensuite d’extraire le contenu avec son extracteur générique. Avant de lancer une récupération complète, la commande inspect permet de vérifier ce qu’il reconnaît sur une URL précise :
dotnet run -- inspect "https://monancienblog.fr/blog/2020/06/15/mon-article/"
Elle affiche notamment le titre, la date, l’auteur, le sélecteur HTML retenu, la longueur du contenu et le nombre de blocs de code ou d’images détectés. Avec l’option --save-dir, elle peut aussi enregistrer le HTML et le Markdown produits pour les examiner tranquillement :
dotnet run -- inspect "https://monancienblog.fr/blog/2020/06/15/mon-article/" --save-dir ./inspect-out
Si l’extracteur générique récupère le menu, oublie la moitié de l’article ou considère le pied de page comme une conclusion particulièrement inspirée, il faut créer un extracteur propre à l’ancien site. Celui-ci implémente IArticleExtractor et sélectionne précisément le conteneur du titre, de la date, de l’auteur et du contenu. Il doit ensuite être ajouté dans ExtractorPipeline, avant GenericArticleExtractor :
_extractors =
[
new JeromeGiacominiExtractor(),
new InfiniteSquareExtractor(),
new MonBlogExtractor(),
new GenericArticleExtractor(),
];
L’extracteur générique reste ainsi le dernier recours, tandis que votre extracteur est utilisé dès que le sourceId vaut monblog.
Lancer la récupération
Une fois la source ajoutée et une URL validée avec inspect, je conseille de commencer par quelques articles grâce à --limit :
dotnet run -- discover --source monblog
dotnet run -- recover --source monblog --download-images true --limit 5
dotnet run -- report
discover construit le catalogue local à partir de la nouvelle source. recover extrait et convertit les cinq premiers articles, tandis que report récapitule les contenus suspects, les échecs et les images absentes.
Si le résultat est satisfaisant, il suffit de relancer la récupération sans limite :
dotnet run -- recover --source monblog --download-images true --resume
La commande de récupération est reprenable avec --resume. Les téléchargements sont mis en cache et l’échec d’un article n’interrompt pas les suivants. Cette propriété devient vite essentielle : une restauration complète dure assez longtemps pour que le réseau, un serveur distant ou votre ordinateur décide de participer au test de résilience.
Le résultat de la résurrection
Cette opération m’a permis de réintégrer dans ce blog :
- 52 articles provenant de mon ancien site
jeromegiacomini.net; - 8 articles uniques publiés sur
blogs.infinitesquare.com; - 12 images retrouvées et stockées localement.
J’ai aussi écarté 17 republications pour ne conserver qu’une seule version de chaque texte. Au total, 60 articles uniques ont retrouvé une vie en Markdown.
Tout n’a pas pu être récupéré parfaitement. Certaines images restent perdues et quelques métadonnées n’existent simplement plus. Mais les textes, les exemples de code et leur provenance sont de nouveau stockés dans un format lisible, versionnable et beaucoup moins dépendant d’un moteur de blog particulier.
Ce que j’en retiens
La Wayback Machine est une ressource extraordinaire, mais elle ne transforme pas automatiquement un ancien site en archive propre. Pour obtenir un résultat réutilisable, il faut distinguer le catalogue des captures de leur contenu, normaliser les URL, connaître un minimum la structure du site, prévoir plusieurs captures de secours et accepter de signaler ce que l’on ne sait pas.
Le Markdown constitue ensuite une bonne destination : les fichiers sont simples, portables, faciles à relire et à conserver dans Git. Si le moteur actuel du blog disparaît un jour, je devrais donc pouvoir migrer plus facilement.
Enfin, en théorie.
Sinon, je suppose que je n’aurai plus qu’à écrire NecroBlog CLI 2 : le retour du retour des morts.
Happy coding 🙂