mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-23 23:28:12 +02:00
Merge pull request #258 from R0m1k3/antigravity
feat: Add Amazon scraper service with persistent browser, anti-detect…
This commit is contained in:
1 file changed
+55
-12
@@ -318,6 +318,16 @@ class AmazonScraperService:
|
|||||||
products = []
|
products = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# First, save a debug screenshot and HTML for analysis
|
||||||
|
try:
|
||||||
|
await page.screenshot(path="/tmp/amazon_dom_debug.png")
|
||||||
|
html = await page.content()
|
||||||
|
with open("/tmp/amazon_dom_debug.html", "w", encoding="utf-8") as f:
|
||||||
|
f.write(html)
|
||||||
|
logger.info("📸 Saved debug screenshot and HTML to /tmp/amazon_dom_debug.*")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not save debug files: {e}")
|
||||||
|
|
||||||
# Find all product cards using Playwright locators
|
# Find all product cards using Playwright locators
|
||||||
product_cards = page.locator('div[data-component-type="s-search-result"]')
|
product_cards = page.locator('div[data-component-type="s-search-result"]')
|
||||||
count = await product_cards.count()
|
count = await product_cards.count()
|
||||||
@@ -336,27 +346,46 @@ class AmazonScraperService:
|
|||||||
# Get ASIN
|
# Get ASIN
|
||||||
asin = await card.get_attribute("data-asin")
|
asin = await card.get_attribute("data-asin")
|
||||||
if not asin:
|
if not asin:
|
||||||
|
logger.debug(f" Card {idx}: No ASIN, skipping")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Log card outer HTML for debugging (first 200 chars)
|
||||||
|
try:
|
||||||
|
outer_html = await card.evaluate("el => el.outerHTML")
|
||||||
|
logger.debug(f" Card {idx} ASIN={asin}: {outer_html[:200]}...")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if sponsored
|
# Check if sponsored
|
||||||
sponsored = await card.locator('[data-component-type="sp-sponsored-result"]').count() > 0
|
sponsored = await card.locator('[data-component-type="sp-sponsored-result"]').count() > 0
|
||||||
|
|
||||||
# Get title - try multiple selectors
|
# Get title - try multiple selectors
|
||||||
title = None
|
title = None
|
||||||
for selector in ["h2 a span", "h2 span", "h2.s-line-clamp-2 span"]:
|
for selector in ["h2 a span", "h2 span", "h2.s-line-clamp-2 span", "h2"]:
|
||||||
title_elem = card.locator(selector).first
|
title_elem = card.locator(selector).first
|
||||||
if await title_elem.count() > 0:
|
if await title_elem.count() > 0:
|
||||||
title = await title_elem.inner_text()
|
try:
|
||||||
if title:
|
title = await title_elem.inner_text()
|
||||||
title = title.strip()
|
if title:
|
||||||
break
|
title = title.strip()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if not title:
|
if not title:
|
||||||
|
# Try to get any text content from the card
|
||||||
|
try:
|
||||||
|
all_text = await card.inner_text()
|
||||||
|
logger.debug(f" Card {idx}: No h2 title. Card text preview: {all_text[:100]}...")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
logger.debug(f" Card {idx}: Found title = {title[:50]}...")
|
||||||
|
|
||||||
# Get URL
|
# Get URL
|
||||||
url = None
|
url = None
|
||||||
link_elem = card.locator("h2 a").first
|
link_elem = card.locator("h2 a, a[href*='/dp/']").first
|
||||||
if await link_elem.count() > 0:
|
if await link_elem.count() > 0:
|
||||||
href = await link_elem.get_attribute("href")
|
href = await link_elem.get_attribute("href")
|
||||||
if href and href != "#":
|
if href and href != "#":
|
||||||
@@ -366,17 +395,26 @@ class AmazonScraperService:
|
|||||||
url = href
|
url = href
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
|
logger.debug(f" Card {idx}: No valid URL found")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
logger.debug(f" Card {idx}: Found URL = {url[:60]}...")
|
||||||
|
|
||||||
# Get price - try multiple selectors
|
# Get price - try multiple selectors
|
||||||
price = None
|
price = None
|
||||||
for selector in [".a-price .a-offscreen", ".a-price-whole", "span.a-price span.a-offscreen"]:
|
for selector in [
|
||||||
|
".a-price .a-offscreen",
|
||||||
|
".a-price-whole",
|
||||||
|
"span.a-price span.a-offscreen",
|
||||||
|
".a-price",
|
||||||
|
]:
|
||||||
price_elem = card.locator(selector).first
|
price_elem = card.locator(selector).first
|
||||||
if await price_elem.count() > 0:
|
if await price_elem.count() > 0:
|
||||||
try:
|
try:
|
||||||
price_text = await price_elem.inner_text()
|
price_text = await price_elem.inner_text()
|
||||||
price = parse_amazon_price(price_text)
|
price = parse_amazon_price(price_text)
|
||||||
if price:
|
if price:
|
||||||
|
logger.debug(f" Card {idx}: Found price = {price}€")
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -393,12 +431,17 @@ class AmazonScraperService:
|
|||||||
|
|
||||||
# Get rating
|
# Get rating
|
||||||
rating = None
|
rating = None
|
||||||
for selector in ['[aria-label*="étoile"]', '[aria-label*="star"]']:
|
for selector in ['[aria-label*="étoile"]', '[aria-label*="star"]', "i.a-icon-star span"]:
|
||||||
rating_elem = card.locator(selector).first
|
rating_elem = card.locator(selector).first
|
||||||
if await rating_elem.count() > 0:
|
if await rating_elem.count() > 0:
|
||||||
try:
|
try:
|
||||||
aria_label = await rating_elem.get_attribute("aria-label")
|
aria_label = await rating_elem.get_attribute("aria-label")
|
||||||
rating = parse_rating(aria_label or "")
|
if aria_label:
|
||||||
|
rating = parse_rating(aria_label)
|
||||||
|
else:
|
||||||
|
# Try inner text
|
||||||
|
rating_text = await rating_elem.inner_text()
|
||||||
|
rating = parse_rating(rating_text)
|
||||||
if rating:
|
if rating:
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -406,7 +449,7 @@ class AmazonScraperService:
|
|||||||
|
|
||||||
# Get reviews count
|
# Get reviews count
|
||||||
reviews_count = None
|
reviews_count = None
|
||||||
reviews_elem = card.locator("span.s-underline-text").first
|
reviews_elem = card.locator("span.s-underline-text, span[aria-label*='évaluation']").first
|
||||||
if await reviews_elem.count() > 0:
|
if await reviews_elem.count() > 0:
|
||||||
try:
|
try:
|
||||||
reviews_text = await reviews_elem.inner_text()
|
reviews_text = await reviews_elem.inner_text()
|
||||||
@@ -416,7 +459,7 @@ class AmazonScraperService:
|
|||||||
|
|
||||||
# Get image URL
|
# Get image URL
|
||||||
image_url = None
|
image_url = None
|
||||||
img_elem = card.locator("img.s-image").first
|
img_elem = card.locator("img.s-image, img").first
|
||||||
if await img_elem.count() > 0:
|
if await img_elem.count() > 0:
|
||||||
image_url = await img_elem.get_attribute("src")
|
image_url = await img_elem.get_attribute("src")
|
||||||
|
|
||||||
@@ -439,7 +482,7 @@ class AmazonScraperService:
|
|||||||
sponsored=sponsored,
|
sponsored=sponsored,
|
||||||
)
|
)
|
||||||
products.append(product)
|
products.append(product)
|
||||||
logger.debug(f" ✓ DOM [{len(products)}] {title[:40]}... - {price}€")
|
logger.info(f" ✓ DOM [{len(products)}] {title[:40]}... - {price}€")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error extracting product {idx} from DOM: {e}")
|
logger.warning(f"Error extracting product {idx} from DOM: {e}")
|
||||||
|
|||||||
Reference in new issue
Block a user