· 11 min de lecture

guide-dl

Outil en ligne de commande qui télécharge les guides de jeux depuis GameFAQs et LP Archive, les stocke dans un cache local et permet de les réexporter vers plusieurs formats sans re-solliciter le site.

  • #Python
  • #CLI
  • #Web scraping
  • #Pandoc
  • #Documentation

Le projet en une phrase

guide-dl est un outil en ligne de commande qui télécharge les guides de jeux depuis GameFAQs et LP Archive, les stocke dans un cache local, et permet de les réexporter à volonté vers plusieurs formats sans avoir à re-télécharger la source.

Concrètement, l’outil transforme une simple URL de guide en une archive locale complète, disponible hors ligne, dans le format qui convient au moment : Markdown pour la lecture rapide, DOCX ou ODT pour l’archivage propre. Il est fonctionnel et utilisé au quotidien ; le code reste privé, par respect pour les sites communautaires dont il exploite les pages.


Ce qui a motivé ce projet

Les guides de jeux publiés sur GameFAQs et LP Archive sont des ressources précieuses mais fragiles : un site peut fermer, une page peut être modifiée, un lien peut se briser. À cela s’ajoute qu’aucun outil existant ne fait proprement ce que je cherchais, à savoir télécharger une fois, stocker une version canonique, puis réexporter à volonté vers plusieurs formats en travaillant entièrement hors ligne une fois la source récupérée.

L’idée de base est donc restée simple. Une commande fetch qui télécharge une fois, une commande render qui exporte autant de fois qu’on veut à partir du cache. Ce cache est un fichier .render.json qui contient le HTML canonique du guide et ses métadonnées ; les images sont téléchargées séparément et leurs URLs réécrites pour pointer vers les fichiers locaux. La conséquence est directe : une fois le guide récupéré, tester un nouveau format, corriger un rendu ou ajuster un template ne coûte plus une seule requête au site source.


Le choix de la stack

CoucheDétail
CLITyper, avec commandes fetch, render, batch, info, extractors
HTTPhttpx (asynchrone), retry exponentiel, rate limiting configurable
Parsing HTMLBeautifulSoup4 + lxml
Affichage terminalRich (mode interactif) avec fallback --plain et --quiet
ConversionPandoc (binaire système) pour Markdown, DOCX, ODT
Post-traitement DOCXpython-docx (bordures de tableaux, en-tête courant, styles)
Post-traitement ODTPatching XML direct via lxml + zipfile
ImagesTéléchargement parallèle, cache conditionnel (ETag, If-Modified-Since)
ExtracteursGameFAQs, LP Archive, interface commune extensible
Packagingsetuptools + setuptools-scm, pyproject.toml

Une architecture en couches

Le projet est structuré en couches, chacune avec une responsabilité claire, ce qui permet d’ajouter un nouveau site (un nouvel extracteur) ou un nouveau format (un nouveau chemin dans le pipeline) sans avoir à toucher au reste.

Extracteurs

Chaque site est adapté à une interface commune, qui isole les particularités du parsing de tout le reste du pipeline.

class Extractor(ABC):
    @classmethod
    def name(cls) -> str
    def matches(cls, url: str) -> bool
    async def inspect(url, client) -> dict
    async def extract(url, client, max_size_mb, progress, ctx) -> RenderCache
    def game_slug_from_url(url) -> str
    def guide_id_from_url(url) -> str

Deux extracteurs sont livrés à ce stade : gamefaqs pour les URLs de la forme gamefaqs.gamespot.com/.../faqs/NNNNN, et lparchive pour lparchive.org/Game-Name/. Ajouter un troisième site se résume à écrire une nouvelle classe qui implémente cette même interface.

Client HTTP

Un wrapper autour de httpx.AsyncClient centralise le retry exponentiel, le délai minimum entre requêtes (min_interval), et surtout deux pools de concurrence distincts pour les pages HTML et pour les images : cette séparation permet de télécharger une centaine d’images en parallèle sans jamais taper plus de deux ou trois fois par seconde sur les pages du guide lui-même.

