mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-24 15:48:11 +02:00
Merge pull request #30 from R0m1k3/claude/design-product-search-01Ea9dWZqZpuKFwBwVmp26PG
fix: Improve domain matching and add discover-all endpoint
This commit is contained in:
2 files changed
+80
-8
No files matched your search
@@ -151,3 +151,40 @@ async def discover_site_search_url(
|
|||||||
"success": False,
|
"success": False,
|
||||||
"message": "Impossible de découvrir l'URL de recherche automatiquement",
|
"message": "Impossible de découvrir l'URL de recherche automatiquement",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/discover-all", response_model=dict)
|
||||||
|
async def discover_all_missing_search_urls(
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Lance la découverte d'URL de recherche pour tous les sites qui n'en ont pas.
|
||||||
|
La découverte se fait en arrière-plan.
|
||||||
|
"""
|
||||||
|
sites = search_service.get_all_sites(db)
|
||||||
|
sites_to_discover = [s for s in sites if not s.search_url]
|
||||||
|
|
||||||
|
if not sites_to_discover:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Tous les sites ont déjà une URL de recherche configurée",
|
||||||
|
"sites_queued": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_url = os.getenv("DATABASE_URL", "")
|
||||||
|
|
||||||
|
for site in sites_to_discover:
|
||||||
|
background_tasks.add_task(
|
||||||
|
_discover_and_update_site,
|
||||||
|
site.id,
|
||||||
|
site.domain,
|
||||||
|
db_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Découverte lancée pour {len(sites_to_discover)} sites",
|
||||||
|
"sites_queued": len(sites_to_discover),
|
||||||
|
"sites": [{"id": s.id, "domain": s.domain} for s in sites_to_discover],
|
||||||
|
}
|
||||||
@@ -16,6 +16,13 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
BROWSERLESS_URL = os.getenv("BROWSERLESS_URL", "ws://browserless:3000")
|
BROWSERLESS_URL = os.getenv("BROWSERLESS_URL", "ws://browserless:3000")
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
MIN_TITLE_LENGTH = 3
|
||||||
|
USER_AGENT = (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||||
|
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchResult:
|
class SearchResult:
|
||||||
"""Résultat de recherche"""
|
"""Résultat de recherche"""
|
||||||
@@ -157,7 +164,7 @@ async def search(
|
|||||||
return all_results[:max_results]
|
return all_results[:max_results]
|
||||||
|
|
||||||
|
|
||||||
async def _search_site_browserless(
|
async def _search_site_browserless( # noqa: PLR0912, PLR0915
|
||||||
query: str,
|
query: str,
|
||||||
site: dict,
|
site: dict,
|
||||||
max_results: int,
|
max_results: int,
|
||||||
@@ -190,7 +197,7 @@ async def _search_site_browserless(
|
|||||||
try:
|
try:
|
||||||
context = await browser.new_context(
|
context = await browser.new_context(
|
||||||
viewport={"width": 1920, "height": 1080},
|
viewport={"width": 1920, "height": 1080},
|
||||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
user_agent=USER_AGENT,
|
||||||
locale="fr-FR",
|
locale="fr-FR",
|
||||||
)
|
)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
@@ -272,7 +279,7 @@ async def _search_site_browserless(
|
|||||||
|
|
||||||
# Extraire le titre
|
# Extraire le titre
|
||||||
title = _extract_title(link)
|
title = _extract_title(link)
|
||||||
if not title or len(title) < 3:
|
if not title or len(title) < MIN_TITLE_LENGTH:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
results.append(SearchResult(
|
results.append(SearchResult(
|
||||||
@@ -295,24 +302,52 @@ async def _search_site_browserless(
|
|||||||
|
|
||||||
def _get_default_search_url(domain: str) -> str | None:
|
def _get_default_search_url(domain: str) -> str | None:
|
||||||
"""Retourne l'URL de recherche par défaut pour un domaine"""
|
"""Retourne l'URL de recherche par défaut pour un domaine"""
|
||||||
|
# Nettoyer le domaine pour la comparaison
|
||||||
|
clean = domain.lower().strip()
|
||||||
|
if clean.startswith("www."):
|
||||||
|
clean = clean[4:]
|
||||||
|
|
||||||
for key, config in DEFAULT_SITE_CONFIGS.items():
|
for key, config in DEFAULT_SITE_CONFIGS.items():
|
||||||
if key in domain or domain in key:
|
key_clean = key.lower().strip()
|
||||||
|
if key_clean.startswith("www."):
|
||||||
|
key_clean = key_clean[4:]
|
||||||
|
|
||||||
|
# Comparaison exacte ou partielle
|
||||||
|
if clean == key_clean or key_clean in clean or clean in key_clean:
|
||||||
return config.get("search_url")
|
return config.get("search_url")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_default_product_selector(domain: str) -> str | None:
|
def _get_default_product_selector(domain: str) -> str | None:
|
||||||
"""Retourne le sélecteur de produits par défaut pour un domaine"""
|
"""Retourne le sélecteur de produits par défaut pour un domaine"""
|
||||||
|
# Nettoyer le domaine pour la comparaison
|
||||||
|
clean = domain.lower().strip()
|
||||||
|
if clean.startswith("www."):
|
||||||
|
clean = clean[4:]
|
||||||
|
|
||||||
for key, config in DEFAULT_SITE_CONFIGS.items():
|
for key, config in DEFAULT_SITE_CONFIGS.items():
|
||||||
if key in domain or domain in key:
|
key_clean = key.lower().strip()
|
||||||
|
if key_clean.startswith("www."):
|
||||||
|
key_clean = key_clean[4:]
|
||||||
|
|
||||||
|
if clean == key_clean or key_clean in clean or clean in key_clean:
|
||||||
return config.get("product_selector")
|
return config.get("product_selector")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_default_wait_selector(domain: str) -> str | None:
|
def _get_default_wait_selector(domain: str) -> str | None:
|
||||||
"""Retourne le sélecteur d'attente par défaut pour un domaine"""
|
"""Retourne le sélecteur d'attente par défaut pour un domaine"""
|
||||||
|
# Nettoyer le domaine pour la comparaison
|
||||||
|
clean = domain.lower().strip()
|
||||||
|
if clean.startswith("www."):
|
||||||
|
clean = clean[4:]
|
||||||
|
|
||||||
for key, config in DEFAULT_SITE_CONFIGS.items():
|
for key, config in DEFAULT_SITE_CONFIGS.items():
|
||||||
if key in domain or domain in key:
|
key_clean = key.lower().strip()
|
||||||
|
if key_clean.startswith("www."):
|
||||||
|
key_clean = key_clean[4:]
|
||||||
|
|
||||||
|
if clean == key_clean or key_clean in clean or clean in key_clean:
|
||||||
return config.get("wait_selector")
|
return config.get("wait_selector")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -359,12 +394,12 @@ def _extract_title(element) -> str:
|
|||||||
"""Extrait le titre d'un élément"""
|
"""Extrait le titre d'un élément"""
|
||||||
# Essayer différentes méthodes
|
# Essayer différentes méthodes
|
||||||
title = element.get("title", "")
|
title = element.get("title", "")
|
||||||
if title and len(title) > 3:
|
if title and len(title) > MIN_TITLE_LENGTH:
|
||||||
return title.strip()
|
return title.strip()
|
||||||
|
|
||||||
# Texte de l'élément
|
# Texte de l'élément
|
||||||
text = element.get_text(strip=True)
|
text = element.get_text(strip=True)
|
||||||
if text and len(text) > 3:
|
if text and len(text) > MIN_TITLE_LENGTH:
|
||||||
return text[:200]
|
return text[:200]
|
||||||
|
|
||||||
# Attribut aria-label
|
# Attribut aria-label
|
||||||
|
|||||||
Reference in new issue
Block a user