Date de publication du RFC : Septembre 2022
Auteur(s) du RFC : M. Koster, G. Illyes, H. Zeller, L. Sassman
(Google)
Chemin des normes
Première rédaction de cet article le 14 septembre 2022
Quand vous gérez un serveur Internet (pas forcément un serveur
Web) prévu pour des humains, une bonne partie du trafic, voire la
majorité, vient de robots. Ils ne sont pas
forcément malvenus mais ils peuvent être agaçants, par exemple s'ils
épuisent le serveur à tout ramasser. C'est pour cela qu'il existe
depuis longtemps une convention, le fichier
robots.txt
, pour dire aux robots gentils ce
qu'ils peuvent faire et ne pas faire. La spécification originale
était très limitée et, en pratique, la plupart des robots comprenait
un langage plus riche que celui du début. Ce nouveau RFC documente plus ou moins
le format actuel.
L'ancienne spécification, qui date de
1996, est toujours en ligne
sur le site de
référence (qui ne semble plus maintenu, certaines
informations ont des années de retard). La réalité des fichiers
robots.txt
d'aujourd'hui est différente, et
plus riche. Mais, comme souvent sur
l'Internet, c'est assez le désordre, avec
différents robots qui ne comprennent pas la totalité du langage
d'écriture des robots.txt
. Peut-être que la
publication de ce RFC aidera à uniformiser les choses.
Donc, l'idée de base est la suivante : le
robot qui veut ramasser des ressources sur un
serveur va d'abord télécharger un fichier situé sur le chemin
/robots.txt
. (Au passage, si cette norme était
faite aujourd'hui, on utiliserait le
/.well-known
du RFC 8615.) Ce fichier va contenir des instructions pour le
robot, lui disant ce qu'il peut récupérer ou pas. Ces instructions
concernent le chemin dans l'URI (et robots.txt
n'a
donc de sens que pour les serveurs utilisant ces URI du RFC 3986, par exemple les serveurs Web). Un exemple très simple
serait ce robots.txt
:
User-Agent: * Disallow: /private
Il dit que, pour tous les robots, tout est autorisé sauf les chemins
d'URI commençant par /private
. Un robot qui
suit des liens (RFC 8288) doit donc ignorer
ceux qui mènent sous /private
.
Voyons maintenant les détails pratiques (section 2 du RFC). Un
robots.txt
est composé de
groupes, chacun s'appliquant à un robot ou un
groupe de robots particulier. Les groupes sont composés de
règles, chaque règle disant que telle partie de
l'URI est interdite ou autorisée. Par défaut, tout est autorisé
(même chose s'il n'y a pas de robots.txt
du
tout, ce qui est le cas de ce blog). Un groupe commence par une
ligne User-Agent
qui va identifier le robot (ou
un astérisque pour désigner tous les
robots) :
User-Agent: GoogleBot
Le robot va donc chercher le ou les groupes qui le concernent. En
HTTP,
normalement, l'identificateur que le robot cherche dans le
robots.txt
est une sous-chaine de
l'identificateur envoyé dans le champ de l'en-tête HTTP
User-Agent
(RFC 9110,
section 10.1.5).
Le robot doit combiner tous les groupes qui s'appliquent à lui,
donc on voit parfois plusieurs groupes avec le même
User-Agent
.
Le groupe est composé de plusieurs règles, chacune commençant par
Allow
ou Disallow
(notez
que la version originale de la spécification n'avait pas
Allow
). Si plusieurs règles correspondent, le
robot doit utiliser la plus spécifique, c'est-à-dire celle dont le
plus d'octets correspond. Par exemple, avec :
Disallow: /private Allow: /private/notcompletely
Une requête pour /private/notcompletely/foobar
sera autorisée. L'ordre des règles ne compte pas (mais certains
robots sont bogués sur ce point). C'est du fait de cette notion de
correspondance la plus spécifique qu'on ne peut pas compiler un
robots.txt
en une simple expression
rationnelle, ce qui était possible avec la
spécification originelle. Si une règle Allow
et une Disallow
correspondent, avec le même
nombre d'octets, l'accès est autorisé.
Le robot a le droit d'utiliser des instructions supplémentaires, par exemple pour les sitemaps.
En HTTP, le robot peut rencontrer une redirection
lorsqu'il essaie de récupérer le robots.txt
(code HTTP 301, 302, 307 ou 308). Il devrait dans ce cas la suivre
(mais pas infinement : le RFC recommande cinq essais au
maximum). S'il n'y a pas de robots.txt
(en
HTTP, code de retour 404), tout est autorisé (c'est le cas de mon
blog). Si par contre il y a une erreur (par exemple 500 en HTTP), le
robot doit attendre et ne pas se croire tout permis. Autre point
HTTP : le robot peut suivre les instructions de mémorisation du
robots.txt
données, par exemple, dans le champ
Cache-control
de l'en-tête.
Avant de passer à la pratique, un peu de sécurité. D'abord, il
faut bien se rappeler que le respect du
robots.txt
dépend de la bonne volonté du
robot. Un robot malveillant, par exemple, ne tiendra certainement
pas compte du robots.txt
. Mais il peut y avoir
d'autres raisons pour ignorer ces règles. Par exemple, l'obligation
du dépôt légal fait que la
BNF
annonce explicitement qu'elle ignore ce
fichier. (Et un programme comme wget a
une option, -e robots=off
, pour débrayer la vérification
du robots.txt
.) Bref, un robots.txt
ne remplace pas
les mesures de sécurité, par exemple des règles d'accès aux
chemins définies dans la configuration de votre serveur HTTP. Le
robots.txt
peut même diminuer la sécurité, en
indiquant les fichiers « intéressants » à un éventuel attaquant.
Passons maintenant à la pratique. On trouve plein de mises en
œuvre de robots.txt
un peu partout mais en
trouver une parfaitement conforme au RFC est bien plus
dur. Disons-le clairement, c'est le bordel, et l'auteur d'un
robots.txt
ne peut jamais savoir comment il va
être interprété. Il ne faut donc pas être trop subtil dans son
robots.txt
et en rester à des règles
simples. Du côté des robots, on a un problème analogue : bien des
robots.txt
qu'on rencontre sont bogués. La
longue période sans spécification officielle est largement
responsable de cette situation. Et tout n'est pas clarifié par le
RFC. Par exemple, la grammaire en section 2.2 autorise un
Disallow
ou un Allow
à
être vide, mais sans préciser clairement la sémantique associée.
Pour Python, il y a un module standard, mais qui est loin de suivre le RFC. Voici un exemple d'utilisation :
import urllib.robotparser import sys if len(sys.argv) != 4: raise Exception("Usage: %s robotstxt-file user-agent path" % sys.argv[0]) input = sys.argv[1] ua = sys.argv[2] path = sys.argv[3] parser = urllib.robotparser.RobotFileParser() parser.parse(open(input).readlines()) if parser.can_fetch(ua, path): print("%s can be fetched" % path) else: print("%s is denied" % path)
Et, ici, on se sert de ce code :
% cat ultra-simple.txt User-Agent: * Disallow: /private % python test-robot.py ultra-simple.txt foobot /public/test.html /public/test.html can be fetched % python test-robot.py ultra-simple.txt foobot /private/test.html /private/test.html is denied
Mais ce module ne respecte pas la précédence des règles :
% cat precedence.txt User-Agent: * Disallow: / Allow: /bar.html % python3 test-robot.py precedence.txt foobot /bar.html /bar.html is denied
En application de la règle la plus spécifique,
/bar.html
aurait dû être autorisé. Si on
inverse Allow
et Disallow
,
on obtient le résultat attendu. Mais ce n'est pas normal, l'ordre
des règles n'étant normalement pas significatif.
Autre problème du module Python, il ne semble pas gérer les
jokers, comme l'exemple *.gif$
du
RFC. Il existe des modules Python alternatifs pour traiter les
robots.txt
mais aucun ne semble vraiment mettre
en œuvre le RFC.
La situation n'est pas forcément meilleure dans les autres langages de programmation. Essayons avec Elixir. Il existe un module pour cela. Écrivons à peu près le même programme qu'en Python :
usage = "Usage: test robotstxtname useragent path" filename = case Enum.at(System.argv(), 0) do nil -> raise RuntimeError, usage other -> other end content = case File.read(filename) do {:ok, data} -> data {:error, reason} -> raise RuntimeError, "#{filename}: #{reason}" end ua = case Enum.at(System.argv(), 1) do nil -> raise RuntimeError, usage other -> other end statuscode = 200 {:ok, rules} = :robots.parse(content, statuscode) path = case Enum.at(System.argv(), 2) do nil -> raise RuntimeError, usage other -> other end IO.puts( case :robots.is_allowed(ua, path, rules) do true -> "#{path} can be fetched" false -> "#{path} can NOT be fetched" end)
Et testons-le :
% mix run test-robot.exs ultra-simple.txt foobot /public/test.html /public/test.html can be fetched % mix run test-robot.exs ultra-simple.txt foobot /private/test.html /private/test.html can NOT be fetched
Il semble avoir moins de bogues que le module Python mais n'est quand même pas parfait.
Comme dit plus haut, le robots.txt
n'est pas
réservé au Web. Il est utilisé par exemple pour
Gemini. Ainsi, le robot de
collecte Lupa lit les robots.txt
et ne
va pas ensuite récupérer les URI interdits. En septembre 2022, 11 %
des capsules Gemini avaient un robots.txt
.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)