Vue normale

Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.
À partir d’avant-hierFlux principal

Scrapling - Le scraper Python qui se répare tout seul

Par : Korben ✨
28 avril 2026 à 08:53

Le scraping web, c'est un combat permanent contre les sites qui changent leur HTML toutes les deux semaines. Vous vous emmerdez à coder vos sélecteurs CSS, ça marche pendant un mois, puis le site refait son design et hop, votre script s'éteint en silence. C'est pourquoi Karim Shoair (alias D4Vinci sur GitHub) a sorti Scrapling, un framework Python qui s'adapte tout seul quand le DOM bouge.

La clé c'est adaptive=True sur n'importe quel sélecteur. Vous lui dites "je cherchais .product", Scrapling sauvegarde alors la signature de l'élément (texte, attributs, position dans l'arbre), et la prochaine fois que le site a renommé sa classe, il retrouve l'élément via similarité.

Concrètement ça donne ça :

from scrapling.fetchers import StealthyFetcher
StealthyFetcher.adaptive = True
page = StealthyFetcher.fetch('https://example.com', headless=True)
product = page.css_first('.product', adaptive=True) # Retrouve l'élément même si la classe a changé

Le truc marche grâce à un algo de similarité maison qui compare la structure DOM autour de l'élément. L'auteur lui-même a écrit un long post Medium intitulé " Creating self-healing spiders with Scrapling in Python without AI ", et ça résume bien la philosophie : pas de modèle IA mais juste des heuristiques solides !

La doc précise que adaptive=True ne sauvegarde que le premier élément de la sélection. Du coup si vous récupérez 50 produits d'un coup avec .css('.product'), seul le premier sera adapté. Faudra donc soit utiliser css_first comme dans l'exemple, soit boucler manuellement et appeler adaptive sur chaque élément. C'est bon à savoir...

Y'a également 3 fetchers selon le besoin. Fetcher pour les requêtes HTTP rapides avec spoofing TLS, StealthyFetcher qui passe Cloudflare Turnstile via un navigateur furtif (Camoufox sous le capot), et DynamicFetcher qui lance un Chromium ou un Chrome via Playwright pour les sites lourds en JS. Du coup vous pouvez démarrer léger en HTTP et basculer vers un navigateur uniquement quand un site bloque, sans réécrire votre code.

Côté perfs, le README annonce du lourd : 2 ms pour extraire 5000 éléments contre 1584 ms pour BeautifulSoup avec lxml. Sauf que Parsel et Scrapy font aussi 2 ms. Donc le gain vient du moteur lxml utilisé en direct, ce qui veut dire que si vous étiez déjà sur Scrapy, vous ne gagnerez pas en vitesse brute. Mais si vous traînez encore du BS4 partout, le saut sera énorme !

Sur le terrain anti-bot, ça se compare bien à Botasaurus dont je vous avais parlé. La différence c'est que Scrapling embarque un ProxyRotator natif et propose un blocage d'ads/trackers (~3500 domaines) activable via block_ads=True ou automatique en mode MCP, ce qui simplifie la vie quand vous tournez sur un serveur (où les IPs des datacenter se font régulièrement filtrer). Botasaurus, lui, vous laisse gérer la rotation à la main.

Détail sympa pour les bidouilleurs : y'a également un serveur MCP intégré (pip install "scrapling[ai]"). Du coup Claude ou Cursor peuvent piloter Scrapling directement pour extraire des données, en réduisant la consommation de tokens car l'IA ne voit pas tout le HTML brut, juste ce qui est extrait. Pour les agents qui scrappent en boucle, c'est cool.

Notez que les sponsors Platinum du projet sont tous des fournisseurs de proxies (DataImpulse, BirdProxies, Evomi, etc.). C'est logique vu l'usage du framework, mais gardez en tête que pour bypasser un Cloudflare sérieux à grande échelle, vous aurez besoin de proxies résidentiels payants, donc d'eux. L'outil est gratuit, mais le contournement industriel ne l'est pas.

