L'ingénierie d'acquisition¶
Un modèle de prédiction est un programme de quelques centaines de lignes. La collecte qui l'alimente en fait plusieurs milliers, tourne pendant des heures, échoue de façon silencieuse et se répare à la main.
Ce chapitre décrit cette moitié-là : les pages, les protocoles, les blocages, les contournements, et les quatre ou cinq fois où l'infrastructure a menti sans prévenir.
Le cache HLTV¶
Tout part de là. HLTV n'expose aucune API JSON : le site est du HTML rendu côté serveur, et la seule façon d'en tirer quoi que ce soit est de télécharger des pages et de les lire.
Ce que le cache contient¶
Au moment de la clôture de la campagne, l'index du cache compte 16 178 lignes pour 13 160 URLs distinctes — l'écart correspond aux pages re-téléchargées volontairement. La composition :
| type de page | URLs distinctes | ce qu'on y prend |
|---|---|---|
/matches/<id>/… |
11 526 | tables de stats par joueur, veto, scores par map, mi-temps CT/T, bloc H2H, rang affiché |
/team/<id>/… |
657 | historique de rang mondial daté, stats par map, ratings joueurs |
/events/<id>/… |
656 | dotation, poids VRS, LAN/online, dates de début et de fin |
/valve-ranking/… |
41 | classements officiels rejoués, détail par équipe et par jour |
/ranking/teams/… |
31 | archives hebdomadaires du classement HLTV |
/results?offset=… |
~60 | les listings paginés de résultats |
Ces 11 526 pages de match sont le gisement principal du projet. Chacune pèse de l'ordre de 200 Ko de HTML et contient, dans un seul fichier, tout ce que trois familles de features entières exploitent.
Un détail de chronologie mérite d'être noté, parce qu'il explique des écarts entre les
rapports : le cache a grandi pendant la campagne. L'approche maps a miné 7 148 pages,
l'approche stats en a miné 9 112, et l'approche finale en parse environ 11 500. Le rapport
d'assault le dit explicitement : build_dataset.py a dû ré-extraire tout le cache
parce que « les .pkl des familles dataient d'avant la collecte étendue ». Comparer des
chiffres issus de deux approches différentes suppose de vérifier qu'ils portent sur la même
matière.
Le mécanisme du cache¶
Trois lignes suffisent à le décrire.
Indexation par hachage. Une URL HLTV contient des slugs et parfois des points
d'interrogation ; elle ne fait pas un nom de fichier. Le chemin de cache est donc les seize
premiers caractères hexadécimaux du SHA-1 de l'URL, suivis de .html. Un fichier
index.txt en mode ajout seul conserve la correspondance nom de fichier → URL, tabulée.
Pas d'empoisonnement. Une réponse en erreur n'est jamais écrite : raise_for_status()
est appelé avant l'écriture disque. Un 503 passager ne laisse pas derrière lui un fichier
vide qui serait servi éternellement comme s'il était valide.
Mode hors ligne strict. Une variable de module, OFFLINE, bascule tout le système : une
page absente du cache devient une LookupError au lieu d'un téléchargement silencieux. Tous
les scripts d'analyse la mettent à True dès l'import. C'est ce qui garantit qu'un
entraînement rejoué deux fois voit exactement les mêmes données.
Politesse : 2,5 secondes¶
Le module maintient l'horodatage de la dernière requête réseau et attend si nécessaire :
wait = MIN_DELAY - (time.time() - _last_request[0])
if wait > 0:
time.sleep(wait)
MIN_DELAY vaut 2,5 secondes par défaut, réglable par variable d'environnement. Le cache
n'est pas concerné : relire une page déjà téléchargée est instantané.
Cette cadence a été relevée quand il le fallait. Le script de réparation de frontière du
cache fixe explicitement hltv.MIN_DELAY = 4.0, avec le commentaire « consigne du jour :
jamais moins de 4 s entre requêtes ». Et les explorations de nouvelles sources travaillent
sous budget de requêtes déclaré : le rapport d'exploration ouvre sur la ligne
« budget_hltv : 10 requêtes / 150 autorisées ».
À retenir
Un scraper qui se comporte bien n'est pas seulement une question d'éthique : c'est ce qui permet à un projet de durer six mois sur la même source. Trois pratiques y suffisent — un délai minimum appliqué au niveau du transport, un cache agressif pour ne jamais redemander deux fois la même chose, et un budget explicite pour l'exploration.
Le parsing par expressions régulières ancrées¶
Ni bs4 ni lxml ne sont installés dans l'image. Le parsing se fait donc par expressions
régulières sur le HTML brut, et la docstring du module prend les devants sur l'objection
évidente :
Ce n'est pas un choix esthétique : chaque motif est ancré sur un attribut ou une classe stable, et le test casse bruyamment si HLTV change son gabarit, ce qui vaut mieux qu'un parseur tolérant qui renverrait silencieusement des listes vides.
L'argument est plus solide qu'il n'y paraît. Un sélecteur CSS qui ne trouve rien renvoie une
liste vide, et une liste vide se propage jusqu'à un jeu de données amputé sans qu'aucune
alarme ne sonne. Une regex ancrée sur <div class="time" data-unix="…"> échoue à zéro
résultat de la même façon — mais les tests du projet vérifient un nombre de résultats
attendu, pas seulement l'absence d'exception.
Un exemple concret, celui qui extrait le rang mondial affiché sur une page de match :
_PAGE_RANK = re.compile(r'/ranking/teams/(\d+)/(\w+)/(\d+)/(\d+)"[^>]*>'
r'<span>World rank: </span>#(\d+)')
Cinq groupes : année, mois, jour, identifiant d'équipe, rang. Le motif est ancré sur le libellé littéral « World rank: » et sur la forme de l'URL d'archive du classement. La date vient de l'URL, pas d'un attribut séparé — c'est ce qui rend cette valeur datable, donc utilisable comme feature.
Les 403 de Cloudflare sur /stats¶
Voici l'obstacle le plus coûteux de la campagne d'acquisition, et son histoire complète.
Le symptôme¶
Les pages /matches/ se téléchargent sans problème avec impersonate="chrome" de
curl_cffi — pas de cookie, pas de session, juste une empreinte TLS de navigateur. Les pages
/stats/*, elles, renvoient systématiquement un 403 accompagné d'une page de challenge
Cloudflare d'environ 6 Ko, reconnaissable à son <title>Just a moment...</title>.
Or /stats/* est exactement là où se trouvent les données les plus désirables : les duels
d'ouverture, les clutchs, les multi-kills par match, les ratings de carrière par joueur, les
splits LAN/online, les winrates par map longue durée. Et /betting/money, c'est-à-dire les
cotes des bookmakers — la cible même que le projet essaie d'atteindre.
Les contournements ratés¶
Six impersonations récentes. curl_cffi 0.16.1 propose des empreintes TLS de navigateurs
modernes. Toutes ont été essayées sur /stats/teams : chrome131, chrome145, chrome150,
firefox147, safari260, chrome131_android. Les six renvoient 403 avec la page « Just a
moment ». Verdict : le blocage n'est pas une question d'empreinte TLS.
La session réchauffée. Deuxième hypothèse : peut-être manque-t-il des cookies. Une
session curl_cffi fait d'abord un GET /matches, obtient un 200 et récupère les cookies
__cflb, __cf_bm, _cfuvid, puis attaque /stats avec ces cookies et un Referer
crédible. Résultat : 403 quand même, avec la même session qui obtenait 200 une seconde
plus tôt.
Le site mobile. Une résolution DNS suffit : m.hltv.org pointe sur la même IP que www.
HLTV est responsive, il n'y a pas de variante mobile. Zéro requête dépensée pour éliminer
cette piste — la bonne façon de tester une hypothèse.
Le diagnostic¶
Le challenge Cloudflare est scopé au chemin /stats/*. Il faudrait exécuter le
JavaScript du challenge pour obtenir un vrai cookie cf_clearance, ce qui suppose un
navigateur sans interface graphique — un outil que le projet n'a pas et n'a pas voulu
ajouter.
Le rapport conclut : « Verdict HLTV /stats en direct : mort, définitivement. »
Intuition
Quand une porte est fermée, il y a trois familles de réponses : forcer la porte (les impersonations), chercher une autre porte du même bâtiment (le site mobile), ou trouver quelqu'un qui a déjà photographié l'intérieur. La troisième a marché, deux fois.
Les contournements réussis¶
Contournement n°1 — miner ce qu'on a déjà. Les pages /matches/ téléchargées depuis des
mois contiennent, dans une section id="all-content", les tables table.totalstats par
joueur : K-D, eK-eD, Swing, ADR, eADR, KAST, eKAST et le rating 3.0. Elles contiennent aussi
le veto complet, les scores par map avec les mi-temps CT/T, le format Bo1/Bo3 et le drapeau
Online/LAN.
Rien de tout cela n'avait jamais été parsé. Le gisement était sur le disque depuis le début.
Coût réseau du contournement : zéro. Gain : les deux familles stats et maps, soit
0,0042 et 0,0038 de Brier.
Contournement n°2 — les tooltips fusioncharts des pages d'équipe. Les pages /team/<id>
embarquent un graphique d'évolution du rang mondial. Ce graphique est alimenté par un blob
JSON fusioncharts inclus dans le HTML, et ce blob contient l'historique complet et daté
point par point, de 2016 à 2026. Sur une page témoin : 822 points. Sur l'ensemble des 656
équipes : 66 860 points, dont 494 équipes avec un historique exploitable, 234 avec des
statistiques par map et 305 avec des ratings de joueurs. 162 pages sont vides côté HLTV.
Coût réseau : zéro, les pages étaient en cache. C'est cette extraction qui a produit
hr_rank_diff et hr_trend_diff, soit 0,0018 de Brier.
Contournement n°3 — la Wayback Machine. L'Internet Archive a crawlé HLTV massivement.
L'API CDX permet de lister les URLs archivées ; le préfixe /web/<timestamp>id_/ sert le
HTML original, sans la couche d'habillage de l'archive — et sans Cloudflare, puisque la
requête va à archive.org.
Le recensement : au moins 20 000 URLs distinctes hltv.org/stats/players/* archivées en 200
depuis 2025, autant de stats/matches/*, et 4 256 stats/teams/*. Sur les dix équipes du
haut de notre pool, dix sont couvertes, avec 11 à 76 snapshots chacune.
Deux propriétés rendent cette source précieuse. La jointure est exacte par construction : l'identifiant HLTV est dans l'URL, c'est notre clé native. Et le timestamp du snapshot date les statistiques, ce qui donne l'intégrité temporelle gratuitement — plusieurs snapshots d'une même page forment même une série temporelle.
La contrepartie est le débit. La politesse envers archive.org impose environ une requête toutes les deux secondes. La collecte des carrières a ramené 966 snapshots en 54,8 minutes, pour 629 joueurs sur 3 038 et 131 équipes sur 656. Le rapport d'exploration recommandait d'ailleurs de cibler « les ~50 équipes / 200 joueurs du pool VRS, pas l'exhaustivité ».
Comme vu au chapitre précédent, cette source n'a finalement rien produit — non par manque de qualité, mais par manque de couverture après filtrage temporel : 1 145 matchs sur 6 056.
bo3.gg : l'API qui existait¶
Le meilleur gisement de la campagne n'était pas chez HLTV.
Ce que c'est¶
bo3.gg expose une API REST ouverte, sans clé, sur https://bo3.gg/api/v1/. Le
recensement initial : 8 378 équipes, 20 308 joueurs, 73 486 matchs terminés répartis sur les
tiers s, a, b et c, et 143 029 manches.
Et surtout, l'endpoint /games/<id> renvoie chaque round, avec pour chaque équipe :
equipment_value, economy_level et son écart, money_spent et money_save,
loss_bonus_streak, pistol_round, first_kills et first_death, trade_kills,
clutch_attempts et clutches, utility_value, flash_assists, poses et désamorçages de
bombe, et la raison de fin de round.
C'est précisément l'économie par round que le cache HLTV ne contient pas.
Deux bonus figurent au passage : un champ ps_id qui est l'identifiant PandaScore de
l'équipe ou du joueur — un pont gratuit vers une autre API si une clé arrive un jour — et un
champ demo_url pointant vers les démos GOTV.
Le piège d'API silencieux¶
Il mérite sa propre section, parce que c'est le mode de défaillance le plus vicieux qu'on puisse rencontrer sur une API.
Sur l'endpoint /players, le filtre par pseudo s'écrit :
filter[nickname][like]=<pseudo>
La forme intuitive, préfixée par le nom de table :
filter[players.nickname][like]=<pseudo>
est ignorée silencieusement. Pas d'erreur 400. Pas de message. L'API renvoie un 200 avec la liste complète, non filtrée.
Un client naïf reçoit donc des données parfaitement bien formées, dans le bon schéma, avec le bon code HTTP — et complètement fausses. Si le code d'appel prend le premier résultat, il apparie chaque joueur au même joueur arbitraire, et la corruption se propage jusqu'aux features sans qu'aucune exception ne soit levée.
Limite importante
Un code 200 ne prouve rien. La seule défense contre ce genre de défaut est un contrôle de cohérence explicite : on demande dix joueurs qu'on connaît, et on vérifie qu'on obtient bien ces dix-là. C'est ce que le projet a fait — jointure d'équipes 15/15 exacte par nom, jointure de joueurs 9/10 exacte, y compris sur un joueur de tier obscur, ce dernier point étant le vrai test.
L'appariement par noms et dates¶
bo3.gg et HLTV n'ont aucun identifiant commun. L'appariement se fait donc sur les noms d'équipes et les dates, en deux passes :
- Passe exacte. Noms d'équipes normalisés (accents retirés, minuscules, alphanumérique seulement) identiques des deux côtés, et date à \(\pm 1\) jour. Rendement : 5 085 appariements.
- Passe floue. Un côté exact, date à \(\pm 12\) heures, et similarité
difflib\(\geq 0{,}75\) sur l'autre nom. Rendement : 64 appariements supplémentaires, chacun marquémethod="fuzzy"avec sonname_ratioconservé — pour que la modélisation puisse les filtrer si elle le souhaite.
Total : 5 149 matchs appariés sur 6 056, soit 85 %. Le résidu non apparié est « essentiellement absent de bo3.gg » : de petits événements que le site ne couvre pas.
Les contrôles qui rendent l'appariement crédible¶
Un appariement par noms est une hypothèse. Trois vérifications l'ont transformée en fait mesuré.
Le vainqueur. Sur les 4 304 matchs pour lesquels bo3.gg fournit des rounds, le vainqueur
selon bo3.gg est le nôtre dans 4 302 cas. Les deux écarts sont listés nommément dans le
rapport et exclus de la modélisation — leurs clés apparaissent en dur dans le code sous le
nom ECO_EXCLUDE.
Le score exact. Concordance sur 3 940 matchs. L'écart avec les 4 302 s'explique par les formats et les manches non jouées.
La reconstruction. Sur les Bo1, le score reconstruit en comptant les rounds gagnés égale le score HLTV. C'est le contrôle le plus fort des trois : il vérifie non pas l'appariement mais la cohérence interne de la donnée round par round.
Enfin, le taux de rounds effectivement parsés a été mesuré par tier, parce que le champ
parsed_status peut rester à « waiting » sur les petits matchs :
| tier | manches avec rounds | taux |
|---|---|---|
| s | 758 / 758 | 100 % |
| a | 354 / 358 | 99 % |
| b | 4 559 / 4 826 | 94 % |
| c | 4 102 / 4 238 | 97 % |
Le volume et le coût¶
| collecte | volume | durée | échecs |
|---|---|---|---|
rounds (rounds.pkl) |
209 482 rounds sur 10 180 manches, 4 304 de nos matchs (71 %) | 209,2 min | 0 |
historique (bo3_history.pkl) |
40 023 matchs et 77 890 manches, 2024-01 → 2026-08 | 27,4 min | — |
| carrières Wayback | 966 snapshots | 54,8 min | 2 |
Environ une requête par seconde, cinq heures de collecte cumulées, zéro échec de
téléchargement sur la partie bo3.gg. Les collecteurs sont résumables : l'état est sur
disque, avec des clés de reprise par fichier et par ligne, et une interruption se relance
telle quelle. Pendant toute cette phase, hltv.OFFLINE reste à True — la moisson externe
ne touche jamais HLTV.
La collecte historique 2025¶
L'historique bo3.gg a été collecté en deux temps. Le socle couvrait la fenêtre native de six
mois ; une extension dédiée (bo3_ext.py) a remonté du 25 août 2025 au 4 février 2026, avec
les mêmes fonctions et le même mécanisme de reprise, puis une troisième passe
(bo3_history.py) a ramassé les listings depuis janvier 2024.
C'est cette collecte historique qui a permis les deux gains les plus importants de la campagne : la fenêtre d'entraînement portée à douze mois (10 287 matchs d'entraînement au lieu de 4 916) et les horloges réchauffées depuis août 2025. Rappel du chiffre : −0,0072 de Brier pour les états chauds, à modèle rigoureusement identique.
Les sources mortes¶
Pour être complet, voici ce qui a été testé et éliminé, avec la preuve à chaque fois.
| source | statut | preuve |
|---|---|---|
HLTV /stats direct |
mort | 403 + « Just a moment » sur 6 impersonations et sur session réchauffée |
m.hltv.org |
mort | même IP que www, résolution DNS |
| esports-charts | mort | 403, et l'audience est peu prédictive |
| Kaggle ESTA | mort | données ère CS:GO, démos ≤ 2022, aucun recouvrement avec notre fenêtre |
| Liquipedia | viable, faible valeur | rosters fiables, peu de statistiques neuves face à bo3.gg |
| PandaScore | non testé | clé requise ; les cotes sont en offre payante |
| cotes dans notre cache | mort | grep sur 30 pages : « odds » n'apparaît que dans le forum |
Ce dernier point a une conséquence stratégique. HLTV injecte les cotes en JavaScript géolocalisé : elles ne sont pas dans le HTML. Aucune source de cotes gratuite n'a été trouvée. Le palier bookmakers — Brier 0,198 — reste donc sans données d'entraînement : on peut s'en approcher, on ne peut pas apprendre à l'imiter.
Les leçons d'infrastructure durement payées¶
Trois défauts ont chacun coûté plusieurs jours. Ils partagent une propriété : ils ne provoquent aucune erreur, seulement des chiffres faux.
Un cache sans expiration sur du contenu vivant¶
Déjà rencontré au chapitre 1 du point de vue de l'intégrité temporelle ; voyons-le du point de vue de l'infrastructure.
Le cache disque n'expire jamais. C'est un choix correct pour une page de match — le résultat d'un match joué ne change plus. C'est un choix faux pour trois autres classes de pages :
- les pages d'événement en cours, qui figent une dotation partielle définitivement ;
- les listings triés par date, dont la première page change à chaque match terminé ;
- les classements du jour, re-rendus au présent.
Le critère unificateur retenu au terme de la campagne : une ressource est acquise quand son
élément le plus récent a plus d'un jour. Trois correctifs distincts implémentent ce même
critère sur trois types de pages — hltv.event() refuse une page mise en cache avant la fin
annoncée du tournoi, results_settled refuse une page de listing trop fraîche, et
ranking_settled rafraîchit toujours la page du jour courant.
Ordre de grandeur du dommage évité : le suivi quotidien du moteur passait de 0,4 point d'écart moyen à 51 points en une dizaine de jours, uniquement à cause d'une page d'événement figée pendant qu'un tournoi majeur courait.
La pagination par offset qui glisse¶
C'est le plus beau bug de la campagne, et il mérite d'être raconté en entier.
Le symptôme. Le 25 août au matin, le test de reproduction quotidienne du classement donne, pour les journées du 19 au 22 août, des écarts de 6,75 / 6,32 / 6,09 / 4,83 points. La veille au soir, le même test donnait 0,63 / 0,69 / 1,11 / 1,10. Rien n'avait changé dans le code. Et re-télécharger les pages de classement n'y changeait rien non plus.
Le mécanisme. Le listing /results est paginé par offset : page 0 = matchs 0 à 99, page
1 = matchs 100 à 199, et ainsi de suite. Cette pagination est relative au présent. Quand
un nouveau match se termine, tout glisse d'un cran.
Conséquence : si la page 6 a été mise en cache lundi et la page 7 mercredi, elles ne se raccordent plus. Entre le dernier match de la page 6 (version lundi) et le premier match de la page 7 (version mercredi), il y a une bande de matchs qui n'apparaît sur aucune page en cache.
collecte du 23/08 18:23 collecte du 25/08 06:02
│ │
▼ ▼
page 7 ┌──────────────┐ page 6 ┌──────────────┐
(offset │ … 30/07 09:04│ (offset │31/07 09:51 … │
700) └──────────────┘ 600) └──────────────┘
└─────────── TROU ───────────┘
35 matchs, 24,8 heures
invisibles pour tout replay OFFLINE
L'état réel du cache ce matin-là, daté par les mtime des fichiers et l'index en mode
ajout seul : les pages 0 à 600 refetchées vers 06:02 par des marches courtes de vérification,
les pages 700 à 6000 datant du 23 août à 18:23. Entre les deux générations, 35 matchs
joués les 30 et 31 juillet — donc en plein cœur de la fenêtre de six mois des journées
rejouées, avec un poids d'âge d'environ 0,88.
La preuve. Trois éléments, dans l'ordre où ils ont été obtenus.
Reproduction : à 06:03, le test rejoué sur le 21 août redonne 6,09, identique à la mesure du matin.
Auto-guérison observée : le démon de production redémarre à 06:06:37 et lance une marche longue de six mois en ligne. Le contrôle de continuité en ligne refetch en pratique toute page en cache d'une marche en ligne, ce qui réécrit les pages 0 à 6000 en une génération cohérente entre 06:07:00 et 06:09:30. À 06:11, le même test donne 1,18. Aucune page de match ni d'événement n'a changé entre les deux mesures : seul le listing a bougé.
Simulation : on prend le listing sain du jour, on l'ampute exactement des 35 matchs de la bande, et on rejoue.
| jour | mesuré le matin | simulé (listing amputé) | sain |
|---|---|---|---|
| 19/08 | 6,75 | 6,78 | 0,63 |
| 21/08 | 6,09 | 6,09 | 1,12 |
| 22/08 | 4,83 | 4,84 | 1,12 |
La bande explique l'intégralité du résidu, au centième près sur le 21 août.
La suite. Un second trou du même type subsistait à la frontière des offsets 6000/6100 : une bande allant du 24 février 18:52 au 25 février 18:30, soit 23,6 heures et environ 35 matchs de plus. Hors de portée de la marche de six mois du démon, mais dans les fenêtres des journées de début août, avec un poids d'âge d'environ 13 %.
Au total, sur les deux frontières, près de deux jours entiers de matchs étaient invisibles pour tout calcul hors ligne, sans qu'aucune alarme ne se déclenche.
Le correctif. Une sentinelle dans le lecteur de listings, active en mode hors ligne : si
deux pages consécutives viennent de générations différentes — détecté par une régression de
mtime — et laissent un trou temporel de plus de 12 heures à plus de 14 jours du début
de fenêtre, alors on lève une LookupError. Le jour est ignoré bruyamment, avec le message
« cache incomplet », au lieu d'être mesuré faux.
Les deux seuils sont justifiés par la mesure et non choisis au hasard : la plus longue accalmie réelle observée dans le calendrier CS2 est de 9,9 heures, ce qui place le seuil de 12 heures au-dessus du bruit légitime ; et à 14 jours du bord de fenêtre, le poids d'âge borne l'impact d'une bande manquante à environ 0,5 point.
Le coût réseau total de l'enquête, du diagnostic au recollage des 13 pages de frontière : 13 requêtes.
À retenir
Un chiffre de test n'est interprétable qu'accompagné de l'état de son entrée. Les générations des pages de listing font partie des données du problème, au même titre que les matchs qu'elles contiennent. La sentinelle rend désormais cet état vérifié à chaque construction de jeu de données.
Les archives re-rendues au moment de la consultation¶
Le troisième défaut est le plus difficile à admettre, parce qu'il ne vient pas de nous.
La page HLTV du classement d'un jour passé n'est pas un document figé : elle est re-générée à chaque consultation, avec la connaissance disponible au moment du rendu — y compris les placements des tournois qui étaient en cours à la date affichée.
Conséquence : pendant qu'un tournoi majeur se déroulait, nos écarts sur les équipes qui y participaient atteignaient 22 à 36 points, alors même que nos cinq facteurs de calcul collaient à \(\pm 0{,}5\) point près à la page de détail de HLTV pour les mêmes équipes. Les deux mesures étaient justes ; c'est la cible qui bougeait.
Correctif : la page du jour courant est toujours rafraîchie, et les onze pages de la période concernée ont été re-capturées après la fin du tournoi. Écart moyen sur la journée du 21 août : 2,44 → 1,11 point.
Deux épisodes entiers étiquetés « dégradation du moteur » se sont ainsi révélés être des artefacts de mesure.
Récapitulatif : la carte des sources¶
┌─────────────────────────────────────────────────────────────────────┐
│ CACHE HLTV LOCAL — 13 160 URLs, 2,5 s/requête, zéro réseau à l'usage│
│ │
│ /matches/ 11 526 ──► stats joueurs (KAST, rating 3.0, ADR) │
│ ──► veto, scores par map, mi-temps CT/T │
│ ──► bloc Head to head daté │
│ ──► rang mondial affiché (lien daté) │
│ /team/ 657 ──► historique de rang (66 860 pts, fusioncharts) │
│ /events/ 656 ──► dotation, poids VRS, LAN, dates │
│ /results/ ~60 ──► le calendrier lui-même ⚠ pagination glissante│
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────┴───────────────────────────────────┐
│ SOURCES EXTERNES │
│ │
│ bo3.gg API ──► 209 482 rounds / 10 180 manches (209 min) │
│ (ouverte) ──► 40 023 matchs 2024→ pour réchauffer (27 min) │
│ ⚠ filtre silencieusement ignoré si mal préfixé │
│ │
│ Wayback CDX ──► 966 snapshots de carrières datés (55 min) │
│ ⚠ couverture 1 145/6 056 après filtre temporel │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────┴───────────────────────────────────┐
│ MORT : HLTV /stats direct · m.hltv.org · esports-charts · ESTA │
│ cotes bookmakers (aucune source gratuite trouvée) │
└─────────────────────────────────────────────────────────────────────┘
Pour aller plus loin¶
Pourquoi ne pas avoir utilisé un navigateur sans interface pour passer Cloudflare ?
C'était techniquement possible et cela aurait ouvert /stats, donc les duels
d'ouverture, les clutchs par match et les ratings de carrière. Le projet ne l'a pas fait
pour deux raisons : cela ajoutait une dépendance lourde à une image volontairement
minimale, et cela franchissait une ligne — contourner activement une protection
explicitement mise en place par le site. Les trois contournements retenus ne forcent
rien : ils lisent ce qui est déjà sur notre disque, ou ce qu'un tiers a archivé
publiquement.
L'autre question porte sur la durée de la collecte bo3.gg, qui surprend souvent.
Pourquoi 209 minutes pour 4 304 matchs ?
Une requête par manche, environ une par seconde, et 10 180 manches. La contrainte n'est pas la bande passante mais la politesse : à raison d'une requête par seconde, 10 180 requêtes font 170 minutes de plancher incompressible, auxquelles s'ajoutent les listings et les reprises. C'est le prix normal d'une collecte respectueuse, et c'est pourquoi les collecteurs sont résumables.
Le chapitre suivant prend la plus exotique des données ramenées par cette collecte — les 209 482 rounds — et montre exactement ce qu'on en a fait, pour un gain d'un à deux millièmes de Brier.