mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-14 18:56:43 +02:00
Merge pull request #134 from R0m1k3/claude/add-product-monitoring-01DyNDGnKBX4fHNSjAkwVckd
feat: Add direct Amazon price extraction with CSS selectors
This commit is contained in:
2 files changed
+68
-1
No files matched your search
+7
-1
@@ -133,13 +133,19 @@ class AIExtractionMetadata(BaseModel):
|
||||
EXTRACTION_PROMPT_TEMPLATE = """Extract product price and stock status from this French e-commerce page.
|
||||
|
||||
**PRICE (IMPORTANT - French format):**
|
||||
- **FIRST**: Look for "PRIX DÉTECTÉ:" at the start of text - this is the extracted price
|
||||
- French prices use COMMA for decimals: "12,99 €" means 12.99
|
||||
- Thousands separator is SPACE or DOT: "1 234,56 €" or "1.234,56 €" means 1234.56
|
||||
- Currency symbol is usually € at the end
|
||||
- Look for: price tags, "Prix:", "€", numbers near "Ajouter au panier"
|
||||
- Look for: "PRIX DÉTECTÉ:", price tags, "Prix:", "€", numbers near "Ajouter au panier"
|
||||
- Extract as DECIMAL NUMBER (convert comma to dot): 12,99 -> 12.99
|
||||
- Ignore crossed-out/barré prices (old prices)
|
||||
- If multiple prices, take the current/main price (not the original)
|
||||
- Examples of valid prices:
|
||||
* "89,99 €" -> 89.99
|
||||
* "1 234,56 €" -> 1234.56
|
||||
* "PRIX DÉTECTÉ: 89,99 €" -> 89.99
|
||||
- If you find ANY price with € symbol, extract it with confidence >= 0.8
|
||||
- If unclear: set null and confidence < 0.5
|
||||
|
||||
**STOCK:**
|
||||
|
||||
@@ -142,6 +142,58 @@ class BrowserlessService:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _extract_amazon_price(self, page: Page) -> str:
|
||||
"""
|
||||
Extract price directly from Amazon product page using CSS selectors.
|
||||
Returns price text (e.g., "89,99 €") or empty string if not found.
|
||||
"""
|
||||
# Amazon price selectors in priority order
|
||||
# Note: .a-offscreen elements are hidden but contain the full price for screen readers
|
||||
price_selectors = [
|
||||
".a-price .a-offscreen", # Most reliable - hidden text with full price
|
||||
"#corePrice_desktop .a-price .a-offscreen",
|
||||
"#corePriceDisplay_desktop_feature_div .a-price .a-offscreen",
|
||||
".a-price[data-a-color='price'] .a-offscreen",
|
||||
"#priceblock_ourprice", # Older Amazon layout
|
||||
"#priceblock_dealprice", # Deal prices
|
||||
"span.a-price-whole", # Visible whole number part
|
||||
]
|
||||
|
||||
for selector in price_selectors:
|
||||
try:
|
||||
# Don't check visibility for .a-offscreen elements (they're hidden by design)
|
||||
element = page.locator(selector).first
|
||||
|
||||
# For offscreen elements, just check if they exist and have text
|
||||
if "offscreen" in selector or "priceblock" in selector:
|
||||
price_text = await element.inner_text(timeout=2000)
|
||||
else:
|
||||
# For visible elements, check visibility first
|
||||
if await element.is_visible(timeout=2000):
|
||||
price_text = await element.inner_text()
|
||||
else:
|
||||
continue
|
||||
|
||||
if price_text and price_text.strip():
|
||||
logger.info(f"Extracted Amazon price via {selector}: {price_text}")
|
||||
return price_text.strip()
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Try combination: whole + fraction
|
||||
try:
|
||||
whole = await page.locator("span.a-price-whole").first.inner_text()
|
||||
fraction = await page.locator("span.a-price-fraction").first.inner_text()
|
||||
if whole and fraction:
|
||||
price_text = f"{whole}{fraction}"
|
||||
logger.info(f"Extracted Amazon price from whole+fraction: {price_text}")
|
||||
return price_text
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.warning("Could not extract Amazon price with any selector")
|
||||
return ""
|
||||
|
||||
async def get_page_content(self, url: str, use_proxy: bool = False, wait_selector: str = None) -> tuple[str, str]:
|
||||
"""
|
||||
Fetch page content with full stealth lifecycle.
|
||||
@@ -248,6 +300,15 @@ class BrowserlessService:
|
||||
# Fallback to HTML content if inner_text fails
|
||||
logger.warning(f"Failed to extract inner_text, falling back to HTML: {e}")
|
||||
content = await page.content()
|
||||
|
||||
# For Amazon, extract price directly and prepend to content
|
||||
# This helps AI by providing explicit price information
|
||||
if "amazon" in url.lower() and "/dp/" in url:
|
||||
amazon_price_text = await self._extract_amazon_price(page)
|
||||
if amazon_price_text:
|
||||
content = f"PRIX DÉTECTÉ: {amazon_price_text}\n\n{content}"
|
||||
logger.info(f"Prepended Amazon price to content: {amazon_price_text}")
|
||||
|
||||
|
||||
# Take screenshot if needed for AI analysis
|
||||
screenshot_path = ""
|
||||
|
||||
Reference in new issue
Block a user