Pour installer, c'est pip install "scrapling[fetchers]" puis scrapling install pour récupérer les binaires navigateur. Une image Docker existe aussi (pyd4vinci/scrapling) et y'a même un shell interactif (scrapling shell) pour debugger vos sélecteurs en live.

Bref, c'est carrément pas mal pour ceux qui scrapent régulièrement. Alors si BS4 vous fait pleurer, allez voir par ici .

Et merci à Letsar pour le lien !

tar-vfs-index - Monter du .tar.gz dans le browser sans l'extraire

Par : Korben ✨
25 avril 2026 à 07:22

Distribuer des paquets binaires en WebAssembly, c'est galère. Vous téléchargez le .tar.gz, vous le gunzippez, vous l'extrayez en mémoire... et ça rame sévèrement !! Mais youpi, Jeroen Ooms (qui contribue à webR et bosse chez ROpenSci) vient de publier tar-vfs-index , un petit npm package qui casse cette malédiction des enfers en sautant carrément l'étape extraction.

L'astuce est toute bête ! Au lieu d'extraire l'archive, on génère un fichier d'index qui liste la taille et l'offset de chaque fichier dans le tar. Du coup le navigateur n'a plus qu'à monter le blob du tar comme un système de fichiers virtuel, et chaque lecture devient alors un simple slice du blob à la bonne position. Pas d'extraction donc, mais juste du slicing à la demande !

Sous le capot, ça repose sur 3 propriétés alignées. 1/ le format tar est un layout plat : une suite de headers de 512 octets suivis des données du fichier, le tout contigu et adressable à l'octet près. 2/ Emscripten propose un backend filesystem appelé WORKERFS, prévu pour servir les lectures d'un Blob sans le copier dans le heap WASM. Et 3/ les navigateurs ont une API native, DecompressionStream , qui gunzippe efficacement pendant le téléchargement.

Concrètement, vous installez le truc avec npm install tar-vfs-index (y'a aucune dépendance externe) puis vous lancez npx tar-vfs-index archive.tar.gz. Et hop, le package vous sort un JSON dans ce genre :

{
 "files": [
 { "filename": "mypackage/DESCRIPTION", "start": 512, "end": 548 },
 { "filename": "mypackage/R/code.R", "start": 1536, "end": 1563 }
 ],
 "remote_package_size": 3072
}

Les valeurs start et end sont tout simplement les offsets dans les données tar décompressées, et remote_package_size indique la taille totale (pour que WORKERFS sache combien préallouer). Ensuite, côté navigateur, ça donne du JavaScript du genre :

const [metaRes, dataRes] = await Promise.all([
 fetch('archive.tar.gz.json'),
 fetch('archive.tar.gz'),
]);
const metadata = await metaRes.json();
const blob = await new Response(
 dataRes.body.pipeThrough(new DecompressionStream('gzip'))
).blob();
FS.mkdir('/pkg');
FS.mount(WORKERFS, { packages: [{ metadata, blob }] }, '/pkg');

Le cas d'usage qui a motivé tout ça, c'est webR , le portage du langage R en WebAssembly. Les paquets R sont distribués en .tar.gz et avant cette astuce, charger un paquet voulait dire copier des trucs partout. Maintenant le temps et la mémoire de chargement reviennent à peu près au coût du téléchargement et du gunzip, ce qui est nettement plus léger qu'une extraction complète en RAM. Et ça marche pour n'importe quel bundle distribué en tar.gz : assets de jeu, datasets pour du machine learning, runtimes Python via Pyodide, bref tout ce qui ressemble à une archive lourde côté navigateur !

Y'a également un mode --append qui colle l'index directement à la fin du tarball (le format tar autorise ce genre de bidouille). Ça donne donc un .tar.gz autonome qu'un loader peut monter sans aller chercher un fichier de métadonnées séparé !

Bref, c'est plutôt joli comme façon de faire et ça vaut le coup d'œil si vous trimballez du tar.gz dans du WebAssembly.

Source

❌
❌