diff --git a/app/services/browserless_service.py b/app/services/browserless_service.py index 2840f5a..07dad1b 100644 --- a/app/services/browserless_service.py +++ b/app/services/browserless_service.py @@ -49,13 +49,17 @@ POPUP_SELECTORS = [ "button:has-text('Tout accepter')", "button:has-text('Accepter')", "input[aria-labelledby='sp-cc-accept-label']", - # Amazon interstitials + "#nav-flyout-prime button", + + # Amazon Interstitials (Soft blocks) "button:has-text('Continuer les achats')", "span:has-text('Continuer les achats')", "a:has-text('Continuer les achats')", "input[value='Continuer les achats']", "input[value='Continue shopping']", + "span.a-button-inner > input.a-button-input[type='submit']", "form:has-text('Continuer les achats') input[type='submit']", + "[aria-labelledby='continue-shopping-label']", ] @@ -200,21 +204,42 @@ class BrowserlessService: @staticmethod async def _handle_popups(page: Page): - """Attempt to close popups and cookie banners.""" + """Attempt to close popups and cookie banners with retry logic.""" logger.debug("Attempting to close popups...") + + # First pass: Try to close all visible popups + closed_popups = [] for popup_selector in POPUP_SELECTORS: try: if await page.locator(popup_selector).count() > 0: logger.info(f"🚫 Closing popup: {popup_selector}") await page.locator(popup_selector).first.click(timeout=2000) - await page.wait_for_timeout(500) + closed_popups.append(popup_selector) + await page.wait_for_timeout(1000) except Exception: pass - try: - await page.keyboard.press("Escape") - except Exception: - pass + # Try Escape key multiple times + for _ in range(2): + try: + await page.keyboard.press("Escape") + await page.wait_for_timeout(500) + except Exception: + pass + + # Second pass: Verify and retry if any popups are still visible + if closed_popups: + logger.info(f"Verifying {len(closed_popups)} closed popup(s)...") + await page.wait_for_timeout(1000) + + for popup_selector in closed_popups: + try: + if await page.locator(popup_selector).count() > 0: + logger.warning(f"Popup reappeared, retrying: {popup_selector}") + await page.locator(popup_selector).first.click(timeout=2000) + await page.wait_for_timeout(1000) + except Exception: + pass @staticmethod async def _extract_amazon_price(page: Page) -> str: diff --git a/app/services/item_service.py b/app/services/item_service.py index 4fa7c4f..d809cd8 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -16,15 +16,28 @@ class ItemService: @staticmethod def get_items(db: Session): items = db.query(models.Item).all() - return [ - { - **item.__dict__, - "screenshot_url": f"/screenshots/item_{item.id}.png" - if os.path.exists(f"screenshots/item_{item.id}.png") - else None, - } - for item in items - ] + result = [] + for item in items: + # Get latest price history with screenshot + latest_history = ( + db.query(models.PriceHistory) + .filter(models.PriceHistory.item_id == item.id) + .filter(models.PriceHistory.screenshot_path.isnot(None)) + .order_by(models.PriceHistory.timestamp.desc()) + .first() + ) + + screenshot_url = None + if latest_history and latest_history.screenshot_path: + # Convert absolute path /app/screenshots/... to URL /screenshots/... + filename = os.path.basename(latest_history.screenshot_path) + screenshot_url = f"/screenshots/{filename}" + elif os.path.exists(f"screenshots/item_{item.id}.png"): + # Fallback to legacy static file + screenshot_url = f"/screenshots/item_{item.id}.png" + + result.append({**item.__dict__, "screenshot_url": screenshot_url}) + return result @staticmethod def create_item(db: Session, item: schemas.ItemCreate): diff --git a/app/services/tracking_scraper_service.py b/app/services/tracking_scraper_service.py index 520428c..f2926e1 100644 --- a/app/services/tracking_scraper_service.py +++ b/app/services/tracking_scraper_service.py @@ -20,6 +20,12 @@ POPUP_SELECTORS = [ "input[aria-labelledby='sp-cc-accept-label']", "#nav-flyout-prime button", # Prime flyout + # Amazon Interstitials (Soft blocks) + "button:has-text('Continuer les achats')", + "input[value='Continuer les achats']", + "span.a-button-inner > input.a-button-input[type='submit']", + "[aria-labelledby='continue-shopping-label']", + # French RGPD/Cookie consent platforms "#axeptio_btn_acceptAll", # Axeptio "#axeptio_main_button",