Trouve-KA — Cherche le Québec.
Moteur de recherche web indépendant, Québec-first. Son propre crawler, son propre index, son propre ranking, son API et son application web publique. Pas un métamoteur : aucune dépendance à Google, Bing ou Brave pour les résultats.
Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.
En production : https://www.trouve-ka.com — un moteur de recherche par Groupe KA.
Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai
Aperçu
![]() |
![]() |
| Accueil — la boîte de recherche, le compteur vivant, le flux « KA bot scrappe en ce moment » au footer | Résultats — pastilles éditoriales (◆ gradué selon le score Québec), vignettes og:image, snippets surlignés, 21 ms |
![]() |
![]() |
| Onglet Images — galerie des pages avec image représentative (hotlink + attribution, jamais de crawl d'images) | /status — métriques publiques réelles, auto-refresh 10 s |
Métriques réelles (2026-08-13, jour 1 — ~6 h après le premier crawl)
| Métrique | Valeur |
|---|---|
| Pages indexées et cherchables | 59 073 (+~35 000/heure) |
| Domaines québécois découverts | 7 898 (à partir de 64 seeds) |
| URLs découvertes en attente (frontier) | 433 999 |
| Pages fetchées / heure | 76 198 |
| Latence de recherche (mesurée en prod) | 20–95 ms (cible p50 < 100 ms ✓) |
| Délai fetch → cherchable | ~2–4 secondes |
| Nodes de crawl | 3 (M2M32 ×5 workers Docker, M2M32b, M2M32c) |
| Taille de l'index | ~14 Ko/document (791 Mo à 55 k docs) |
| Tests unitaires | 57 ✓ (canonicalisation, SSRF, robots, scoring Québec, pièges, fetcher, ranking) |
Chaque chiffre ci-dessus vient des vraies tables (crawl_attempts, frontier_items) et de
l'index réel — aucun compteur simulé (règle § fake du projet).
Ce que c'est
Crawler → Frontier → Fetcher → Parser → Classification Québec
→ Déduplication → Indexer → Index → Ranking → API → Web AppLe crawler découvre le web québécois à partir de seeds à forte autorité (gouvernement,
municipalités, universités, médias), suit les liens sortants, juge la pertinence
québécoise de chaque page (page_quebec_score et domain_quebec_score),
et indexe immédiatement : une page fetchée est cherchable en ~2-4 secondes,
pendant que le frontier continue de grandir. L'enrichissement (autorité, entités,
embeddings) arrive après, en asynchrone, sans jamais bloquer.
Détails : docs/architecture.md (diagrammes Mermaid) et docs/decisions.md (pourquoi OpenSearch, pourquoi Postgres comme frontier, etc.).
Stack
| Couche | Choix |
|---|---|
| Web | Next.js 15, TypeScript, React 19, Tailwind (composants style shadcn/ui) |
| API | FastAPI (Python 3.12) |
| Pipeline | Python 3.12 async — httpx, selectolax, Protego |
| BD relationnelle | PostgreSQL 16 (frontier, domaines, documents, graphe de liens, analytics) |
| Coordination | Redis 7 (politesse par hôte, pause, Redis Streams pour l'enrichissement) |
| Recherche | OpenSearch 2.17 (BM25 FR/EN, synonymes bilingues, function_score Québec-first) |
| Déploiement | Docker Compose sur m2m32 + ngrok (www.trouve-ka.com) |
Layout du monorepo
apps/web # moteur public + dashboard /admin (Next.js)
apps/api # FastAPI (trouveka.api)
services/ # crawler, frontier, parser, classifier, indexer, ranking, scheduler, enrichment
packages/ # config, database, logging, queue, search-core, shared, types
infrastructure/ # docker/, migrations/, monitoring/, deployment/ (m2m32 + ngrok)
scripts/ # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py
tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, rankingLe backend Python est un seul package namespace trouveka.* mappé sur ce layout
(voir pyproject.toml).
Démarrage rapide (dev)
git clone … && cd trouve-ka
cp .env.example .env
docker compose up -d postgres redis opensearch # infra
uv venv --python 3.12 .venv && uv pip install -e ".[dev]" --python .venv/bin/python
pnpm install
pnpm crawl:seed # migrations + 60+ seeds québécoises
bash scripts/start-crawler/start.sh & # le crawl démarre
.venv/bin/uvicorn trouveka.api.main:app --port 8080 &
pnpm dev # → http://localhost:3000
# → des résultats apparaissent en quelques secondesPorts occupés sur la machine? Surcharger dans .env : PG_PORT, REDIS_PORT,
SEARCH_PORT, API_PORT (et les URLs correspondantes).
Stack complet en containers : docker compose up -d --build (le service migrate
applique les migrations, crawler-worker se scale avec
docker compose up -d --scale crawler-worker=3).
Comment ça marche
Crawl et politesse
- Identité assumée : UA
Mozilla/5.0 (compatible; TrouveKABot/0.1; +https://www.trouve-ka.com/trouveka-bot), page publique /trouveka-bot, pas de stealth. - robots.txt parsé avec Protego, cache 24 h en base;
noindex/nofollow/X-Robots-Tagrespectés. - Politesse par origine : verrou Redis par hôte (défaut 2 s entre requêtes,
Crawl-delayrespecté), quel que soit le nombre de workers. - Sécurité : garde SSRF (IP privées/loopback/métadonnées cloud bloquées, revalidée à chaque redirection), limites par réponse (3 Mo, 5 redirections, timeout 20 s), détection de pièges (session IDs, calendriers infinis, facettes explosives, pagination sans fin).
Indexation incrémentale
Le worker exécute fetch→parse→score→index inline : refresh_interval: 1s côté
OpenSearch → cherchable en secondes. Détection de changement par hash de contenu +
ETag/If-Modified-Since; recrawl adaptatif (inchangé → intervalle ×2, volatil → ÷2).
Détection Québec
Deux scores distincts (page et domaine) calculés à partir de : TLD (.qc.ca, .quebec),
gazetteer de toponymes (pondération réduite pour les ambigus type Laval/Hull), codes
postaux G/H/J, indicatifs (418/514/438/…), organisations connues (Hydro-Québec, RAMQ,
UQAM…), mentions structurées de la province (JSON-LD), langue française (indice, pas preuve).
Déterministe et gratuit — aucun LLM dans le chemin chaud (§12).
Ranking
function_score OpenSearch : BM25 bilingue (analyzers FR + EN, synonymes
thermopompe↔heat pump à la recherche) + scores Québec + autorité de domaine
(inlinks pondérés) + fraîcheur + boost de localité (« plombier Gatineau » → documents
avec preuve géographique locations). Chaque composant est optionnel; BM25 tient seul.
Évaluation mesurable : python3 scripts/eval/run-eval.py --api http://localhost:8080
(dataset dans scripts/eval/ranking-eval.yaml).
API
GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=GET /api/status— compteurs publics (pages, domaines, débit, état du crawler)POST /api/submit {"url": …}— soumettre un site québécois (soumission ≠ inclusion)GET|POST /api/admin/*— protégé par headerX-Admin-Token: overview, flux live, pause/reprise, seeds, recrawl, blocage de domaine, inspection du frontier
Tests
.venv/bin/python -m pytest tests/ # 54 tests : URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking
python3 scripts/check-headers.py # header auteur obligatoire dans chaque fichier source (CI)
bash scripts/health-check/check.sh # santé du stackDéploiement m2m32 + ngrok (www.trouve-ka.com)
Prérequis sur m2m32 : colima + docker + docker-compose (brew), ngrok authentifié,
domaine www.trouve-ka.com réservé dans le compte ngrok.
pnpm deploy:m2m32 # = bash infrastructure/deployment/deploy-m2m32.shLe script : rsync du monorepo → docker compose up -d --build → migrations + seeds
(idempotent) → tunnel ngrok http --url=www.trouve-ka.com 3000 → health-check.
Le web proxifie /api/* vers l'API interne : un seul port exposé, pas d'URL absolues
côté client, cookies/CORS sans surprise derrière le tunnel.
Opérations courantes sur le node :
ssh M2M32 'cd trouve-ka && docker compose logs -f crawler-worker' # crawl en direct
ssh M2M32 'cd trouve-ka && docker compose up -d --scale crawler-worker=5'Crawl distribué (M2M32b, M2M32c)
Des workers satellites tournent en natif sur d'autres nodes du cluster et se
coordonnent sans orchestrateur via le frontier Postgres (FOR UPDATE SKIP LOCKED)
et les verrous de politesse Redis (tous nodes confondus : jamais plus d'une requête
par site par seconde). Communication par l'Ethernet LAN interne (192.168.2.x).
# Sur un node satellite (uv + python 3.12, .env pointé sur le node central) :
cd ~/trouve-ka && pm2 start .venv/bin/python --name trouveka-crawler \
--interpreter none -- -m trouveka.crawler.worker⚠️ macOS Sequoia bloque l'accès « réseau local » des processus launchd : lancer les workers satellites via pm2/nohup depuis une session SSH, pas via LaunchAgent/Daemon.
Observabilité
Logs JSON structurés par service, métriques réelles via /api/status et
/api/admin/overview, dashboard /admin (files, débits, distribution HTTP, latences
p50/p95, flux live). Voir infrastructure/monitoring/README.md.
Règles du dépôt
- Header auteur obligatoire dans chaque fichier source (§0.1) — vérifié par
scripts/check-headers.py. - Aucune donnée factice : pas de compteurs simulés, pas de résultats hard-codés;
les fixtures vivent dans
tests/uniquement. - Provenance préservée : URL originale, canonique, timestamp de crawl, domaine source. Trouve-KA renvoie vers les éditeurs originaux.



