Allégeons notre flux RSS avec make et pandoc
Il y a quelques jours, je me suis penché sur la fréquentation du site des arsouyes et j'ai découvert que 97,8 % des visiteurs étaient des robots. En y regardant de plus près, j'ai aussi découvert que les agrégateurs venus suivre notre flux RSS pour le compte de nos fans représentent 8.5 % des visiteurs mais génèrent 36 % du volume renvoyé par le serveur.
D'un côté, c'est vrai que certains de ces robots sont un peu débiles. Certains viennent vraiment trop souvent (jusqu'à 350 par jour pour Nextcloud) et d'autres demandent à récupérer le flux complet à chaque fois (jusqu'à 170 par jour et un volume de 60 Mo). Alors que le site (et donc le flux) n'est mis à jour qu'une fois par semaine.
D'un autre côté, nous avons aussi notre part de responsabilité dans ce volume relatif dus aux visites du flux RSS :
- Nos pages sont légères. Si nous utilisions plein d'images de partout, des polices et étalions du Javascript partout1, les pages seraient plus grosses, le volume généré serait plus gros et, en comparaison, le RSS serait tout petit.
- Notre RSS est trop gros. Lors d'une nième mise à jour dont je ne me souvient plus, je me suis mis à copier le contenu intégral des articles dans le flux. Même s'il n'y a que 20 articles (et pas les 158 que le site compte jusqu'à celui-ci), 3000 mots c'est bien plus lourd qu'une petite description.
Vu que je ne peux pas agir sur le comportement des robots (c'est leur responsabilité) et que je ne veux pas rendre mes pages plus grosses pour que la part du RSS se réduise, je me suis attelé à réduire sa taille. Et tant qu'à y être, vous expliquer comment je le construit à base de petites commandes orchestrées par un makefile qui génère le site.
make pour orchestrer
On aurait pu coder un truc qui génère le flux RSS tout seul mais comme on avait déjà un makefile qui décrivait comment compiler nos pages markdown vers HTML, c’était plus cohérent2 de le compléter pour gérer aussi le fichier RSS.
Plutôt que vous mettre le contenu du makefile complet3, voici un schéma de l’organisation générale du truc. En bleu les fichiers de base (les articles en markdown et quelques templates), en jaune les commandes utilisées (envsubst, pandoc et cat), en vert les fichiers intermédiaires et en violet le fichier final.
envsubst pour remplacer
Le flux RSS est un fichier au format XML qui comprend une première partie, que j’appelle l’en-tête et qui décrit de quel flux il s’agit. Cette partie ne change presque pas mais, histoire de m’embêter, il y quand même quelques données à adapter à chaque fois :
- L’adresse du site ne change pas, mais je voulais garder la possibilité de produire un flux sur une sorte de pré-prod qui aurait son adresse a elle. J’ai donc un premier paramètre « siteurl »
- Le titre du flux, il ne change pas non plus mais j’ai un flux en français et un autre en anglais. Plutôt que de faire deux patrons (un par langue), et puisque j’ai de toutes façons déjà un paramètre, j’ajoute un second paramètre « title »,
- Le chemin vers le flux, pour la même raison que le titre, j’en fait mon troisième paramètre « path »,
- Idem pour la langue, « lang »,
- Enfin la date de publication qui correspond au moment où le flux est généré, je le nomme « pubdate ». C’est peut être le seul vrai paramètre dans l’histoire et qui justifie pourquoi je suis obligé de générer cette partie du fichier à chaque fois.
Avec ces paramètres, voici le patron du fichier d’en-tête (ou le header template pour jargonner) que j’utilise.
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>${title}</title>
<link>${siteurl}</link>
<atom:link href="${siteurl}${path}" rel="self" type="application/rss+xml"/>
<description></description>
<language>${lang}</language>
<lastBuildDate>${pubdate}</lastBuildDate>Pour remplacer les paramètres par leur valeurs, j’utilisais, encore il y a peu, pandoc. Mais autant cet outil est adapté pour convertir des documents d’un format à l’autre, autant il peut donc facilement juste remplacer des paramètres par leurs valeurs, autant je me dit que c’est un peu overkill et je voulais faire plus simple.
Je me suis tourné vers la commande envsubst qui sert justement à ça. Elle lit un fichier, remplace les variables définies dans l’environnement et utilisées dans le fichier par leur valeur et fourni le résultat. C’est tout bête4, c’est livré par défaut presque partout.
Je peux maintenant vous montrer la portion du makefile qui s’occupe de générer l’en-tête du fichier (SRC_DIR est la variable qui pointe vers le répertoire avec le code source du site, SITE_URL est la variable qui défini l’adresse du site une fois en ligne).
# Définition des variables utiles ensuite
MD_TPL_NEWS_H := templates/rss_header.xml
SRC_NEWS_FR_H := $(SRC_DIR)/rss-header.fr.xml
SRC_NEWS_EN_H := $(SRC_DIR)/rss-header.en.xml
# Dit à make que ces fichiers sont temporaires et qu'il
# doit les supprimer à la fin.
.INTERMEDIATE: $(SRC_NEWS_FR_H) $(SRC_NEWS_EN_H)
# Comment générer l'en-tête en français
$(SRC_NEWS_FR_H): $(MD_TPL_NEWS_H)
path=/rss.fr.xml \
pubdate="$$(date -R)" \
title="Flux RSS des arsouyes" \
siteurl=$(SITE_URL) \
envsubst < $^ > $@
# Comment générer l'en-tête en anglais
$(SRC_NEWS_EN_H): $(MD_TPL_NEWS_H)
path=/rss.en.xml \
pubdate="$$(date -R)" \
title="Arsouyes' RSS" \
siteurl=$(SITE_URL) \
envsubst < $^ > $@L’idée générale est très classique pour un makefile, voici les quelques subtilités que j’ai utilisées ici :
- La cible
.INTERMEDIATE, pour forcer make à considérer les fichiers en question comme temporaires et qu’il doit les supprimer lorsqu’il aura fini de générer le site. Sans cette ligne, ces fichiers temporaires viennent polluer le code source etgit5 me les listes à chaque fois que je veux voir ce que je dois commiter6. - Je dois passer mes paramètres à
envsubstsous forme de variables d’environnement. Inutile d’exporter ces variables dans l’environnement (avec la commandeexport), je peux juste les définir en début de ligne de commande (d’où les\en fin de lignes pour qu’elles n’en fasse qu’une).
On pourrait vouloir remplacer ces deux grosses recettes par une seule plus générique qui prendrait la langue comme paramètre (sous forme de % dans la règle) et utilise une sorte de fichier de configuration pour chaque langue. Mais comme ces paramètres ne sont utilisés qu’à cet endroit, je trouve ces fichiers de conf superflux.
pandoc pour compiler
Le fichier RSS comprend, après cet en-tête, une liste d’éléments correspondant chacun à un article du site7. Ici les paramètres sont plus nombreux :
- Le titre de l’article, la langue, la date de publication et une description (j’y reviens ensuite),
- L’adresse de la page, et son identifiant (ici je met le lien comme identifiant, ça marche et ça suffit largement),
- Les catégories et les auteurs.
Comme ces informations sont inscrites dans les fichiers sources des articles8 et que pandoc est bien adapté à les gérer, c’est donc cette fois un patron au format pandoc qu’on utilise.
<item>
<title>$title$</title>
<link>$siteurl$$path$</link>
<guid>$siteurl$$path$</guid>
<language>$lang$</language>
<pubDate>$date-iso$</pubDate>
<description><![CDATA[
$if(description)$$description$
$elseif(abstract)$$abstract$
$endif$
]]></description>
<author>$if(author)$$for(author)$$author$$sep$ & $endfor$$else$Les arsouyes$endif$</author>
$for(keywords)$<category>$keywords$</category>$endfor$
</item>Pour revenir à la raison qui m’a poussé à revoir la génération du RSS, à savoir l’alléger, c’est dans ce patron que ça se passe.
- Avant : je plaçais le corps de l’article dans la description (avec la variable
$body), - Maintenant : je ne place que la description (variable
$description$) et s’il n’y en a pas je place le divulgâchage (variable$abstract$) et sinon je ne met rien.
Non seulement le fichier sera plus léger, mais il respectera ce que j’avais dit en 2023 : privilégier le contenu sur le site pour que les lecteurs aient plus facile à identifier qui leur parle. Si certains préfèrent lire les pages via leurs agrégateurs, ils peuvent toujours les configurer pour qu’ils chargent la page9.
Cette fois les instructions du makefile sont un peu plus nombreuses et verbeuses mais rien de bien méchant en vrai... PANDOC_FLAGS contient des arguments spécifiques passés à pandoc de manière générale et MD_FORMAT est le format (avec le choix des extensions) que j’utilise partout.
# Fichier qui me sert de patron
MD_TPL_NEWS_ITEM := templates/rss_item.xml
# Liste des derniers articles pour chaque langue
SRC_NEWS_FR := $(shell find $(SRC_DIR)/articles -name "index.fr.md" | sort | tail -n 20)
SRC_NEWS_EN := $(shell find $(SRC_DIR)/articles -name "index.en.md" | sort | tail -n 20)
# Liste des fichiers temporaires pour chaque élément dans le flux RSS
SRC_NEWS_FR_ITEMS := $(SRC_NEWS_FR:.md=.rss.xml)
SRC_NEWS_EN_ITEMS := $(SRC_NEWS_EN:.md=.rss.xml)
.INTERMEDIATE : $(SRC_NEWS_FR_ITEMS) $(SRC_NEWS_EN_ITEMS)
# Production des éléments en français
%.fr.rss.xml: %.fr.md $(MD_TPL_NEWS_ITEM)
pandoc $(PANDOC_FLAGS) \
-M path=$(patsubst $(SRC_DIR)%,%,$(dir $@)) \
-M lang=fr \
-M siteurl=$(SITE_URL) \
--template $(MD_TPL_NEWS_ITEM) \
-f $(MD_FORMAT) \
-t html \
-o $@ \
$<
# Production des éléments en anglais
%.en.rss.xml: %.en.md $(MD_TPL_NEWS_ITEM)
pandoc $(PANDOC_FLAGS) \
-M path=$(patsubst $(SRC_DIR)%,%,$(dir $@)) \
-M lang=en \
-M siteurl=$(SITE_URL) \
--template $(MD_TPL_NEWS_ITEM) \
-f $(MD_FORMAT) \
-t html \
-o $@ \
$<J’utilise ici aussi le truc des fichiers intermédiaires10 donc pas besoin de revenir dessus. La nouveauté, c’est la liste des fichiers à inclure dans le RSS... Je vous l’isole ici pour qu’elle soit plus claire :
find $(SRC_DIR)/articles -name "index.fr.md" \
| sort \
| tail -n 20J’utilise find pour trouver les fichiers sources correspondants aux articles dans la langue que je veux. Je passe cette liste à sort pour les avoir dans l’ordre (le nom des fichiers comporte la date ou un numéro de séquence donc ça marche). Je passe enfin la liste à tail pour ne garder que les 20 derniers11.
cat pour regrouper
Le flux RSS se termine par un épilogue de deux balises XML qui viennent simplement terminer les balises laissées ouvertes plus haut. Aucune variable à remplacer ici.
</channel>
</rss>On dispose donc de tous les contenus du fichier RSS et il ne reste qu’à les mettre bouts à bouts. D’abord l’en-tête, puis les éléments et enfin l’épilogue. Puisqu’il ne s’agit que de concaténer les fichiers, la commande cat est largement suffisante pour ça.
Voici les instructions correspondantes dans le makefile. DST_DIR contient le nom du répertoire où écrire les fichiers du site et DST_FILES contient la liste des fichiers à générer.
# patron de l'épilogue
MD_TPL_NEWS_F := templates/rss_footer.xml
# Fichier RSS complet
DST_RSS_FR := $(DST_DIR)/rss.fr.xml
DST_RSS_EN := $(DST_DIR)/rss.en.xml
# On produit ces fichiers
$(DST_RSS_FR): $(SRC_NEWS_FR_H) $(SRC_NEWS_FR_ITEMS) $(MD_TPL_NEWS_F)
cat $^ > $@
$(DST_RSS_EN): $(SRC_NEWS_EN_H) $(SRC_NEWS_EN_ITEMS) $(MD_TPL_NEWS_F)
cat $^ > $@
# On les ajoute à la liste des fichiers à générer
DST_FILES += $(DST_RSS_FR) $(DST_RSS_EN)Et après ?
Même si la syntaxe des makefile n’est pas la plus belle qui soit, je trouve qu’il a une certaine élégance dans la génération des fichiers. Plutôt qu’un gros script qui fait tout, on a ici une panoplie de petites tâches simples et autonomes12.
Comme prévu, les fichiers produits sont moins volumineux qu’avant. Les deux fichiers RSS sont passés de 350 Ko (à peu près) à 9 Ko (à peu près). Soit 2.5 % ou une division de la taille par 40 (à peu près). Certains agrégateurs, plus intelligents que la moyenne, demandent si le fichier a changé avant de demander le contenu ce qui va réduire un peu l’économie, mais de combien ?
Pour avoir de vrais chiffres, j’ai publié ce nouveau RSS lundi 19 en soirée (aux alentours de 18h30), attendu une journée puis récupéré les journaux depuis la publication du précédent article. C’est à dire du 15 au 20 janvier 2026 inclus. J’ai repris les mêmes commandes mais je ne cherche plus à identifier les humains vs les robots mais la proportion du RSS par rapport au reste.
Sur la journée d’hier (20 janvier), les RSS représentent 10% des visiteurs pour à peu près 38% des requêtes mais seulement 3% du volume ! D’accord ils continuent à faire plein de requêtes, mais maintenant ça ne gaspille plus tant de bande passante que ça.
Si on regarde sur la durée, mis à part un pic de visite le 17 (suite à la publication sur le journal du hacker), le volume du site est assez stable ces derniers jours. Par contre, on voit nettement l’impact d’avoir allégé le RSS dans le volume du 20 janvier : il est bien plus faible et le RSS a presque disparu (en comparaison des autres jours).
Pour vous donner une idée de l’impact sur la proportion des visiteurs, on a repris les données du 15 janvier du précédent article en y intégrant l’économie que l’allégement du flux RSS représente13. C’est assez visible je trouve ; 34 % qui s’envolent ça se voit. Mais du coup, le volume à destination des pénibles passe de 56 à 85 %...
Déprimant ? Non.
Parce que si on regarde les chiffres qui comptent vraiment, on se rend compte que le volume que nous renvoyons à nos lecteurs humains (et leurs robots) a été réduit d’environ 82 % en moyenne par jour. Dit autrement, l’allègement du flux a permis d’économiser la bande passante de nos lecteurs, sans rien améliorer à celle des pénibles. Et ça, je trouve que c’est une bonne nouvelle.