Construire un détecteur de technologies web avec Scrapy¶
Ce projet a commencé de façon très simple : éviter d'ouvrir manuellement 15 onglets pour comprendre la stack d'une entreprise.
C'est probablement le projet qui m'a le plus appris sur le scraping réel, les signaux techniques, les limites du web moderne, et la différence entre un script qui marche et un système qui tient dans le temps.
Le problème de départ¶
Dans mes workflows de prospection B2B, je passais énormément de temps à faire toujours la même chose : ouvrir un site, inspecter le code source, regarder les scripts chargés, chercher des tags marketing, comprendre rapidement le niveau de maturité technique.
Très vite, j'ai remarqué qu'un site web raconte énormément de choses sur une entreprise : pas seulement quelle techno elle utilise, mais aussi comment elle travaille, sa culture produit, parfois même son niveau de dette technique.
| Signal détecté | Ce que ça raconte souvent |
|---|---|
| Stack e-commerce moderne | |
| Logique inbound / CRM | |
| Culture data | |
| Tracking devenu difficile à maintenir | |
| Absence de CMP | Faible maturité RGPD |
| Stack DTC assez classique |
Au début, je faisais ça entièrement à la main. Et honnêtement, c'était vite infernal.
Pourquoi pas les outils existants¶
J'ai évidemment regardé les outils existants :
| Outil | Problème principal | |
|---|---|---|
| BuiltWith | Fermé, pas d'API exploitable proprement, cher à grande échelle | |
| Wappalyzer | Orienté extension navigateur, peu adapté à l'automatisation | |
| Klaviyo & co | APIs propriétaires, résultats non enrichissables dans un workflow |
Le vrai blocage
Les outils existants étaient soit :
- Trop orientés "extension navigateur"
- Trop fermés pour une intégration propre dans des workflows
- Trop chers à grande échelle
- Impossibles à enrichir ou à modifier rapidement
- Peu ou pas exploitables en automatisation
Et surtout :
je voulais comprendre exactement comment la détection fonctionnait, pas juste consommer un résultat.
Je voulais pouvoir ajouter mes propres signatures, modifier les règles, comprendre les faux positifs, et brancher le tout dans d'autres systèmes.
Les débuts : tout tournait sur mon PC Windows¶
C'est important de le préciser, parce que beaucoup d'articles techniques donnent l'impression que tout commence avec Kubernetes, des workers distribués et une architecture cloud propre.
Dans mon cas :
C'était largement suffisant pour démarrer.
Le but n'était pas de crawler Internet. Je voulais surtout comprendre jusqu'où je pouvais pousser un moteur de détection relativement simple.
La première version était ridicule¶
Pendant environ 15 minutes, j'ai trouvé ça génial.
Puis les problèmes ont commencé : scripts injectés dynamiquement, CDNs mutualisés, technos détectables uniquement via cookies, frameworks JS qui renvoient un HTML presque vide, faux positifs partout.
C'est là que le projet a changé de nature.
Le changement de perspective
Un site web n'est pas une page HTML.
C'est un ensemble de signaux faibles.
À partir de là, je n'essayais plus de répondre à :
mais plutôt :
Ça a complètement changé l'architecture du système.
Architecture globale¶
Le pipeline a progressivement évolué vers ça :
flowchart TD
A[Liste de domaines] --> B[Spider Scrapy]
B --> C[HTML] & D[Scripts] & E[Headers] & F[Cookies] & G[Meta tags]
C & D & E & F & G --> H[Moteur de signatures]
H --> I[Scoring]
I --> J[Technologies détectées]
J --> K[Export JSON] & L[Supabase] & M[n8n workflows]
Point important : Scrapy ne détecte rien. Il collecte uniquement des signaux. La logique métier est ailleurs.
Pourquoi Scrapy¶
Performances natives
Même sur une machine locale classique, Scrapy tient des volumes très corrects : plusieurs centaines de pages/minute sans optimisation agressive, tant que le rendu JS reste désactivé.
Tout est déjà inclus : retries, throttling, concurrence, pipelines, middlewares.
Structure naturelle du projet
Très vite, le repo a commencé à ressembler à quelque chose de propre :
Scrapy force presque naturellement à structurer le projet correctement.
| Outil | Pourquoi écarté à ce stade |
|---|---|
| Playwright seul | Trop lourd pour démarrer, coût trop élevé |
| Selenium | Lent, verbeux, peu adapté au volume |
| requests + BeautifulSoup | Bien pour du one-shot, pas pour de l'industriel |
| Crawl4AI | Trop récent au moment du projet |
Certains ont fini par entrer dans le pipeline, mais pas au départ.
Le problème du JavaScript moderne¶
C'est probablement la partie qui m'a le plus frustré.
Au début, je voulais absolument éviter Playwright : lourd, gourmand, complexifie tout.
Le problème, c'est qu'une partie du web moderne renvoie quasiment un shell vide. Sur certains sites Next.js, le crawler récupérait littéralement :
Et pratiquement tout le reste arrivait après hydration.
Conséquence directe
Certaines technos devenaient totalement invisibles côté Scrapy pur.
Le compromis : un fallback Playwright conditionnel. Scrapy d'abord, Playwright uniquement si nécessaire.
flowchart LR
A[Scrapy] --> B{Détection suffisante ?}
B -->|Oui| C[Fin]
B -->|Non| D[Fallback Playwright]
D --> E[DOM rendu]
E --> F[Analyse complémentaire]
from playwright.async_api import async_playwright
async def render_page(url):
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url)
html = await page.content()
await browser.close()
return html
Le plus difficile n'était pas ce code : c'était de décider quand déclencher le fallback, sur quels domaines, et avec quels timeouts.
Ce que ça change concrètement :
| Mode | Pages/min (approx.) | RAM | Scalabilité locale |
|---|---|---|---|
| Scrapy seul | ~300-400 | Faible | Bonne |
| Scrapy + Playwright | ~20-50 | Élevée | Difficile |
C'est probablement l'arbitrage technique le plus important du projet.
La vraie difficulté : les signatures¶
Le scraping n'était finalement pas le problème principal. Le vrai sujet :
comment maintenir un moteur de signatures fiable.
Résultat : faux positifs en masse, détections fragiles, aucune notion de confiance, impossible à maintenir.
Le vrai progrès
Je ne détectais plus "une occurrence".
Je cumulais des preuves.
Le scoring réel¶
J'ai testé des approches plus statistiques, plus automatiques. Elles rendaient surtout le système plus difficile à debugger.
Aujourd'hui, le scoring reste volontairement lisible :
def compute_confidence(matches):
score = 0
for match in matches:
if match["type"] == "cookie":
score += 0.45 # (1)!
elif match["type"] == "script":
score += 0.30 # (2)!
elif match["type"] == "html":
score += 0.10 # (3)!
return min(score, 1.0) # (4)!
- Un cookie est très fiable : il faut l'avoir explicitement installé sur le site.
- Un script CDN est fiable mais peut être mutualisé (Cloudflare, jsdelivr…).
- Du HTML brut peut contenir le mot par hasard dans du contenu éditorial.
- On plafonne à 1.0 : au-delà de plusieurs signaux concordants, c'est quasi certain.
Ce n'est pas "intelligent". Mais c'est stable, lisible, et facile à faire évoluer.
Exemple réel de sortie¶
{
"domain": "example-store.com",
"technologies": [
{
"name": "Shopify",
"confidence": 0.97,
"signals": ["cdn.shopify.com", "_shopify_y", "shopify-checkout"]
},
{
"name": "Klaviyo",
"confidence": 0.91,
"signals": ["static.klaviyo.com", "__kla_id"]
}
],
"meta": {
"crawl_time_ms": 1840,
"javascript_rendered": false,
"redirects": 1
}
}
C'est là que le projet sort du simple "détecteur". À partir de ces signaux, on peut :
- scorer des entreprises selon leur maturité tech,
- enrichir des workflows IA,
- construire des ICP,
- détecter des changements de stack dans le temps,
- générer des signaux commerciaux.
Ce qui a cassé plusieurs fois¶
Les données web sont extrêmement sales.
Redirects absurdes, certificats invalides, timeouts, protections anti-bot, HTML cassés, encodages étranges : on passe beaucoup plus de temps à gérer les cas limites qu'à écrire la logique métier.

