mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-21 06:08:12 +02:00
Merge pull request #33 from R0m1k3/claude/hardcode-website-urls-01QrWZK7suQnuBrxYgK1r5JC
fix: Improve French e-commerce sites configuration
This commit is contained in:
2 files changed
+120
-49
No files matched your search
+2
-2
@@ -38,7 +38,7 @@ class AIExtractionResponse(BaseModel):
|
|||||||
description="Extracted price as a number, or null if no price found",
|
description="Extracted price as a number, or null if no price found",
|
||||||
)
|
)
|
||||||
currency: str = Field(
|
currency: str = Field(
|
||||||
"USD",
|
"EUR",
|
||||||
description="Currency code (ISO 4217)",
|
description="Currency code (ISO 4217)",
|
||||||
)
|
)
|
||||||
in_stock: bool | None = Field(
|
in_stock: bool | None = Field(
|
||||||
@@ -137,7 +137,7 @@ EXTRACTION_PROMPT_TEMPLATE = """Extract product price and stock status from the
|
|||||||
Respond ONLY with valid JSON:
|
Respond ONLY with valid JSON:
|
||||||
{{
|
{{
|
||||||
"price": <number or null>,
|
"price": <number or null>,
|
||||||
"currency": "USD",
|
"currency": "EUR",
|
||||||
"in_stock": <true, false, or null>,
|
"in_stock": <true, false, or null>,
|
||||||
"price_confidence": <0.0 to 1.0>,
|
"price_confidence": <0.0 to 1.0>,
|
||||||
"in_stock_confidence": <0.0 to 1.0>,
|
"in_stock_confidence": <0.0 to 1.0>,
|
||||||
|
|||||||
@@ -126,12 +126,13 @@ DEFAULT_SITE_CONFIGS = {
|
|||||||
# === GRANDES SURFACES ===
|
# === GRANDES SURFACES ===
|
||||||
"e.leclerc": {
|
"e.leclerc": {
|
||||||
"name": "E.Leclerc",
|
"name": "E.Leclerc",
|
||||||
"search_url": "https://www.e.leclerc/recherche?q={query}",
|
"search_url": "https://www.e.leclerc/cat/recherche~q={query}",
|
||||||
"product_selector": (
|
"product_selector": (
|
||||||
"a[data-testid='product-card-link'], .product-card a, "
|
".product-card a, .product-thumbnail a, "
|
||||||
".product-tile a, a[href*='/fp/']"
|
"a[href*='/fp/'], a[href*='/cat/'], .product-tile a, "
|
||||||
|
"[data-testid='product-card'] a"
|
||||||
),
|
),
|
||||||
"wait_selector": ".product-card, .products-grid, [data-testid='product-card']",
|
"wait_selector": ".product-card, .products-grid, [data-testid='product-grid']",
|
||||||
"category": "Grande Surface",
|
"category": "Grande Surface",
|
||||||
"requires_js": True,
|
"requires_js": True,
|
||||||
"priority": 8,
|
"priority": 8,
|
||||||
@@ -140,10 +141,11 @@ DEFAULT_SITE_CONFIGS = {
|
|||||||
"name": "Auchan",
|
"name": "Auchan",
|
||||||
"search_url": "https://www.auchan.fr/recherche?text={query}",
|
"search_url": "https://www.auchan.fr/recherche?text={query}",
|
||||||
"product_selector": (
|
"product_selector": (
|
||||||
".product-card a, .product-tile a, "
|
".product-thumbnail a, .product-card__link, "
|
||||||
"a[href*='/p/'], .product-item a"
|
"a[href*='/p/'], .list-product__item a, "
|
||||||
|
"[data-testid='product-card'] a, .product-card a"
|
||||||
),
|
),
|
||||||
"wait_selector": ".product-card, .products, .product-list",
|
"wait_selector": ".list-product, .product-grid, [data-testid='product-grid']",
|
||||||
"category": "Grande Surface",
|
"category": "Grande Surface",
|
||||||
"requires_js": True,
|
"requires_js": True,
|
||||||
"priority": 9,
|
"priority": 9,
|
||||||
@@ -152,10 +154,11 @@ DEFAULT_SITE_CONFIGS = {
|
|||||||
"name": "Carrefour",
|
"name": "Carrefour",
|
||||||
"search_url": "https://www.carrefour.fr/s?q={query}",
|
"search_url": "https://www.carrefour.fr/s?q={query}",
|
||||||
"product_selector": (
|
"product_selector": (
|
||||||
"a[data-testid='product-card-link'], .product-card a, "
|
".product-card-title a, .product-card__link, "
|
||||||
".product-item a, a[href*='/p/']"
|
"a[href*='/p/'], .product-thumbnail a, "
|
||||||
|
"[data-testid='product-card'] a, .product-card a"
|
||||||
),
|
),
|
||||||
"wait_selector": ".product-card, .product-list, [data-testid='product-card']",
|
"wait_selector": ".product-grid, .product-list, [data-testid='product-grid']",
|
||||||
"category": "Grande Surface",
|
"category": "Grande Surface",
|
||||||
"requires_js": True,
|
"requires_js": True,
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
@@ -163,9 +166,12 @@ DEFAULT_SITE_CONFIGS = {
|
|||||||
# === E-COMMERCE GÉNÉRALISTE ===
|
# === E-COMMERCE GÉNÉRALISTE ===
|
||||||
"amazon.fr": {
|
"amazon.fr": {
|
||||||
"name": "Amazon France",
|
"name": "Amazon France",
|
||||||
"search_url": "https://www.amazon.fr/s?k={query}",
|
"search_url": "https://www.amazon.fr/s?k={query}&language=fr_FR",
|
||||||
"product_selector": "div[data-component-type='s-search-result'] h2 a",
|
"product_selector": (
|
||||||
"wait_selector": "div[data-component-type='s-search-result']",
|
"div[data-component-type='s-search-result'] h2 a, "
|
||||||
|
".s-result-item h2 a, [data-asin] h2 a"
|
||||||
|
),
|
||||||
|
"wait_selector": "div[data-component-type='s-search-result'], .s-result-item",
|
||||||
"category": "E-commerce",
|
"category": "E-commerce",
|
||||||
"requires_js": True,
|
"requires_js": True,
|
||||||
"priority": 11,
|
"priority": 11,
|
||||||
@@ -229,63 +235,128 @@ DEFAULT_SITE_CONFIGS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Sélecteurs communs pour les bannières de cookies
|
# Sélecteurs communs pour les bannières de cookies
|
||||||
|
# Ordonnés du plus spécifique au plus générique pour une meilleure détection
|
||||||
COOKIE_ACCEPT_SELECTORS = [
|
COOKIE_ACCEPT_SELECTORS = [
|
||||||
# Boutons d'acceptation génériques
|
# === SITES SPÉCIFIQUES FRANÇAIS ===
|
||||||
|
# Amazon France
|
||||||
|
"#sp-cc-accept",
|
||||||
|
"input[name='accept'][type='submit']",
|
||||||
|
"[data-action='sp-cc']",
|
||||||
|
# E.Leclerc
|
||||||
|
"#onetrust-accept-btn-handler",
|
||||||
|
".onetrust-accept-btn-handler",
|
||||||
|
# Auchan
|
||||||
|
"#popin_tc_privacy_button_2",
|
||||||
|
".popin_tc_privacy_button",
|
||||||
|
"#didomi-notice-agree-button",
|
||||||
|
# Carrefour
|
||||||
|
"#onetrust-accept-btn-handler",
|
||||||
|
"[data-testid='accept-cookies-button']",
|
||||||
|
# Cdiscount
|
||||||
|
"#footer_tc_privacy_button_2",
|
||||||
|
".privacy_prompt_accept",
|
||||||
|
# Darty / Fnac
|
||||||
|
"#onetrust-accept-btn-handler",
|
||||||
|
# Boulanger
|
||||||
|
".bcom-consent-accept-all",
|
||||||
|
"#cookieBanner-accept",
|
||||||
|
|
||||||
|
# === CMP (Consent Management Platforms) ===
|
||||||
|
"#didomi-notice-agree-button",
|
||||||
|
".didomi-continue-without-agreeing",
|
||||||
|
"#onetrust-accept-btn-handler",
|
||||||
|
".onetrust-accept-btn-handler",
|
||||||
|
"#tarteaucitronPersonalize2",
|
||||||
|
".tarteaucitronAllow",
|
||||||
|
"#tarteaucitronAllDenied2",
|
||||||
|
".cc-btn.cc-allow",
|
||||||
|
".cky-btn-accept",
|
||||||
|
"#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll",
|
||||||
|
"#CybotCookiebotDialogBodyButtonAccept",
|
||||||
|
".qc-cmp2-summary-buttons button:first-child",
|
||||||
|
"[data-accept-cookies]",
|
||||||
|
"[data-gdpr-accept]",
|
||||||
|
"#axeptio_btn_acceptAll",
|
||||||
|
".axeptio_acceptAll",
|
||||||
|
|
||||||
|
# === TEXTE FRANÇAIS (Playwright :has-text) ===
|
||||||
|
"button:has-text('Tout accepter')",
|
||||||
|
"button:has-text('Accepter tout')",
|
||||||
|
"button:has-text('Accepter et fermer')",
|
||||||
|
"button:has-text('Accepter les cookies')",
|
||||||
|
"button:has-text('Accepter')",
|
||||||
|
"button:has-text('J\\'accepte')",
|
||||||
|
"button:has-text('OK')",
|
||||||
|
"button:has-text('Continuer')",
|
||||||
|
"button:has-text('Continuer sans accepter')",
|
||||||
|
"a:has-text('Tout accepter')",
|
||||||
|
"a:has-text('Accepter')",
|
||||||
|
"a:has-text('J\\'accepte')",
|
||||||
|
"span:has-text('Tout accepter')",
|
||||||
|
|
||||||
|
# === TEXTE ANGLAIS ===
|
||||||
|
"button:has-text('Accept all')",
|
||||||
|
"button:has-text('Accept cookies')",
|
||||||
|
"button:has-text('Accept')",
|
||||||
|
"button:has-text('I accept')",
|
||||||
|
"button:has-text('Allow all')",
|
||||||
|
|
||||||
|
# === GÉNÉRIQUES ===
|
||||||
"button[id*='accept']",
|
"button[id*='accept']",
|
||||||
"button[id*='cookie']",
|
"button[id*='cookie']",
|
||||||
"button[class*='accept']",
|
"button[class*='accept']",
|
||||||
"button[class*='cookie']",
|
"button[class*='cookie']",
|
||||||
"button[data-testid*='accept']",
|
"button[data-testid*='accept']",
|
||||||
"button[data-testid*='cookie']",
|
"button[data-testid*='cookie']",
|
||||||
# Texte français
|
|
||||||
"button:has-text('Accepter')",
|
|
||||||
"button:has-text('J\\'accepte')",
|
|
||||||
"button:has-text('Tout accepter')",
|
|
||||||
"button:has-text('Accepter tout')",
|
|
||||||
"button:has-text('Accepter les cookies')",
|
|
||||||
"button:has-text('OK')",
|
|
||||||
"button:has-text('Continuer')",
|
|
||||||
# Texte anglais (certains sites utilisent l'anglais)
|
|
||||||
"button:has-text('Accept')",
|
|
||||||
"button:has-text('Accept all')",
|
|
||||||
"button:has-text('Accept cookies')",
|
|
||||||
"button:has-text('I accept')",
|
|
||||||
# Liens
|
|
||||||
"a:has-text('Accepter')",
|
|
||||||
"a:has-text('J\\'accepte')",
|
|
||||||
"a:has-text('Tout accepter')",
|
|
||||||
# Classes communes de CMP (Consent Management Platforms)
|
|
||||||
".didomi-continue-without-agreeing",
|
|
||||||
"#didomi-notice-agree-button",
|
|
||||||
".onetrust-accept-btn-handler",
|
|
||||||
"#onetrust-accept-btn-handler",
|
|
||||||
".tarteaucitronAllow",
|
|
||||||
"#tarteaucitronPersonalize",
|
|
||||||
".cc-btn.cc-allow",
|
|
||||||
".cky-btn-accept",
|
|
||||||
"#CybsAccept",
|
|
||||||
".qc-cmp2-summary-buttons button:first-child",
|
|
||||||
"[data-accept-cookies]",
|
|
||||||
"[data-gdpr-accept]",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def _accept_cookies(page, domain: str) -> bool:
|
async def _accept_cookies(page, domain: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Tente d'accepter les cookies sur une page.
|
Tente d'accepter les cookies sur une page.
|
||||||
|
Essaie plusieurs fois et gère les iframes.
|
||||||
Retourne True si un bouton a été cliqué, False sinon.
|
Retourne True si un bouton a été cliqué, False sinon.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Attendre un court moment pour que la bannière apparaisse
|
# Attendre un court moment pour que la bannière apparaisse
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
|
# Essayer d'abord dans la page principale
|
||||||
for selector in COOKIE_ACCEPT_SELECTORS:
|
for selector in COOKIE_ACCEPT_SELECTORS:
|
||||||
try:
|
try:
|
||||||
button = page.locator(selector).first
|
button = page.locator(selector).first
|
||||||
if await button.is_visible(timeout=500):
|
if await button.is_visible(timeout=300):
|
||||||
await button.click(timeout=2000)
|
await button.click(timeout=2000)
|
||||||
logger.info(f"{domain}: Cookies acceptés avec sélecteur {selector}")
|
logger.info(f"{domain}: Cookies acceptés avec sélecteur {selector}")
|
||||||
# Attendre que la bannière disparaisse
|
await asyncio.sleep(0.5)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Essayer dans les iframes (certains CMP utilisent des iframes)
|
||||||
|
frames = page.frames
|
||||||
|
for frame in frames:
|
||||||
|
if frame == page.main_frame:
|
||||||
|
continue
|
||||||
|
for selector in COOKIE_ACCEPT_SELECTORS[:20]: # Limiter aux premiers sélecteurs
|
||||||
|
try:
|
||||||
|
button = frame.locator(selector).first
|
||||||
|
if await button.is_visible(timeout=200):
|
||||||
|
await button.click(timeout=2000)
|
||||||
|
logger.info(f"{domain}: Cookies acceptés dans iframe avec {selector}")
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Deuxième tentative après un peu plus d'attente (pour les bannières lentes)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
for selector in COOKIE_ACCEPT_SELECTORS[:30]:
|
||||||
|
try:
|
||||||
|
button = page.locator(selector).first
|
||||||
|
if await button.is_visible(timeout=200):
|
||||||
|
await button.click(timeout=2000)
|
||||||
|
logger.info(f"{domain}: Cookies acceptés (2e essai) avec {selector}")
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in new issue
Block a user