mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-20 05:38:12 +02:00
feat: Add Amazon France search page with Crawl4AI anti-detection
- Created Amazon scraper service with advanced anti-bot techniques: * User-Agent rotation from realistic pool * Complete browser headers (Accept, Accept-Language, etc.) * Proxy rotation (10 residential proxies) * Random delays (1.5-4s) to mimic human behavior * Crawl4AI browser fingerprint randomization * NetworkIdle waiting for complete page load * Cookie acceptance automation - Added Amazon search API endpoint with SSE streaming * Real-time progress updates * Proper error handling * Health check endpoint - Created dedicated Amazon France frontend page: * Modern UI with product cards * Rating display (stars + review count) * Price formatting with discount badges * Prime badge support * Stock status indicators * Sponsored product labels * Direct Amazon links - Removed store list (ENSEIGNES_DATA cleared) * Migration from discount stores to Amazon France * Catalog system kept for future use - Updated navigation: * Added "Amazon France" menu item with ShoppingBag icon * Positioned between Search and Compare * Available on desktop and mobile Technical stack: - Backend: Crawl4AI + BeautifulSoup for scraping - Frontend: React + Shadcn UI components - API: FastAPI with SSE streaming
This commit is contained in:
7 files changed
+887
-77
No files matched your search
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
API Router pour la recherche Amazon France
|
||||
Utilise Server-Sent Events (SSE) pour le streaming des résultats
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.services.amazon_scraper import scrape_amazon_search, AmazonProduct
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/amazon", tags=["amazon"])
|
||||
|
||||
|
||||
class AmazonSearchProgress(BaseModel):
|
||||
"""Progress model for SSE streaming"""
|
||||
status: str # 'searching', 'completed', 'error'
|
||||
total: int
|
||||
completed: int
|
||||
message: str
|
||||
results: list[dict]
|
||||
|
||||
|
||||
@router.get("/search")
|
||||
async def search_amazon(
|
||||
q: str = Query(..., min_length=1, description="Terme de recherche"),
|
||||
max_results: int = Query(20, ge=1, le=50, description="Nombre max de résultats"),
|
||||
):
|
||||
"""
|
||||
Recherche de produits sur Amazon France.
|
||||
|
||||
Retourne un flux SSE avec les résultats.
|
||||
|
||||
Format des événements SSE:
|
||||
- event: progress
|
||||
- data: {"status": "...", "total": 1, "completed": 0/1, "results": [...]}
|
||||
"""
|
||||
|
||||
async def generate() -> AsyncGenerator[str, None]:
|
||||
try:
|
||||
# Initial progress
|
||||
progress = AmazonSearchProgress(
|
||||
status="searching",
|
||||
total=1,
|
||||
completed=0,
|
||||
message=f"Recherche sur Amazon France: '{q}'...",
|
||||
results=[]
|
||||
)
|
||||
yield f"event: progress\ndata: {progress.model_dump_json()}\n\n"
|
||||
|
||||
# Execute search
|
||||
logger.info(f"Starting Amazon search for: {q}")
|
||||
products = await scrape_amazon_search(q, max_results=max_results)
|
||||
|
||||
# Convert to dict
|
||||
results = [p.model_dump() for p in products]
|
||||
|
||||
# Final progress
|
||||
if results:
|
||||
progress = AmazonSearchProgress(
|
||||
status="completed",
|
||||
total=1,
|
||||
completed=1,
|
||||
message=f"✅ {len(results)} produits trouvés",
|
||||
results=results
|
||||
)
|
||||
else:
|
||||
progress = AmazonSearchProgress(
|
||||
status="completed",
|
||||
total=1,
|
||||
completed=1,
|
||||
message="Aucun produit trouvé",
|
||||
results=[]
|
||||
)
|
||||
|
||||
yield f"event: progress\ndata: {progress.model_dump_json()}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during Amazon search: {e}", exc_info=True)
|
||||
error_progress = AmazonSearchProgress(
|
||||
status="error",
|
||||
total=1,
|
||||
completed=1,
|
||||
message=f"Erreur: {str(e)}",
|
||||
results=[]
|
||||
)
|
||||
yield f"event: progress\ndata: {error_progress.model_dump_json()}\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 amazon_health():
|
||||
"""Vérifie que le service Amazon est opérationnel"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "Amazon France Scraper",
|
||||
"anti_detection": "enabled",
|
||||
"features": [
|
||||
"User-Agent rotation",
|
||||
"Proxy rotation (10 proxies)",
|
||||
"Random delays",
|
||||
"Realistic headers",
|
||||
"Crawl4AI anti-detection"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user