Les faux positifs ont été un enfer
Exemple classique : le mot segment peut vouloir dire Segment CDP, un mot dans du contenu, ou un slug d'URL.
Même problème avec certains CDNs mutualisés (Cloudflare, jsdelivr, Google APIs).
Détecter moins de technos mais avec des signaux fiables est largement préférable.
Ce qui m'a surpris après plusieurs milliers de domaines¶
Observations
- Énormément de sites sont cassés ou à moitié fonctionnels
- Beaucoup plus de WordPress que prévu, dans tous les secteurs
- Les stacks marketing sont très répétitives par secteur d'activité
- Certains sites chargent des dizaines de scripts morts ou hérités
- Le web moderne est beaucoup plus fragile que je ne l'imaginais
Ce que je referais autrement¶
Ce que j'avais :
Ce que je ferais :
/signatures
/crm
hubspot.json
salesforce.json
/marketing
klaviyo.json
mailchimp.json
/analytics
segment.json
gtm.json
Avec validation, tests, scoring et versionning propre dès le départ.
J'ai longtemps testé manuellement sur des domaines au hasard.
Erreur classique.
Ce que je construirais aujourd'hui :
- une liste de domaines de référence avec stack connue,
- des snapshots HTML,
- des tests de régression automatisés.
Les signatures vieillissent extrêmement vite. Une techno peut changer de CDN, de script, ou de domaine du jour au lendemain.
Ce qui reste encore bricolé¶
Le rendu JS coûte cher
Playwright améliore considérablement les résultats. Mais RAM, CPU et temps de crawl explosent vite. Aujourd'hui, le fallback reste difficile à scaler proprement.
Certaines technos restent invisibles
Le système voit surtout ce qui fuite côté frontend.
Tout ce qui est backend, infra, outils internes ou APIs reste beaucoup plus difficile à détecter. C'est une limite structurelle du scraping côté HTML.
Ce que ce projet m'a réellement appris¶
Je pensais construire un détecteur de technos. En réalité, j'ai surtout construit un système de collecte de signaux web.
C'est très différent.
Le projet m'a surtout appris :
- la robustesse d'abord,
- les arbitrages entre précision et coût,
- les limites réelles du scraping,
- et l'importance de garder des systèmes simples et lisibles.
Un pipeline compréhensible et stable vaut souvent mieux qu'un système "intelligent" impossible à maintenir.
Ce que j'ai envie d'explorer ensuite
- Scoring probabiliste basé sur l'historique
- Évolution des stacks dans le temps (monitoring continu)
- Clustering de technologies par secteur
- Embeddings sur les signaux détectés
- Détection de changements de maturité technique