Modèles

Toutes les structures de données passent par un petit ensemble de dataclasses : GuideMetadata, GuidePart, ImageInfo, ImageManifest, RenderCache. Ces modèles servent à la fois de contrat entre les couches et de format de sérialisation JSON du cache.

Pipeline de rendu

C’est le cœur du projet, et c’est là que se joue l’essentiel du travail. Le HTML brut du guide, tel que capté par l’extracteur, est loin d’être directement exportable : il traîne des menus, des tables de mise en page, des scripts, des iframes YouTube, des tables de matières parasites. Le pipeline le nettoie, l’assemble, le convertit via Pandoc, puis post-traite le résultat pour compenser les limites de Pandoc sur les formats bureautiques.

Les quatre étapes : nettoyage HTML (cleaner.py) qui retire les scripts et styles et convertit les tables de mise en page en listes lisibles ; rendu monolithique qui rassemble toutes les pages du guide en un seul document avec métadonnées et TOC ; conversion Pandoc vers Markdown, DOCX ou ODT ; post-traitement pour appliquer les styles de tableaux, l’en-tête courant, et corriger les bugs connus de Pandoc.

Images et affichage

Le téléchargement des images fonctionne en parallèle avec cache conditionnel (les images déjà téléchargées ne sont pas retéléchargées si elles n’ont pas changé côté serveur), reprise sur Range en cas d’interruption, et une politique explicite (missing, refresh, reuse) pour choisir le comportement. Un manifeste JSON garde trace de tout.

Côté affichage, Rich est utilisé en terminal interactif avec progress bars ; en mode --plain (ou quand NO_COLOR est défini, ou quand la sortie est pipée), c’est du texte brut sur stderr, avec stdout réservé aux données pipeables (--json pour l’inspection).


Le respect des auteurs comme contrainte de conception

Les guides publiés sur GameFAQs et LP Archive sont écrits par des joueurs qui y consacrent parfois des semaines, gratuitement, pour d’autres joueurs. Un outil qui les archive doit au minimum ne pas les effacer, et si possible les remettre en avant. Le rendu des métadonnées, aussi discret soit-il visuellement, occupe donc une part importante du code.

La métadonnée du guide (titre, auteur, date de mise à jour, URL source) est la première chose qui apparaît dans chaque format de sortie, sans exception.

FormatMétadonnée affichée
MarkdownFront matter YAML complet (title, author, updated, source)
HTMLEn-tête en <pre> en haut de page, avant le corps du guide
DOCX / ODTEn-tête courant répété sur chaque page : titre / par auteur / mis à jour
TXTBloc encadré ASCII en haut du fichier

Peu visible côté utilisateur, cette couche est celle qui a demandé le plus d’itérations. Les fonctions render_metadata(), apply_docx_header() et _patch_styles_xml() ont été retravaillées à la main sur des dizaines de guides réels avant de produire un résultat systématiquement propre, quel que soit le format.


Les limites de Pandoc, et comment le projet les contourne

Pandoc est un outil remarquable, capable de convertir presque n’importe quoi vers presque n’importe quoi d’autre, mais il a des limites bien documentées sur les formats bureautiques. Une bonne partie du pipeline consiste précisément à travailler autour de ces limites.

Tables sans bordures ni en-tête répétée. Pandoc génère des tableaux DOCX sans bordures, dont l’en-tête ne se répète pas sur les pages suivantes et dont les largeurs de colonnes sont souvent inégales. Le projet post-traite les fichiers avec python-docx pour ajouter les bordures, colorier l’en-tête, marquer la première ligne comme en-tête à répéter, et redistribuer les largeurs. Pour l’ODT, c’est plus radical : odfpy a des bugs sur les fichiers produits par Pandoc, donc le code patche directement le XML via zipfile et lxml.

