273 lines
18 KiB
Markdown
273 lines
18 KiB
Markdown
# PdfScrapper
|
||
|
||
Navigateur visuel qui permet de sélectionner du texte sur des pages web
|
||
et de l'accumuler dans un PDF, au fil de la navigation.
|
||
|
||
## Fonctionnalités
|
||
|
||
- **Navigateur intégré** (moteur Chromium via QtWebEngine) : barre d'adresse,
|
||
précédent / suivant / recharger, session persistante (cookies/connexions
|
||
conservées entre deux lancements).
|
||
- **Sauvegarde de projet** (*"📂 Ouvrir…"* / *"💾 Enregistrer"* / *"💾
|
||
Enregistrer sous…"*, dans la barre d'outils) : un projet regroupe
|
||
les extraits capturés, le style PDF et la page en cours, dans un
|
||
fichier `.pdfscrap` (JSON). *Enregistrer* réutilise le fichier déjà
|
||
nommé (ou propose *Enregistrer sous* la première fois) ; *Ouvrir*
|
||
recharge un projet précédent (avec confirmation si des extraits non
|
||
enregistrés seraient remplacés). Une **sauvegarde automatique** tourne
|
||
aussi en tâche de fond, toutes les 60 secondes et à la fermeture de
|
||
l'appli : tant qu'aucun fichier n'a été nommé explicitement, elle
|
||
écrit dans un fichier de récupération par défaut, rechargé
|
||
automatiquement au prochain lancement (comme la restauration de
|
||
session d'un navigateur) — dès qu'un *Enregistrer sous* est fait, elle
|
||
bascule sur ce fichier.
|
||
- **Capture de texte (sélection manuelle)** : sélectionnez du texte dans la
|
||
page puis cliquez sur *"➕ Ajouter la sélection au PDF"* (ou
|
||
`Ctrl+Shift+A`). L'extrait est stocké avec le titre de la page, son URL et
|
||
l'heure de capture.
|
||
- **Capture de bloc au survol** : le bloc de contenu sous le curseur
|
||
(paragraphe, item de liste, section, article...) se surligne
|
||
automatiquement quand le mode est actif ; un clic dessus l'ajoute
|
||
directement au PDF, sans avoir à sélectionner le texte à la main. Deux
|
||
façons de l'activer, combinables :
|
||
- **Maintenir `Ctrl` enfoncé** dans la page : actif tant que la touche est
|
||
tenue, désactivé dès qu'on la relâche (pratique pour capturer un bloc
|
||
à la volée sans repasser par la souris ensuite).
|
||
- **`Ctrl+Shift+B`** (ou le bouton *"🎯 Mode capture au survol"*) : reste
|
||
actif en continu jusqu'à nouvelle bascule, y compris après avoir suivi
|
||
un lien vers la page suivante.
|
||
|
||
Tant que le mode est actif (par l'une ou l'autre méthode), le clic normal
|
||
(suivre les liens) est suspendu sur la page.
|
||
- **Panneau latéral** : liste des extraits capturés (réorganisables,
|
||
supprimables, **renommables**). Si un extrait est sélectionné dans la liste, chaque nouvelle
|
||
capture s'insère juste à côté de lui — **au-dessus** ou **en dessous**
|
||
selon la case à cocher *"Insérer les nouvelles captures au-dessus de la
|
||
sélection"* (cochée par défaut = au-dessus) — et les captures suivantes
|
||
continuent de s'enchaîner du même côté du repère sans avoir à
|
||
re-sélectionner à chaque fois. Sans sélection, les captures s'accumulent
|
||
simplement à la fin, quel que soit ce réglage.
|
||
- **Renommer un extrait** : double-cliquez sur un extrait dans la liste (ou
|
||
sélectionnez-le puis *"✏️ Renommer"*) pour changer son titre affiché —
|
||
utilisé aussi comme titre de section dans le PDF exporté.
|
||
- **Tableau des extraits** : le panneau latéral affiche les extraits
|
||
capturés dans un vrai tableau (colonnes *Heure*, *Titre*, *Aperçu*,
|
||
*Lien*, avec en-tête), plutôt qu'une simple liste de texte.
|
||
- **Lier plusieurs extraits en un seul chapitre** : quand un chapitre est
|
||
réparti sur plusieurs pages web (donc plusieurs extraits capturés
|
||
séparément), l'icône de la colonne *Lien* permet de relier un extrait
|
||
au **suivant** dans le tableau — à la manière du maillon de chaîne
|
||
entre calques adjacents dans GIMP. Les extraits reliés sont exportés
|
||
comme **une seule section** dans le PDF : un seul titre (celui du
|
||
premier extrait du groupe, toujours renommable) et une seule entrée de
|
||
sommaire, sans saut de page entre eux. Les extraits qui suivent le
|
||
premier d'un groupe sont repérés par un préfixe "↳" (colonne *Titre*,
|
||
et onglet "Corriger le texte"). Deux extraits directement reliés ne
|
||
peuvent pas être réordonnés (▲/▼) sans être déliés au préalable, pour
|
||
éviter d'inverser silencieusement le sens du lien.
|
||
- **Aperçu et style du PDF** (*"🖌 Aperçu et style du PDF…"*, barre
|
||
d'outils) : fenêtre
|
||
dédiée qui affiche un rendu en direct du PDF final (via QtPdf) pendant
|
||
que vous ajustez les réglages, répartis en deux onglets :
|
||
- **🎨 Style** : titre du document, format de page (A4/Letter), police
|
||
globale (Helvetica/Times/Courier — s'applique à tout le document),
|
||
taille du texte et des titres, couleur des titres, couleur du texte,
|
||
**couleur d'arrière-plan**, marges, affichage ou non de l'URL/date
|
||
sous chaque titre, saut de page entre extraits ou non, et
|
||
**inclusion d'un sommaire** (voir ci-dessous).
|
||
- **✏️ Corriger le texte** : liste des extraits, avec un champ titre et
|
||
une zone de texte éditable pour corriger le contenu capturé
|
||
directement (fautes de frappe, nettoyage de texte scrappé, etc.) — les
|
||
modifications s'appliquent immédiatement à l'extrait (y compris dans
|
||
le panneau latéral de la fenêtre principale) et l'aperçu se
|
||
régénère automatiquement.
|
||
|
||
L'aperçu se régénère automatiquement (avec un léger délai anti-rebond)
|
||
à chaque changement. Les réglages de style et les corrections de texte
|
||
faits ici restent actifs pour l'export "normal" depuis le panneau
|
||
latéral, même après avoir fermé cette fenêtre.
|
||
- **Sommaire cliquable** : activable depuis l'onglet Style (*"Inclure un
|
||
sommaire"*, actif par défaut dès qu'il y a plus d'un extrait). Insère une
|
||
page de sommaire juste après le titre du document, avec un lien
|
||
cliquable vers chaque section — liens internes PDF standard (annotations
|
||
`/Link` avec destination directe), utilisables dans n'importe quel
|
||
lecteur PDF.
|
||
- **Détection de liens de "suite"** : les liens dont le texte — ou le texte
|
||
d'un conteneur voisin proche (icône + légende, ex. "Partie Suivante" à
|
||
côté du lien) — ressemble à *suivant / next / chapitre suivant / lire la
|
||
suite / part 2* etc. (ou une balise `<link rel="next">`) sont surlignés
|
||
en orange et listés dans le panneau, pour enchaîner facilement la
|
||
lecture et la capture sans les chercher manuellement. La détection
|
||
tourne en continu (`MutationObserver` JS, résultats poussés vers Python
|
||
via le pont QWebChannel) plutôt qu'une seule fois au chargement —
|
||
nécessaire car beaucoup de sites (SPA, pagination injectée après coup en
|
||
JavaScript) ajoutent ce genre de lien bien après le signal de fin de
|
||
chargement.
|
||
- **Scraping assisté par IA locale** (llama-cpp-python, aucune donnée
|
||
envoyée en ligne) : configurez un modèle via *"⚙️ Configurer l'IA
|
||
locale"* (fichier `.gguf` de votre choix — non fourni avec l'appli,
|
||
liens de téléchargement vérifiés dans la section
|
||
[Installation](#modèles-gguf-recommandés) ci-dessous). Trois usages,
|
||
combinables :
|
||
- **🤖 Extraire (IA)** : demande au modèle d'extraire le texte narratif
|
||
principal de la page (menus/pubs/commentaires écartés) et l'ajoute au
|
||
PDF — alternative à la sélection manuelle/au mode survol, utile sur
|
||
les pages au contenu trop fragmenté pour un clic unique.
|
||
- **🤖 Lien suivant (IA)** : demande au modèle de désigner, parmi *tous*
|
||
les liens de la page, celui qui mène le plus probablement à la suite —
|
||
complète la détection heuristique quand celle-ci ne trouve rien
|
||
(structures de page trop inhabituelles pour un motif texte). Pour
|
||
chaque lien, le texte du **conteneur parent proche** est aussi transmis
|
||
au modèle quand il diffère du texte du lien lui-même — utile pour les
|
||
liens de pagination réduits à une icône/un chiffre sans texte propre
|
||
(ex. "2", "›"), fréquents quand un chapitre est réparti sur plusieurs
|
||
pages web. La **fin du contenu de la page actuelle** (déjà extrait,
|
||
voir 🤖 Extraire (IA) — ou relu pour l'occasion pour le bouton manuel)
|
||
est aussi transmise : un texte qui s'interrompt en plein milieu d'une
|
||
scène est un indice fort de pagination interne, qu'un simple texte de
|
||
lien ne suffit pas toujours à révéler. Le modèle indique aussi si le
|
||
lien choisi mène à la suite du **même chapitre** (pagination interne)
|
||
ou à un **nouveau chapitre**.
|
||
- **🔁 Scraping auto (IA)** : combine les deux en boucle —
|
||
extrait, capture, trouve et suit le lien suivant, répète — jusqu'à un
|
||
nombre de chapitres maximum fixé au démarrage (garde-fou demandé à
|
||
chaque lancement, **0 = illimité**, voir ci-dessous) ou jusqu'à ce
|
||
qu'aucune suite ne soit trouvée ; une détection de boucle (retour vers
|
||
une page déjà visitée) arrête aussi le processus automatiquement. Le
|
||
bouton devient "⏹ Arrêter le scraping" tant qu'il tourne. L'inférence
|
||
tourne sur un thread dédié (en streaming) pour ne jamais geler
|
||
l'interface, avec un **délai d'inactivité** (2 min sans le moindre
|
||
fragment de réponse généré) au-delà duquel le scraping s'arrête
|
||
proprement plutôt que de rester bloqué indéfiniment si l'IA ne répond
|
||
vraiment plus jamais (modèle figé, fichier corrompu...) — chaque
|
||
fragment généré réarme ce délai, donc une analyse simplement lente
|
||
(mais qui progresse encore) n'est jamais interrompue à tort, contrairement
|
||
à une limite sur la durée totale de l'appel. Les éventuelles erreurs
|
||
d'inférence n'affichent plus de boîte de dialogue bloquante pendant un
|
||
scraping automatique (juste un arrêt + un message dans le journal),
|
||
pour ne jamais donner l'impression que l'appli s'est figée faute d'un
|
||
clic sur une fenêtre passée inaperçue. Une **fenêtre de suivi** (non
|
||
modale, se réaffiche à chaque lancement) s'ouvre en même temps :
|
||
journal horodaté de chaque étape (lecture de la page,
|
||
extraction IA, recherche puis choix du lien suivant, arrêt et sa
|
||
raison...), barre de progression "chapitre X/N" (animation
|
||
indéterminée en mode illimité), et son propre bouton *"⏹ Arrêter le
|
||
scraping"*. La fermer (X) la masque seulement — le scraping continue
|
||
en arrière-plan si on ne l'arrête pas, la fenêtre se rouvrant au
|
||
prochain démarrage de scraping. **Chapitres multipages liés
|
||
automatiquement** : quand le modèle juge que la page suivante est la
|
||
suite du même chapitre (voir ci-dessus), l'extrait qui vient d'être
|
||
capturé est automatiquement marqué 🔗 (même mécanisme que la liaison
|
||
manuelle, voir *Lier plusieurs extraits en un seul chapitre*
|
||
ci-dessous) — les pages d'un même chapitre réparti sur plusieurs URLs
|
||
ressortent donc comme une seule section dans le PDF final, sans action
|
||
manuelle.
|
||
- **Détection des tables des matières** : avant de tenter d'en extraire
|
||
le contenu, chaque page est d'abord analysée pour savoir si c'est une
|
||
table des matières / liste de chapitres plutôt qu'un chapitre en soi
|
||
(page d'accueil d'une série, sommaire...). Si oui : aucun faux
|
||
"chapitre" n'est capturé depuis cette liste de liens, et la recherche
|
||
du lien suivant vise explicitement le **premier chapitre** de la liste
|
||
plutôt qu'une suite arbitraire parmi tous les chapitres listés — sans
|
||
cette détection, le scraping automatique tend à se perdre dès qu'il
|
||
démarre sur une page de ce type. S'applique aussi bien au scraping
|
||
automatique qu'au bouton manuel *"🤖 Lien suivant (IA)"*.
|
||
- **Limite "0" = illimité / détection automatique** : entrer 0 comme
|
||
nombre maximum de chapitres tente d'abord de déterminer le nombre
|
||
total de chapitres à partir du titre/texte de la page de départ (ex.
|
||
une table des matières, "Chapitre 3 sur 12"...) avant même de
|
||
commencer — trouvé, ce nombre devient la limite ; indéterminable, le
|
||
scraping tourne sans limite fixe et s'arrête de lui-même dès qu'aucune
|
||
suite n'est trouvée (voir garde-fous ci-dessus).
|
||
- **Prompt personnalisable** : le prompt système utilisé pour le choix du
|
||
lien suivant (Lien suivant (IA) et Scraping auto) est éditable dans
|
||
*"⚙️ Configurer l'IA locale"* — utile pour affiner le comportement sur
|
||
un site particulièrement atypique. Un bouton *"↺ Réinitialiser au
|
||
défaut"* restaure le texte intégré à l'appli. Laisser le champ tel
|
||
quel revient à utiliser ce prompt par défaut ; le modifier doit
|
||
conserver le format de réponse JSON décrit dans le texte
|
||
(`index`/`same_chapter`), sous peine de casser l'analyse de la réponse
|
||
du modèle.
|
||
|
||
Tous les boutons ci-dessus (projet, capture, aperçu, IA, export) sont
|
||
regroupés dans la **barre d'outils** en haut de la fenêtre ; le panneau
|
||
latéral ne garde que le tableau des extraits, ses actions de
|
||
réorganisation, et les liens suggérés sur la page courante.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
python -m venv .venv
|
||
.venv\Scripts\activate # Windows
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
## Lancement
|
||
|
||
```bash
|
||
python run.py
|
||
```
|
||
|
||
`llama-cpp-python` compile une extension native au moment de l'installation
|
||
(pas de wheel précompilé pour toutes les configurations) — l'installation
|
||
peut donc prendre plusieurs minutes selon la machine. Les fonctionnalités
|
||
IA restent optionnelles : sans modèle configuré, le reste de l'application
|
||
fonctionne normalement (les boutons IA affichent un message clair invitant
|
||
à configurer un modèle plutôt que d'échouer silencieusement).
|
||
|
||
### IA locale (optionnel)
|
||
|
||
1. Téléchargez un modèle au format **GGUF** (liens vérifiés ci-dessous —
|
||
le fichier n'est pas fourni avec l'appli, à télécharger une seule fois).
|
||
2. Dans PdfScrapper, ouvrez *"⚙️ Configurer l'IA locale"* et indiquez le
|
||
chemin du fichier `.gguf`.
|
||
3. Les boutons *"🤖 Extraire (IA)"*, *"🤖 Lien suivant (IA)"* et
|
||
*"🔁 Scraping automatique (IA)"* sont alors utilisables.
|
||
|
||
#### Modèles GGUF recommandés
|
||
|
||
Existence, nom de fichier exact et taille vérifiés au moment de la
|
||
rédaction. Prendre la variante **Q4_K_M** (bon compromis qualité/vitesse/
|
||
taille pour du CPU) sauf indication contraire — c'est le lien direct
|
||
donné ci-dessous.
|
||
|
||
| Modèle | Taille (Q4_K_M) | Pourquoi celui-ci | Téléchargement |
|
||
|---|---|---|---|
|
||
| **Qwen3-4B-Instruct-2507** (recommandé par défaut) | ~2,5 Go | Le plus récent de la liste, très bon suivi d'instructions (utile pour la sortie JSON du choix de lien) et bon en français, reste rapide sur CPU | [lien direct](https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q4_K_M.gguf) · [page du modèle](https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF) |
|
||
| **Qwen2.5-3B-Instruct** (le plus léger) | ~1,9 Go | Le plus petit/rapide de la liste, correct en français, suffisant pour dépanner sur une machine modeste | [lien direct](https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf) · [page du modèle](https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF) |
|
||
| **Mistral-7B-Instruct-v0.3** (meilleur français) | ~4,4 Go | Modèle Mistral AI (français), référence historique en qualité de français ; demande plus de RAM/CPU | [lien direct](https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF/resolve/main/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf) · [page du modèle](https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF) |
|
||
| **Llama-3.2-3B-Instruct** (alternative légère) | ~2,0 Go | Généraliste léger, bonne alternative si Qwen ne convient pas | [lien direct](https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_M.gguf) · [page du modèle](https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF) |
|
||
| **Qwen2.5-7B-Instruct** (meilleure qualité) | ~4,7 Go | Si ≥ 8 Go de RAM disponibles et qu'on veut la meilleure qualité d'extraction | [lien direct](https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf) · [page du modèle](https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF) |
|
||
|
||
Pour une machine avec peu de RAM, préférer les modèles 3-4B. Pour plus de
|
||
qualité (et si le CPU/la RAM suit), les 7B donnent de meilleurs résultats
|
||
sur l'extraction de contenu comme sur le choix du lien suivant.
|
||
|
||
## Structure du projet
|
||
|
||
```
|
||
pdfscrapper/
|
||
main.py # point d'entrée (QApplication)
|
||
main_window.py # fenêtre principale : navigateur + panneau latéral
|
||
link_detector.py # heuristiques JS de détection des liens "suivant"
|
||
block_picker.py # heuristique JS de surlignage/capture de bloc au survol
|
||
bridge.py # pont QWebChannel (JS -> Python) pour la capture de bloc
|
||
clip_table.py # tableau des extraits (panneau latéral) : colonnes Heure/Titre/Aperçu/Lien
|
||
pdf_style.py # dataclass PdfStyle (réglages de mise en forme du PDF)
|
||
pdf_builder.py # génération du PDF (reportlab) : style, fond, sommaire/liens
|
||
preview_window.py # fenêtre d'aperçu en direct (QtPdf), style et correction du texte
|
||
ai_config.py # dataclass AiConfig, persistée via QSettings
|
||
ai_settings_dialog.py # fenêtre de configuration du modèle IA local (.gguf)
|
||
ai_worker.py # exécution des appels IA sur un thread dédié (non bloquant)
|
||
local_ai.py # inférence llama-cpp-python : extraction + choix du lien suivant
|
||
auto_scrape_dialog.py # fenêtre de suivi du scraping auto (journal + progression)
|
||
models.py # dataclass Clip (un extrait capturé) + regroupement en chapitres
|
||
project_io.py # sauvegarde/chargement de projet (.pdfscrap) + pointeur de session
|
||
run.py # lance l'application
|
||
```
|
||
|
||
## Pistes d'évolution
|
||
|
||
- Réglage des motifs de détection de liens depuis l'interface.
|
||
- Export additionnel en `.txt` / `.epub`.
|