Files
Priceflow/app/routers/search.py
T
Claude 68db9ad15e feat: Improve Search & Comparateur with persistent browser ScraperService pattern
- Created improved_search_service.py using persistent browser pattern
- Persistent browser connection with auto-reconnect capability
- Proper popup/cookie handling across all sites
- Price extraction with validation (reject unrealistic prices)
- Stock status detection
- Concurrent scraping with semaphore limits (2 sites, 2 products)
- Reuses browser context for better session management
- Screenshot capture for product images
- Updated search router to use improved service
- Added initialization/shutdown in main.py lifespan

Benefits:
✓ More reliable scraping with persistent connections
✓ Better anti-detection (consistent sessions)
✓ Improved price accuracy with validation
✓ Faster performance (reuses browser contexts)
✓ Auto-recovery from connection failures
2025-11-30 10:51:22 +00:00

90 lines
2.5 KiB
Python

"""
API Router pour la recherche de produits
Utilise Server-Sent Events (SSE) pour le streaming des résultats
"""
import json
import logging
from fastapi import APIRouter, Depends, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from app.database import get_db
from app.services import improved_search_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/search", tags=["search"])
@router.get("")
async def search_products(
q: str = Query(..., min_length=1, description="Terme de recherche"),
sites: str | None = Query(None, description="IDs des sites, séparés par virgule"),
max_results: int = Query(20, ge=1, le=50, description="Nombre max de résultats"),
db: Session = Depends(get_db),
):
"""
Recherche des produits sur les sites configurés.
Retourne un flux SSE avec les résultats au fur et à mesure.
Format des événements SSE:
- event: progress
- data: {"status": "...", "total": N, "completed": N, "results": [...]}
"""
# Parser les IDs de sites
site_ids = None
if sites:
try:
site_ids = [int(s.strip()) for s in sites.split(",") if s.strip()]
except ValueError:
site_ids = None
async def generate():
try:
async for progress in improved_search_service.search_products(
query=q,
db=db,
site_ids=site_ids,
max_results=max_results,
):
# Format SSE
data = progress.model_dump_json()
yield f"event: progress\ndata: {data}\n\n"
except Exception as e:
logger.error(f"Erreur recherche: {e}")
error_data = json.dumps({
"status": "error",
"total": 0,
"completed": 0,
"message": str(e),
"results": [],
})
yield f"event: progress\ndata: {error_data}\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)
@router.get("/health")
async def search_health():
"""Vérifie que le service de recherche est opérationnel"""
from app.services import searxng_service
searxng_ok = await searxng_service.health_check()
return {
"status": "ok" if searxng_ok else "degraded",
"searxng": "ok" if searxng_ok else "unavailable",
}