Templates de référence limités. L’option --reference-doc de Pandoc ne permet de personnaliser que les styles, pas le contenu, et Pandoc peut ajouter des entrées invalides dans [Content_Types].xml qui rendent le fichier corrompu à l’ouverture dans Word. Le projet utilise des templates de référence intégrés dans guide_dl/templates/ pour les styles de base, et le post-traitement corrige les fichiers générés.

Style TableCaption absent en ODT. Le template ODT par défaut de Pandoc ne le contient pas ; le post-traitement l’injecte.

Tableaux complexes. Les rowspan et colspan ne sont pas entièrement supportés dans tous les formats. Les extracteurs simplifient les structures de tables complexes en amont lorsque c’est possible, et le nettoyage HTML réduit les tables de mise en page en listes.


Le rôle de l’IA sur ce projet

J’ai utilisé Claude Code sur guide-dl, mais pas partout, et pas de la même manière selon les couches.

L’IA a servi principalement sur les branchements techniques que je ne connaissais pas ou peu : la gestion de la concurrence asynchrone (le pool de workers, le RateLimiter, la coordination entre requêtes HTML et requêtes images), la structure du CLI avec Typer et ses sous-commandes partagées, ainsi que le squelette initial des extracteurs, des modèles et du pipeline.

Le projet ne tient pas sur le terrain grâce à l’IA. Le templating et le rendu des métadonnées, les heuristiques de classification des tables (distinguer une table de données d’une table de mise en page dans un guide GameFAQs demande des règles ad hoc), le post-traitement ODT par manipulation directe du XML, et surtout les dizaines de guides réels téléchargés pour valider les cas particuliers relèvent d’un travail manuel, obtenu par ajustements successifs sur du contenu réel plutôt que par génération.


L’observation terrain

Le projet a été testé sur des guides réels, choisis pour couvrir les cas typiques et les cas limites.

Type de guideNombreObservations
GameFAQs, guides texte simples5Fonctionne sans intervention
GameFAQs, guides avec tableaux de données8Classification des tables correcte, parfois trop agressive sur les cas ambigus
GameFAQs, guides avec crédits ou menus4Menus bien reconvertis en listes propres
LP Archive, guides avec images6Images téléchargées, URLs réécrites vers le local
LP Archive, guides avec iframes YouTube3Iframes transformées en liens texte, plus lisibles à l’export

Les cas qui posent encore problème sont rares : guides avec des tableaux imbriqués, structures HTML non standard, ou guides d’un seul très long fichier qui saturent la mémoire au parsing.


Ce qui est délibérément absent

Le projet n’est pas publié sur GitHub. GameFAQs et LP Archive sont des sites communautaires qui vivent de leur trafic direct et de leurs bénévoles, pas des fournisseurs d’API. Un outil clé en main qui télécharge un guide entier en une commande présente un risque net pour leur modèle : trop visible, il pousserait les sites à durcir leurs protections (Cloudflare, CAPTCHA, blocages), au détriment des utilisateurs légitimes qui viennent lire un guide en ligne. D’autres projets similaires ont été retirés ou gardés confidentiels pour la même raison.

L’outil reste donc à usage personnel : je peux le montrer, en parler, en expliquer les choix, mais je n’en distribue pas le code. La cohérence avec ce que le projet dit du respect des auteurs et des ressources se joue précisément là.


Aperçu de l’interface

# Télécharger un guide depuis GameFAQs (Markdown par défaut)
guide-dl fetch "https://gamefaqs.gamespot.com/pc/206086-ys-viii/faqs/75520"

# Depuis LP Archive, avec plusieurs formats en une passe
guide-dl fetch "https://lparchive.org/Persona-4/" -f md,html,docx

# Réexporter depuis le cache local, sans re-solliciter le site
guide-dl render ./guides/ys-viii-lacrimosa-of-dana/75520 -f docx -O

