Actualise la méthodologie de détection des liens morts
This commit is contained in:
@@ -17,70 +17,54 @@ Je le fais par plaisir du travail bien fait et par respect pour mes visiteurs, m
|
||||
|
||||
## Méthodologie
|
||||
|
||||
Le script utilise désormais un fetch HTTP maison (basé sur [`undici`](https://github.com/nodejs/undici)) qui :
|
||||
Le rapport publié sur cette page est produit par la commande `manage links check external`.
|
||||
La vérification des liens internes existe aussi, mais elle n'alimente pas cette page.
|
||||
|
||||
- génère un _user-agent_ réaliste à chaque exécution (bibliothèque `user-agents`)
|
||||
- envoie d'abord une requête [`HEAD`](https://developer.mozilla.org/fr/docs/Web/HTTP/Reference/Methods/HEAD) puis, en cas d'échec ou de code ≥ 400, une requête `GET` après un délai de 5s
|
||||
- suit les redirections manuellement (jusqu'à 5 sauts) et abandonne au-delà
|
||||
- applique un _timeout_ de 5 s par requête
|
||||
- envoie des en-têtes classiques d'un navigateur (`Accept`, `Accept-Language`)
|
||||
- n'enregistre pas de cookies
|
||||
### Extraction des URL
|
||||
|
||||
Trois cas de figure se présentent à ce stade.
|
||||
Avant toute requête réseau, l'outil parcourt les fichiers markdown et yaml du dossier `content/`.
|
||||
Il en extrait les URL externes uniques depuis le frontmatter, le corps des articles et les fichiers de métadonnées.
|
||||
Pour chaque URL, il conserve tous les emplacements précis où elle apparaît.
|
||||
Les liens présents dans du code, dans un texte barré, dans une valeur YAML commentée, ou dans certaines propriétés volontairement ignorées comme `aliases`, `alias` et `comments_url`, ne sont pas pris en compte.
|
||||
|
||||
### Code HTTP entre 200 et 400
|
||||
### Sélection des URL à contrôler
|
||||
|
||||
Mon outil considère systématiquement qu'un code HTTP supérieur à 200 et strictement inférieur à 400 est une page accessible.
|
||||
Certaines URL sont ignorées avant même la phase réseau.
|
||||
C'est le cas des domaines explicitement exclus de la vérification.
|
||||
C'est aussi le cas des URL déjà couvertes par mes fichiers de remplacements ou de suppressions logiques.
|
||||
A l'inverse, certains domaines sont explicitement marqués comme morts.
|
||||
Lorsqu'une URL appartient à cette dernière liste, elle est signalée comme morte sans aucun accès réseau.
|
||||
|
||||
Cela peut générer des faux positifs (des pages considérées comme accessibles, mais qui ne le sont pas), notamment dans les cas suivants :
|
||||
### Vérification réseau
|
||||
|
||||
- Si le site affiche une page d'erreur sans relayer le code HTTP correspondant à l'erreur
|
||||
- L'URL est conservée pour un contenu totalement différent de la page originale
|
||||
Les URL restantes sont vérifiées au moyen d'un véritable navigateur headless, piloté par Playwright avec Chromium, et non plus par un simple client HTTP.
|
||||
Le contrôle est parallélisé sur plusieurs threads.
|
||||
En revanche, deux URL relevant d'un même hôte ne sont jamais vérifiées en parallèle.
|
||||
Un délai minimal configurable peut en outre être imposé entre deux accès au même hôte.
|
||||
Chaque URL est ouverte dans un contexte de navigation neuf, sans réutiliser l'état d'une URL à l'autre.
|
||||
Le navigateur se présente avec un user-agent de navigateur de bureau, la locale `fr-FR`, le fuseau horaire `Europe/Paris` et un en-tête `Accept-Language` cohérent.
|
||||
Les erreurs de certificats TLS sont ignorées.
|
||||
Le temps maximal accordé à une navigation est de 30 secondes.
|
||||
Les redirections ordinaires sont suivies comme le ferait un navigateur classique.
|
||||
Le dernier état observé et son historique sont ensuite enregistrés dans un cache local.
|
||||
|
||||
Lorsque je constate qu'un URL retourne un code strictement inférieur à 400, il n'est pas re-testé avant 1 mois.
|
||||
### Interprétation des résultats
|
||||
|
||||
### Code HTTP entre 400 et 499
|
||||
Par défaut, tout code HTTP compris entre 200 et 399 est considéré comme accessible.
|
||||
Une URL qui déclenche immédiatement un téléchargement est elle aussi considérée comme accessible.
|
||||
Les réponses `429` sont traitées comme une limitation temporaire et ne sont pas classées comme des liens morts.
|
||||
Certaines pages de protection anti-bot ou de filtrage applicatif sont reclassées comme "Accès protégé", même lorsqu'elles répondent en `403`, et ne sont pas non plus considérées comme mortes.
|
||||
En revanche, un timeout, une erreur navigateur sans code HTTP exploitable, une page Wayback indiquant qu'aucune capture n'existe, ou un domaine explicitement marqué comme mort sont considérés comme des erreurs.
|
||||
Autrement dit, le rapport ne reproduit pas simplement les codes HTTP.
|
||||
Il publie le verdict final du navigateur, enrichi par quelques heuristiques destinées à réduire les faux positifs les plus grossiers.
|
||||
|
||||
Toute réponse avec un code HTTP compris entre 400 et 499 est considérée comme une erreur, dans le respect de la [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231).
|
||||
### Cache
|
||||
|
||||
Cela génère de nombreux faux négatifs (des pages considérées comme inaccessibles alors qu'elles le sont), symptomatiques d'une volonté de blocage des techniques de navigation automatisée, ou d'un problème de paramétrage de mon outil.
|
||||
|
||||
Par construction, par honnêteté intellectuelle et par bienveillance, mon outil est développé de manière à ne pas être intrusif.
|
||||
Son "paramétrage" permettrait en théorie d'exploiter des techniques plus agressives afin de limiter ces faux négatifs.
|
||||
J'ai fait le choix délibéré de ne pas rendre mon outil plus agressif, et de marquer tout lien retournant un code supérieur ou égal à 400 comme étant inaccessible, peu importe la raison réelle.
|
||||
|
||||
Je considère que ne pas respecter la RFC 7231 est une pratique destructive.
|
||||
Donc les serveurs qui répondent avec un code inapproprié doivent être marqués comme étant inaccessibles.
|
||||
|
||||
Le problème ici est que, si l'on retourne une erreur 403 pour un contenu qui existe réellement, sous prétexte que la navigation ne s'est pas faite avec un navigateur "traditionnel", il n'est pas possible pour moi de savoir si la page a été déplacée, si j'ai commis une erreur dans le copier-coller de l'URL, ou si j'ai accédé à un URL protégé par un mot de passe (un exemple de motif légitime d'utilisation de l'erreur 403).
|
||||
|
||||
Il existe trop de ces cas de figure pour que j'accepte de prendre le temps de les identifier manuellement.
|
||||
|
||||
Les requêtes ayant abouti à un code HTTP compris entre 400 et 499 ne sont pas réitérées avant 1 semaine.
|
||||
|
||||
### Code HTTP supérieur ou égal à 500
|
||||
|
||||
Les requêtes ayant abouti à un code HTTP supérieur ou égal à 500 ne sont pas réitérées avant 1 jour : ces erreurs sont censées être légitimes, transitoires et promptement corrigées.
|
||||
|
||||
J'ai néanmoins identifié que certains serveurs répondent à un navigateur automatisé avec une erreur 500.
|
||||
Je refuse de constituer et de maintenir une liste de ces serveurs.
|
||||
|
||||
### Timeout
|
||||
|
||||
De nombreux sites ont fait le choix de punir la navigation automatisée en ne répondant tout simplement pas à la requête, en laissant le client "tourner dans le vide".
|
||||
Il n'est donc pas possible, pour un script bienveillant, de savoir si le serveur distant bloque la requête ou s'il s'agit d'un problème transitoire.
|
||||
|
||||
On pourrait ergoter longtemps sur le bienfondé (ou pas) de cette technique.
|
||||
Pour ma part, je considère qu'elle est destructive.
|
||||
Donc les serveurs qui ne répondent jamais doivent être marqués comme étant inaccessibles, parce que certains d'entre eux peuvent réellement être temporairement inaccessibles.
|
||||
|
||||
Les requêtes ayant abouti à un _timeout_ ne sont pas renouvelées avant 1 semaine.
|
||||
|
||||
### Autres cas
|
||||
|
||||
Il arrive que le fetch me renvoie un statut nul/0 (qui n'existe pas réellement).
|
||||
Dans la majorité des cas, le problème est lié aux certificats du serveur (obsolescence, nom de domaine qui ne correspond pas, etc.) ou à un refus de connexion.
|
||||
|
||||
Les requêtes aboutissant à un code HTTP 0 ou à une erreur réseau ne sont pas renouvelées avant 1 semaine.
|
||||
Une URL dont le dernier état est un succès `2xx` ou `3xx` n'est pas revérifiée avant 30 jours.
|
||||
Une erreur `4xx` ordinaire est revérifiée au bout d'un jour.
|
||||
Une erreur `5xx` est revérifiée au bout de 7 jours.
|
||||
Les timeouts de 30 secondes, les réponses classées en "Accès protégé" et les réponses `429` sont revérifiés à chaque exécution.
|
||||
Les autres erreurs navigateur sans code HTTP exploitable sont revérifiées au bout d'un jour.
|
||||
Tant qu'une entrée de cache reste valide, son dernier verdict est réutilisé tel quel.
|
||||
|
||||
## Rapport
|
||||
|
||||
Reference in New Issue
Block a user