mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-13 18:26:20 +02:00
- 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
90 lines
2.5 KiB
Python
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",
|
|
}
|