# Inspecter un guide sans le télécharger
guide-dl info "https://lparchive.org/Persona-4/" --json

# Traitement par lots
guide-dl batch urls.txt -f md --no-images

Chaque guide téléchargé produit une arborescence stable :

guides/
└── game-slug/
    └── guide-id/
        ├── game-slug-guide-id.md         # Markdown
        ├── game-slug-guide-id.html       # HTML autonome
        ├── game-slug-guide-id.docx       # Word (si Pandoc installé)
        ├── game-slug-guide-id.txt        # Texte brut
        ├── metadata.json                 # Métadonnées du guide
        ├── .render.json                  # Cache HTML interne
        └── images/
            ├── image1.jpg
            └── image2.png

Perspectives

Le socle est stable, les grands cas sont couverts. Trois axes d’évolution me semblent naturels : ajouter d’autres extracteurs selon les besoins (StrategyWiki, VGCharts, wikis communautaires) tant que ça reste faisable proprement, améliorer le post-traitement DOCX et ODT sur les tableaux imbriqués qui restent un cas limite, et optimiser le pipeline sur les très longs guides (plus de 100 pages) où le parsing monolithique commence à peser.

Le projet reste un outil personnel, et le restera.


Compétences mobilisées

Concevoir une architecture qui protège la ressource source. Défi : télécharger sans jamais retélécharger, permettre de tester n’importe quel format de sortie sans une seule requête supplémentaire au site source. Réponse : séparation fetch / render autour d’un cache HTML immuable (.render.json), deux pools de concurrence distincts (HTML et images), délai minimum entre requêtes configurable, cache d’images conditionnel via ETag et If-Modified-Since. Résultat : une fois un guide récupéré, ajuster un template ou tester un format supplémentaire coûte zéro requête au site.

Contourner les limites documentées d’un outil de conversion. Défi : Pandoc produit des DOCX et ODT dont les tableaux n’ont ni bordures ni en-têtes répétées, des largeurs de colonnes inégales, et parfois un [Content_Types].xml corrompu à l’ouverture dans Word. Réponses : post-traitement python-docx pour les DOCX (styles de tableau, en-tête courant, colonnes équilibrées), manipulation directe du XML par zipfile et lxml pour les ODT (odfpy étant cassé sur les fichiers Pandoc), templates de référence intégrés au projet pour piloter les styles de base. Résultat : fichiers Office ouvrables sans warning, tableaux lisibles en impression, métadonnées d’auteur affichées en en-tête sur chaque page.

Étendre proprement un système à de nouvelles sources. Défi : les guides ne vivent pas tous sur GameFAQs. Prévoir l’ajout de futures sources sans casser l’existant. Réponse : interface Extractor unique (matches, inspect, extract, game_slug_from_url, guide_id_from_url) que chaque site implémente, isolant les particularités de parsing du reste du pipeline. Résultat : deux sites livrés (GameFAQs, LP Archive), l’ajout d’un troisième se réduit à écrire une nouvelle classe.

Distinguer ce que l’IA peut faire de ce qu’elle ne peut pas. Réponse : Claude Code utilisé pour la concurrence asynchrone, le squelette CLI Typer et l’ossature des modèles ; heuristiques de classification des tables, templating des métadonnées et post-traitement ODT ajustés à la main sur des dizaines de guides réels, faute d’exemples génériques exploitables par l’IA. Résultat : le projet fonctionne sur les cas particuliers, pas seulement sur les cas d’école.

Assumer une posture publique. Défi : un outil clé en main de téléchargement expose ses sites cibles à un renforcement des protections qui nuirait à toute la communauté qui les fréquente légitimement. Réponse : projet privé assumé, argumenté par le respect des ressources plutôt que par la peur d’un contentieux, cohérent avec le crédit systématique des auteurs affiché en tête de chaque fichier généré.