Files
Priceflow/app/services/parsers/fnac_parser.py
T
Claude 43ea314e41 fix: Improve Fnac image extraction and add Shopify fallbacks
Fixes two issues:

**1. Fnac - No Images Displayed**
Problem: FnacParser only searched for images in parent container,
missing images nested directly in the link element.

Solution: Two-priority search
- PRIORITY 1: Search in link element first (picture, img)
- PRIORITY 2: Search in parent container
- Added warning log when no image found
- Ensures parent is always defined before use

**2. L'Incroyable - 0 Products (Shopify sites)**
Problem: GenericParser fallbacks didn't include Shopify patterns.
L'Incroyable uses Shopify structure with /products/ URLs.

Solution: Added Shopify-specific fallbacks
- a[href*='/products/'] (Shopify product URLs)
- a.product-card__link (Shopify class)
- a[href*='/collections/'] (Shopify collections)
- li[class*='product'] a (list-based layouts)

**Fallback Order (now 17 selectors):**
1. Product URL patterns (Shopify, generic)
2. Semantic classes (article, .product-card)
3. Shopify-specific selectors
4. Generic structure patterns
5. Last resort wildcards

**Expected Impact:**
- ✅ Fnac images should now display
- ✅ L'Incroyable should find products (Shopify)
- ✅ Better coverage of Shopify-based stores
2025-11-30 16:25:30 +00:00

160 lines
5.2 KiB
Python

"""
Fnac Parser - Specialized parser for Fnac.com
"""
from bs4 import BeautifulSoup
from .base_parser import BaseParser, ProductResult
class FnacParser(BaseParser):
"""
Parser for Fnac.com
Key selectors:
- Product links: a[href*='/a'], a.Article-title, article a
- Images: img, picture source
"""
def __init__(self):
super().__init__(
site_name="Fnac",
base_url="https://www.fnac.com"
)
def parse_search_results(self, html: str, query: str, search_url: str) -> list[ProductResult]:
"""Parse Fnac search results"""
soup = BeautifulSoup(html, 'html.parser')
products = []
# Try multiple selectors
selectors = [
"a[href*='/a']",
"a.Article-title",
"article a",
".Article-item a",
]
links = []
for selector in selectors:
links = soup.select(selector)
if links:
self.logger.info(f"Found {len(links)} products with selector: {selector}")
break
if not links:
self.logger.warning("No products found on Fnac")
return []
seen_urls = set()
for link in links:
try:
href = link.get("href")
if not href:
continue
# Filter Fnac URLs
if "/a" not in href:
continue
full_url = self.make_absolute_url(href)
if full_url in seen_urls:
continue
seen_urls.add(full_url)
# Extract title
title = link.get_text(strip=True)
if not title:
title = link.get("title", "")
# Try aria-label
if not title:
title = link.get("aria-label", "")
if not title or len(title) < 3:
continue
# Filter by query
if not self.filter_by_query(title, query):
continue
# Extract image - try in link first, then parent
image_url = None
parent = link.find_parent(["article", "li", "div"])
# PRIORITY 1: Try in the link itself
picture = link.find("picture")
if picture:
source = picture.find("source")
if source and source.get("srcset"):
image_url = source.get("srcset").split(",")[0].split()[0]
if not image_url:
img = picture.find("img")
if img:
image_url = self._get_image_src(img)
# Try img directly in link
if not image_url:
img = link.find("img")
if img:
image_url = self._get_image_src(img)
# PRIORITY 2: Try in parent container
if not image_url and parent:
# Try picture > source
picture = parent.find("picture")
if picture:
source = picture.find("source")
if source and source.get("srcset"):
image_url = source.get("srcset").split(",")[0].split()[0]
if not image_url:
img = picture.find("img")
if img:
image_url = self._get_image_src(img)
# Fallback to img
if not image_url:
img = parent.find("img")
if img:
image_url = self._get_image_src(img)
if image_url:
image_url = self.make_absolute_url(image_url)
else:
self.logger.warning(f"No image found for Fnac product: {title[:40]}...")
# Extract price
price = None
if parent:
price_selectors = [
".Article-price",
"[class*='price']",
"span[class*='prix']",
"[data-testid='price']",
]
for price_sel in price_selectors:
price_elem = parent.select_one(price_sel)
if price_elem:
price = self.parse_price_text(price_elem.get_text(strip=True))
if price:
break
products.append(ProductResult(
title=title,
url=full_url,
source=self.site_name,
price=price,
currency="EUR",
image_url=image_url,
snippet=f"Product from {self.site_name}"
))
except Exception as e:
self.logger.error(f"Error parsing Fnac product: {e}")
continue
self.logger.info(f"Extracted {len(products)} products from Fnac")
return products